2
* Copyright (C) 1999 Lars Knoll (knoll@kde.org)
3
* (C) 1999 Antti Koivisto (koivisto@kde.org)
4
* (C) 2000 Dirk Mueller (mueller@kde.org)
5
* (C) 2006 Allan Sandfeld Jensen (kde@carewolf.com)
6
* (C) 2006 Samuel Weinig (sam.weinig@gmail.com)
7
* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009, 2010, 2011 Apple Inc. All rights reserved.
8
* Copyright (C) 2010 Google Inc. All rights reserved.
9
* Copyright (C) Research In Motion Limited 2011-2012. All rights reserved.
11
* This library is free software; you can redistribute it and/or
12
* modify it under the terms of the GNU Library General Public
13
* License as published by the Free Software Foundation; either
14
* version 2 of the License, or (at your option) any later version.
16
* This library is distributed in the hope that it will be useful,
17
* but WITHOUT ANY WARRANTY; without even the implied warranty of
18
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19
* Library General Public License for more details.
21
* You should have received a copy of the GNU Library General Public License
22
* along with this library; see the file COPYING.LIB. If not, write to
23
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
24
* Boston, MA 02110-1301, USA.
29
#include "RenderImage.h"
31
#include "BitmapImage.h"
33
#include "FontCache.h"
35
#include "FrameSelection.h"
36
#include "GraphicsContext.h"
37
#include "HTMLAreaElement.h"
38
#include "HTMLImageElement.h"
39
#include "HTMLInputElement.h"
40
#include "HTMLMapElement.h"
41
#include "HTMLNames.h"
42
#include "HitTestResult.h"
44
#include "PaintInfo.h"
45
#include "RenderView.h"
47
#include <wtf/UnusedParam.h>
53
using namespace HTMLNames;
55
RenderImage::RenderImage(Node* node)
56
: RenderReplaced(node, IntSize())
57
, m_needsToSetSizeForAltText(false)
58
, m_didIncrementVisuallyNonEmptyPixelCount(false)
59
, m_isGeneratedContent(false)
64
RenderImage::~RenderImage()
66
ASSERT(m_imageResource);
67
m_imageResource->shutdown();
70
void RenderImage::setImageResource(PassOwnPtr<RenderImageResource> imageResource)
72
ASSERT(!m_imageResource);
73
m_imageResource = imageResource;
74
m_imageResource->initialize(this);
77
// If we'll be displaying either alt text or an image, add some padding.
78
static const unsigned short paddingWidth = 4;
79
static const unsigned short paddingHeight = 4;
81
// Alt text is restricted to this maximum size, in pixels. These are
82
// signed integers because they are compared with other signed values.
83
static const float maxAltTextWidth = 1024;
84
static const int maxAltTextHeight = 256;
86
IntSize RenderImage::imageSizeForError(CachedImage* newImage) const
88
ASSERT_ARG(newImage, newImage);
89
ASSERT_ARG(newImage, newImage->imageForRenderer(this));
92
if (newImage->willPaintBrokenImage()) {
93
float deviceScaleFactor = WebCore::deviceScaleFactor(frame());
94
pair<Image*, float> brokenImageAndImageScaleFactor = newImage->brokenImage(deviceScaleFactor);
95
imageSize = brokenImageAndImageScaleFactor.first->size();
96
imageSize.scale(1 / brokenImageAndImageScaleFactor.second);
98
imageSize = newImage->imageForRenderer(this)->size();
100
// imageSize() returns 0 for the error image. We need the true size of the
101
// error image, so we have to get it by grabbing image() directly.
102
return IntSize(paddingWidth + imageSize.width() * style()->effectiveZoom(), paddingHeight + imageSize.height() * style()->effectiveZoom());
105
// Sets the image height and width to fit the alt text. Returns true if the
106
// image size changed.
107
bool RenderImage::setImageSizeForAltText(CachedImage* newImage /* = 0 */)
110
if (newImage && newImage->imageForRenderer(this))
111
imageSize = imageSizeForError(newImage);
112
else if (!m_altText.isEmpty() || newImage) {
113
// If we'll be displaying either text or an image, add a little padding.
114
imageSize = IntSize(paddingWidth, paddingHeight);
117
// we have an alt and the user meant it (its not a text we invented)
118
if (!m_altText.isEmpty()) {
119
FontCachePurgePreventer fontCachePurgePreventer;
121
const Font& font = style()->font();
122
IntSize textSize(min(font.width(RenderBlock::constructTextRun(this, font, m_altText, style())), maxAltTextWidth), min(font.fontMetrics().height(), maxAltTextHeight));
123
imageSize = imageSize.expandedTo(textSize);
126
if (imageSize == intrinsicSize())
129
setIntrinsicSize(imageSize);
133
void RenderImage::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
135
RenderReplaced::styleDidChange(diff, oldStyle);
136
if (m_needsToSetSizeForAltText) {
137
if (!m_altText.isEmpty() && setImageSizeForAltText(m_imageResource->cachedImage()))
138
imageDimensionsChanged(true /* imageSizeChanged */);
139
m_needsToSetSizeForAltText = false;
141
#if ENABLE(CSS_IMAGE_RESOLUTION)
142
if (diff == StyleDifferenceLayout
143
&& (oldStyle->imageResolution() != style()->imageResolution()
144
|| oldStyle->imageResolutionSnap() != style()->imageResolutionSnap()
145
|| oldStyle->imageResolutionSource() != style()->imageResolutionSource()))
146
imageDimensionsChanged(true /* imageSizeChanged */);
150
void RenderImage::imageChanged(WrappedImagePtr newImage, const IntRect* rect)
152
// FIXME (86669): Instead of the RenderImage determining whether its document is in the page
153
// cache, the RenderImage should remove itself as a client when its document is put into the
155
if (documentBeingDestroyed() || document()->inPageCache())
158
if (hasBoxDecorations() || hasMask())
159
RenderReplaced::imageChanged(newImage, rect);
161
if (!m_imageResource)
164
if (newImage != m_imageResource->imagePtr() || !newImage)
167
if (!m_didIncrementVisuallyNonEmptyPixelCount) {
168
// At a zoom level of 1 the image is guaranteed to have an integer size.
169
view()->frameView()->incrementVisuallyNonEmptyPixelCount(flooredIntSize(m_imageResource->imageSize(1.0f)));
170
m_didIncrementVisuallyNonEmptyPixelCount = true;
173
bool imageSizeChanged = false;
175
// Set image dimensions, taking into account the size of the alt text.
176
if (m_imageResource->errorOccurred()) {
177
if (!m_altText.isEmpty() && document()->hasPendingStyleRecalc()) {
180
m_needsToSetSizeForAltText = true;
181
node()->setNeedsStyleRecalc(SyntheticStyleChange);
185
imageSizeChanged = setImageSizeForAltText(m_imageResource->cachedImage());
188
imageDimensionsChanged(imageSizeChanged, rect);
191
bool RenderImage::updateIntrinsicSizeIfNeeded(const LayoutSize& newSize, bool imageSizeChanged)
193
if (newSize == intrinsicSize() && !imageSizeChanged)
195
if (m_imageResource->errorOccurred())
196
return imageSizeChanged;
197
setIntrinsicSize(newSize);
201
void RenderImage::imageDimensionsChanged(bool imageSizeChanged, const IntRect* rect)
203
#if ENABLE(CSS_IMAGE_RESOLUTION)
204
double scale = style()->imageResolution();
205
if (style()->imageResolutionSnap() == ImageResolutionSnapPixels)
206
scale = roundForImpreciseConversion<int>(scale);
209
bool intrinsicSizeChanged = updateIntrinsicSizeIfNeeded(m_imageResource->imageSize(style()->effectiveZoom() / scale), imageSizeChanged);
211
bool intrinsicSizeChanged = updateIntrinsicSizeIfNeeded(m_imageResource->imageSize(style()->effectiveZoom()), imageSizeChanged);
214
// In the case of generated image content using :before/:after/content, we might not be
215
// in the render tree yet. In that case, we just need to update our intrinsic size.
216
// layout() will be called after we are inserted in the tree which will take care of
217
// what we are doing here.
218
if (!containingBlock())
221
bool shouldRepaint = true;
222
if (intrinsicSizeChanged) {
223
if (!preferredLogicalWidthsDirty())
224
setPreferredLogicalWidthsDirty(true);
225
LogicalExtentComputedValues computedValues;
226
computeLogicalWidthInRegion(computedValues);
227
LayoutUnit newWidth = computedValues.m_extent;
228
computeLogicalHeight(height(), 0, computedValues);
229
LayoutUnit newHeight = computedValues.m_extent;
231
if (imageSizeChanged || width() != newWidth || height() != newHeight) {
232
shouldRepaint = false;
233
if (!selfNeedsLayout())
234
setNeedsLayout(true);
239
LayoutRect repaintRect;
241
// The image changed rect is in source image coordinates (pre-zooming),
242
// so map from the bounds of the image to the contentsBox.
243
repaintRect = enclosingIntRect(mapRect(*rect, FloatRect(FloatPoint(), m_imageResource->imageSize(1.0f)), contentBoxRect()));
244
// Guard against too-large changed rects.
245
repaintRect.intersect(contentBoxRect());
247
repaintRect = contentBoxRect();
249
repaintRectangle(repaintRect);
251
#if USE(ACCELERATED_COMPOSITING)
252
// Tell any potential compositing layers that the image needs updating.
253
contentChanged(ImageChanged);
258
void RenderImage::notifyFinished(CachedResource* newImage)
260
if (!m_imageResource)
263
if (documentBeingDestroyed())
266
#if USE(ACCELERATED_COMPOSITING)
267
if (newImage == m_imageResource->cachedImage()) {
268
// tell any potential compositing layers
269
// that the image is done and they can reference it directly.
270
contentChanged(ImageChanged);
273
UNUSED_PARAM(newImage);
277
void RenderImage::paintReplaced(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
279
LayoutUnit cWidth = contentWidth();
280
LayoutUnit cHeight = contentHeight();
281
LayoutUnit leftBorder = borderLeft();
282
LayoutUnit topBorder = borderTop();
283
LayoutUnit leftPad = paddingLeft();
284
LayoutUnit topPad = paddingTop();
286
GraphicsContext* context = paintInfo.context;
289
if (Frame* frame = this->frame())
290
page = frame->page();
292
if (!m_imageResource->hasImage() || m_imageResource->errorOccurred()) {
293
if (paintInfo.phase == PaintPhaseSelection)
296
if (page && paintInfo.phase == PaintPhaseForeground)
297
page->addRelevantUnpaintedObject(this, visualOverflowRect());
299
if (cWidth > 2 && cHeight > 2) {
300
// Draw an outline rect where the image should be.
301
context->setStrokeStyle(SolidStroke);
302
context->setStrokeColor(Color::lightGray, style()->colorSpace());
303
context->setFillColor(Color::transparent, style()->colorSpace());
304
context->drawRect(pixelSnappedIntRect(LayoutRect(paintOffset.x() + leftBorder + leftPad, paintOffset.y() + topBorder + topPad, cWidth, cHeight)));
306
bool errorPictureDrawn = false;
307
LayoutSize imageOffset;
308
// When calculating the usable dimensions, exclude the pixels of
309
// the ouline rect so the error image/alt text doesn't draw on it.
310
LayoutUnit usableWidth = cWidth - 2;
311
LayoutUnit usableHeight = cHeight - 2;
313
RefPtr<Image> image = m_imageResource->image();
315
if (m_imageResource->errorOccurred() && !image->isNull() && usableWidth >= image->width() && usableHeight >= image->height()) {
316
float deviceScaleFactor = WebCore::deviceScaleFactor(frame());
317
// Call brokenImage() explicitly to ensure we get the broken image icon at the appropriate resolution.
318
pair<Image*, float> brokenImageAndImageScaleFactor = m_imageResource->cachedImage()->brokenImage(deviceScaleFactor);
319
image = brokenImageAndImageScaleFactor.first;
320
IntSize imageSize = image->size();
321
imageSize.scale(1 / brokenImageAndImageScaleFactor.second);
322
// Center the error image, accounting for border and padding.
323
LayoutUnit centerX = (usableWidth - imageSize.width()) / 2;
326
LayoutUnit centerY = (usableHeight - imageSize.height()) / 2;
329
imageOffset = LayoutSize(leftBorder + leftPad + centerX + 1, topBorder + topPad + centerY + 1);
330
context->drawImage(image.get(), style()->colorSpace(), pixelSnappedIntRect(LayoutRect(paintOffset + imageOffset, imageSize)), CompositeSourceOver, shouldRespectImageOrientation());
331
errorPictureDrawn = true;
334
if (!m_altText.isEmpty()) {
335
String text = document()->displayStringModifiedByEncoding(m_altText);
336
context->setFillColor(style()->visitedDependentColor(CSSPropertyColor), style()->colorSpace());
337
const Font& font = style()->font();
338
const FontMetrics& fontMetrics = font.fontMetrics();
339
LayoutUnit ascent = fontMetrics.ascent();
340
LayoutPoint altTextOffset = paintOffset;
341
altTextOffset.move(leftBorder + leftPad, topBorder + topPad + ascent);
343
// Only draw the alt text if it'll fit within the content box,
344
// and only if it fits above the error image.
345
TextRun textRun = RenderBlock::constructTextRun(this, font, text, style());
346
LayoutUnit textWidth = font.width(textRun);
347
if (errorPictureDrawn) {
348
if (usableWidth >= textWidth && fontMetrics.height() <= imageOffset.height())
349
context->drawText(font, textRun, altTextOffset);
350
} else if (usableWidth >= textWidth && cHeight >= fontMetrics.height())
351
context->drawText(font, textRun, altTextOffset);
354
} else if (m_imageResource->hasImage() && cWidth > 0 && cHeight > 0) {
355
RefPtr<Image> img = m_imageResource->image(cWidth, cHeight);
356
if (!img || img->isNull()) {
357
if (page && paintInfo.phase == PaintPhaseForeground)
358
page->addRelevantUnpaintedObject(this, visualOverflowRect());
363
if (style()->highlight() != nullAtom && !paintInfo.context->paintingDisabled())
364
paintCustomHighlight(toPoint(paintOffset - location()), style()->highlight(), true);
367
LayoutSize contentSize(cWidth, cHeight);
368
LayoutPoint contentLocation = paintOffset;
369
contentLocation.move(leftBorder + leftPad, topBorder + topPad);
370
paintIntoRect(context, LayoutRect(contentLocation, contentSize));
372
if (cachedImage() && page && paintInfo.phase == PaintPhaseForeground) {
373
// For now, count images as unpainted if they are still progressively loading. We may want
374
// to refine this in the future to account for the portion of the image that has painted.
375
if (cachedImage()->isLoading())
376
page->addRelevantUnpaintedObject(this, LayoutRect(contentLocation, contentSize));
378
page->addRelevantRepaintedObject(this, LayoutRect(contentLocation, contentSize));
383
void RenderImage::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
385
RenderReplaced::paint(paintInfo, paintOffset);
387
if (paintInfo.phase == PaintPhaseOutline)
388
paintAreaElementFocusRing(paintInfo);
391
void RenderImage::paintAreaElementFocusRing(PaintInfo& paintInfo)
393
Document* document = this->document();
395
if (document->printing() || !document->frame()->selection()->isFocusedAndActive())
398
if (paintInfo.context->paintingDisabled() && !paintInfo.context->updatingControlTints())
401
Node* focusedNode = document->focusedNode();
402
if (!focusedNode || !focusedNode->hasTagName(areaTag))
405
HTMLAreaElement* areaElement = static_cast<HTMLAreaElement*>(focusedNode);
406
if (areaElement->imageElement() != node())
409
// Even if the theme handles focus ring drawing for entire elements, it won't do it for
410
// an area within an image, so we don't call RenderTheme::supportsFocusRing here.
412
Path path = areaElement->computePath(this);
416
// FIXME: Do we need additional code to clip the path to the image's bounding box?
418
RenderStyle* areaElementStyle = areaElement->computedStyle();
419
unsigned short outlineWidth = areaElementStyle->outlineWidth();
423
paintInfo.context->drawFocusRing(path, outlineWidth,
424
areaElementStyle->outlineOffset(),
425
areaElementStyle->visitedDependentColor(CSSPropertyOutlineColor));
428
void RenderImage::areaElementFocusChanged(HTMLAreaElement* element)
430
ASSERT_UNUSED(element, element->imageElement() == node());
432
// It would be more efficient to only repaint the focus ring rectangle
433
// for the passed-in area element. That would require adding functions
434
// to the area element class.
438
void RenderImage::paintIntoRect(GraphicsContext* context, const LayoutRect& rect)
440
IntRect alignedRect = pixelSnappedIntRect(rect);
441
if (!m_imageResource->hasImage() || m_imageResource->errorOccurred() || alignedRect.width() <= 0 || alignedRect.height() <= 0)
444
RefPtr<Image> img = m_imageResource->image(alignedRect.width(), alignedRect.height());
445
if (!img || img->isNull())
448
HTMLImageElement* imageElt = hostImageElement();
449
CompositeOperator compositeOperator = imageElt ? imageElt->compositeOperator() : CompositeSourceOver;
450
Image* image = m_imageResource->image().get();
451
bool useLowQualityScaling = shouldPaintAtLowQuality(context, image, image, alignedRect.size());
452
context->drawImage(m_imageResource->image(alignedRect.width(), alignedRect.height()).get(), style()->colorSpace(), alignedRect, compositeOperator, shouldRespectImageOrientation(), useLowQualityScaling);
455
bool RenderImage::boxShadowShouldBeAppliedToBackground(BackgroundBleedAvoidance bleedAvoidance, InlineFlowBox*) const
457
if (!RenderBoxModelObject::boxShadowShouldBeAppliedToBackground(bleedAvoidance))
460
return !backgroundIsObscured();
463
bool RenderImage::backgroundIsObscured() const
465
if (!m_imageResource->hasImage() || m_imageResource->errorOccurred())
468
if (m_imageResource->cachedImage() && !m_imageResource->cachedImage()->isLoaded())
471
EFillBox backgroundClip = style()->backgroundClip();
473
// Background paints under borders.
474
if (backgroundClip == BorderFillBox && style()->hasBorder() && !borderObscuresBackground())
477
// Background shows in padding area.
478
if ((backgroundClip == BorderFillBox || backgroundClip == PaddingFillBox) && style()->hasPadding())
481
// Check for bitmap image with alpha.
482
Image* image = m_imageResource->image().get();
483
if (!image || !image->isBitmapImage() || image->currentFrameHasAlpha())
489
LayoutUnit RenderImage::minimumReplacedHeight() const
491
return m_imageResource->errorOccurred() ? intrinsicSize().height() : LayoutUnit();
494
HTMLMapElement* RenderImage::imageMap() const
496
HTMLImageElement* i = hostImageElement();
497
return i ? i->treeScope()->getImageMap(i->fastGetAttribute(usemapAttr)) : 0;
500
bool RenderImage::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, HitTestAction hitTestAction)
502
HitTestResult tempResult(result.hitTestLocation());
503
bool inside = RenderReplaced::nodeAtPoint(request, tempResult, locationInContainer, accumulatedOffset, hitTestAction);
505
if (tempResult.innerNode() && node()) {
506
if (HTMLMapElement* map = imageMap()) {
507
LayoutRect contentBox = contentBoxRect();
508
float scaleFactor = 1 / style()->effectiveZoom();
509
LayoutPoint mapLocation = locationInContainer.point() - toLayoutSize(accumulatedOffset) - locationOffset() - toLayoutSize(contentBox.location());
510
mapLocation.scale(scaleFactor, scaleFactor);
512
if (map->mapMouseEvent(mapLocation, contentBox.size(), tempResult))
513
tempResult.setInnerNonSharedNode(node());
517
if (!inside && result.isRectBasedTest())
518
result.append(tempResult);
524
void RenderImage::updateAltText()
529
if (node()->hasTagName(inputTag))
530
m_altText = static_cast<HTMLInputElement*>(node())->altText();
531
else if (HTMLImageElement* image = hostImageElement())
532
m_altText = image->altText();
535
void RenderImage::layout()
537
StackStats::LayoutCheckPoint layoutCheckPoint;
538
RenderReplaced::layout();
540
// Propagate container size to image resource.
541
IntSize containerSize(contentWidth(), contentHeight());
542
if (!containerSize.isEmpty())
543
m_imageResource->setContainerSizeForRenderer(containerSize);
546
void RenderImage::computeIntrinsicRatioInformation(FloatSize& intrinsicSize, double& intrinsicRatio, bool& isPercentageIntrinsicSize) const
548
RenderReplaced::computeIntrinsicRatioInformation(intrinsicSize, intrinsicRatio, isPercentageIntrinsicSize);
550
// Our intrinsicSize is empty if we're rendering generated images with relative width/height. Figure out the right intrinsic size to use.
551
if (intrinsicSize.isEmpty() && (m_imageResource->imageHasRelativeWidth() || m_imageResource->imageHasRelativeHeight())) {
552
RenderObject* containingBlock = isOutOfFlowPositioned() ? container() : this->containingBlock();
553
if (containingBlock->isBox()) {
554
RenderBox* box = toRenderBox(containingBlock);
555
intrinsicSize.setWidth(box->availableLogicalWidth());
556
intrinsicSize.setHeight(box->availableLogicalHeight());
559
// Don't compute an intrinsic ratio to preserve historical WebKit behavior if we're painting alt text and/or a broken image.
560
if (m_imageResource && m_imageResource->errorOccurred()) {
566
HTMLImageElement* RenderImage::hostImageElement() const
571
if (isHTMLImageElement(node()))
572
return toHTMLImageElement(node());
574
if (node()->hasTagName(webkitInnerImageTag)) {
575
if (Node* ancestor = node()->shadowAncestorNode()) {
576
if (ancestor->hasTagName(imgTag))
577
return toHTMLImageElement(ancestor);
584
bool RenderImage::needsPreferredWidthsRecalculation() const
586
if (RenderReplaced::needsPreferredWidthsRecalculation())
588
return embeddedContentBox();
591
RenderBox* RenderImage::embeddedContentBox() const
593
if (!m_imageResource)
597
CachedImage* cachedImage = m_imageResource->cachedImage();
598
if (cachedImage && cachedImage->image() && cachedImage->image()->isSVGImage())
599
return static_cast<SVGImage*>(cachedImage->image())->embeddedContentBox();
605
} // namespace WebCore