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.
10 * This library is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Library General Public
12 * License as published by the Free Software Foundation; either
13 * version 2 of the License, or (at your option) any later version.
15 * This library is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * Library General Public License for more details.
20 * You should have received a copy of the GNU Library General Public License
21 * along with this library; see the file COPYING.LIB. If not, write to
22 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
23 * Boston, MA 02110-1301, USA.
28 #include "RenderImage.h"
30 #include "BitmapImage.h"
31 #include "FontCache.h"
33 #include "FrameSelection.h"
34 #include "GraphicsContext.h"
35 #include "HTMLAreaElement.h"
36 #include "HTMLImageElement.h"
37 #include "HTMLInputElement.h"
38 #include "HTMLMapElement.h"
39 #include "HTMLNames.h"
40 #include "HitTestResult.h"
42 #include "RenderLayer.h"
43 #include "RenderView.h"
45 #include <wtf/UnusedParam.h>
51 using namespace HTMLNames;
53 RenderImage::RenderImage(Node* node)
54 : RenderReplaced(node, IntSize())
55 , m_needsToSetSizeForAltText(false)
56 , m_didIncrementVisuallyNonEmptyPixelCount(false)
61 RenderImage::~RenderImage()
63 ASSERT(m_imageResource);
64 m_imageResource->shutdown();
67 void RenderImage::setImageResource(PassOwnPtr<RenderImageResource> imageResource)
69 ASSERT(!m_imageResource);
70 m_imageResource = imageResource;
71 m_imageResource->initialize(this);
74 // If we'll be displaying either alt text or an image, add some padding.
75 static const unsigned short paddingWidth = 4;
76 static const unsigned short paddingHeight = 4;
78 // Alt text is restricted to this maximum size, in pixels. These are
79 // signed integers because they are compared with other signed values.
80 static const float maxAltTextWidth = 1024;
81 static const int maxAltTextHeight = 256;
83 IntSize RenderImage::imageSizeForError(CachedImage* newImage) const
85 ASSERT_ARG(newImage, newImage);
86 ASSERT_ARG(newImage, newImage->imageForRenderer(this));
89 if (newImage->willPaintBrokenImage()) {
90 float deviceScaleFactor = WebCore::deviceScaleFactor(frame());
91 pair<Image*, float> brokenImageAndImageScaleFactor = newImage->brokenImage(deviceScaleFactor);
92 imageSize = brokenImageAndImageScaleFactor.first->size();
93 imageSize.scale(1 / brokenImageAndImageScaleFactor.second);
95 imageSize = newImage->imageForRenderer(this)->size();
97 // imageSize() returns 0 for the error image. We need the true size of the
98 // error image, so we have to get it by grabbing image() directly.
99 return IntSize(paddingWidth + imageSize.width() * style()->effectiveZoom(), paddingHeight + imageSize.height() * style()->effectiveZoom());
102 // Sets the image height and width to fit the alt text. Returns true if the
103 // image size changed.
104 bool RenderImage::setImageSizeForAltText(CachedImage* newImage /* = 0 */)
107 if (newImage && newImage->imageForRenderer(this))
108 imageSize = imageSizeForError(newImage);
109 else if (!m_altText.isEmpty() || newImage) {
110 // If we'll be displaying either text or an image, add a little padding.
111 imageSize = IntSize(paddingWidth, paddingHeight);
114 // we have an alt and the user meant it (its not a text we invented)
115 if (!m_altText.isEmpty()) {
116 FontCachePurgePreventer fontCachePurgePreventer;
118 const Font& font = style()->font();
119 IntSize textSize(min(font.width(RenderBlock::constructTextRun(this, font, m_altText, style())), maxAltTextWidth), min(font.fontMetrics().height(), maxAltTextHeight));
120 imageSize = imageSize.expandedTo(textSize);
123 if (imageSize == intrinsicSize())
126 setIntrinsicSize(imageSize);
130 void RenderImage::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
132 RenderReplaced::styleDidChange(diff, oldStyle);
133 if (m_needsToSetSizeForAltText) {
134 if (!m_altText.isEmpty() && setImageSizeForAltText(m_imageResource->cachedImage()))
135 imageDimensionsChanged(true /* imageSizeChanged */);
136 m_needsToSetSizeForAltText = false;
140 void RenderImage::imageChanged(WrappedImagePtr newImage, const IntRect* rect)
142 if (documentBeingDestroyed())
145 if (hasBoxDecorations() || hasMask())
146 RenderReplaced::imageChanged(newImage, rect);
148 if (!m_imageResource)
151 if (newImage != m_imageResource->imagePtr() || !newImage)
154 if (!m_didIncrementVisuallyNonEmptyPixelCount) {
155 view()->frameView()->incrementVisuallyNonEmptyPixelCount(m_imageResource->imageSize(1.0f));
156 m_didIncrementVisuallyNonEmptyPixelCount = true;
159 bool imageSizeChanged = false;
161 // Set image dimensions, taking into account the size of the alt text.
162 if (m_imageResource->errorOccurred()) {
163 if (!m_altText.isEmpty() && document()->isPendingStyleRecalc()) {
166 m_needsToSetSizeForAltText = true;
167 node()->setNeedsStyleRecalc(SyntheticStyleChange);
171 imageSizeChanged = setImageSizeForAltText(m_imageResource->cachedImage());
174 imageDimensionsChanged(imageSizeChanged, rect);
177 bool RenderImage::updateIntrinsicSizeIfNeeded(const LayoutSize& newSize, bool imageSizeChanged)
179 if (newSize == intrinsicSize() && !imageSizeChanged)
181 if (m_imageResource->errorOccurred())
182 return imageSizeChanged;
183 setIntrinsicSize(newSize);
187 void RenderImage::imageDimensionsChanged(bool imageSizeChanged, const IntRect* rect)
189 bool shouldRepaint = true;
190 if (updateIntrinsicSizeIfNeeded(m_imageResource->imageSize(style()->effectiveZoom()), imageSizeChanged)) {
191 // In the case of generated image content using :before/:after, we might not be in the
192 // render tree yet. In that case, we don't need to worry about check for layout, since we'll get a
193 // layout when we get added in to the render tree hierarchy later.
194 if (containingBlock()) {
195 // lets see if we need to relayout at all..
196 int oldwidth = width();
197 int oldheight = height();
198 if (!preferredLogicalWidthsDirty())
199 setPreferredLogicalWidthsDirty(true);
200 computeLogicalWidth();
201 computeLogicalHeight();
203 if (imageSizeChanged || width() != oldwidth || height() != oldheight) {
204 shouldRepaint = false;
205 if (!selfNeedsLayout())
206 setNeedsLayout(true);
210 setHeight(oldheight);
217 // The image changed rect is in source image coordinates (pre-zooming),
218 // so map from the bounds of the image to the contentsBox.
219 repaintRect = enclosingIntRect(mapRect(*rect, FloatRect(FloatPoint(), m_imageResource->imageSize(1.0f)), contentBoxRect()));
220 // Guard against too-large changed rects.
221 repaintRect.intersect(contentBoxRect());
223 repaintRect = contentBoxRect();
225 repaintRectangle(repaintRect);
227 #if USE(ACCELERATED_COMPOSITING)
229 // Tell any potential compositing layers that the image needs updating.
230 layer()->contentChanged(RenderLayer::ImageChanged);
236 void RenderImage::notifyFinished(CachedResource* newImage)
238 if (!m_imageResource)
241 if (documentBeingDestroyed())
244 #if USE(ACCELERATED_COMPOSITING)
245 if (newImage == m_imageResource->cachedImage() && hasLayer()) {
246 // tell any potential compositing layers
247 // that the image is done and they can reference it directly.
248 layer()->contentChanged(RenderLayer::ImageChanged);
251 UNUSED_PARAM(newImage);
255 void RenderImage::paintReplaced(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
257 LayoutUnit cWidth = contentWidth();
258 LayoutUnit cHeight = contentHeight();
259 LayoutUnit leftBorder = borderLeft();
260 LayoutUnit topBorder = borderTop();
261 LayoutUnit leftPad = paddingLeft();
262 LayoutUnit topPad = paddingTop();
264 GraphicsContext* context = paintInfo.context;
266 if (!m_imageResource->hasImage() || m_imageResource->errorOccurred()) {
267 if (paintInfo.phase == PaintPhaseSelection)
270 if (cWidth > 2 && cHeight > 2) {
271 // Draw an outline rect where the image should be.
272 context->setStrokeStyle(SolidStroke);
273 context->setStrokeColor(Color::lightGray, style()->colorSpace());
274 context->setFillColor(Color::transparent, style()->colorSpace());
275 context->drawRect(LayoutRect(paintOffset.x() + leftBorder + leftPad, paintOffset.y() + topBorder + topPad, cWidth, cHeight));
277 bool errorPictureDrawn = false;
278 LayoutSize imageOffset;
279 // When calculating the usable dimensions, exclude the pixels of
280 // the ouline rect so the error image/alt text doesn't draw on it.
281 LayoutUnit usableWidth = cWidth - 2;
282 LayoutUnit usableHeight = cHeight - 2;
284 RefPtr<Image> image = m_imageResource->image();
286 if (m_imageResource->errorOccurred() && !image->isNull() && usableWidth >= image->width() && usableHeight >= image->height()) {
287 float deviceScaleFactor = WebCore::deviceScaleFactor(frame());
288 // Call brokenImage() explicitly to ensure we get the broken image icon at the appropriate resolution.
289 pair<Image*, float> brokenImageAndImageScaleFactor = m_imageResource->cachedImage()->brokenImage(deviceScaleFactor);
290 image = brokenImageAndImageScaleFactor.first;
291 IntSize imageSize = image->size();
292 imageSize.scale(1 / brokenImageAndImageScaleFactor.second);
293 // Center the error image, accounting for border and padding.
294 LayoutUnit centerX = (usableWidth - imageSize.width()) / 2;
297 LayoutUnit centerY = (usableHeight - imageSize.height()) / 2;
300 imageOffset = LayoutSize(leftBorder + leftPad + centerX + 1, topBorder + topPad + centerY + 1);
301 context->drawImage(image.get(), style()->colorSpace(), IntRect(paintOffset + imageOffset, imageSize));
302 errorPictureDrawn = true;
305 if (!m_altText.isEmpty()) {
306 String text = document()->displayStringModifiedByEncoding(m_altText);
307 context->setFillColor(style()->visitedDependentColor(CSSPropertyColor), style()->colorSpace());
308 const Font& font = style()->font();
309 const FontMetrics& fontMetrics = font.fontMetrics();
310 LayoutUnit ascent = fontMetrics.ascent();
311 LayoutPoint altTextOffset = paintOffset;
312 altTextOffset.move(leftBorder + leftPad, topBorder + topPad + ascent);
314 // Only draw the alt text if it'll fit within the content box,
315 // and only if it fits above the error image.
316 TextRun textRun = RenderBlock::constructTextRun(this, font, text, style());
317 LayoutUnit textWidth = font.width(textRun);
318 if (errorPictureDrawn) {
319 if (usableWidth >= textWidth && fontMetrics.height() <= imageOffset.height())
320 context->drawText(font, textRun, altTextOffset);
321 } else if (usableWidth >= textWidth && cHeight >= fontMetrics.height())
322 context->drawText(font, textRun, altTextOffset);
325 } else if (m_imageResource->hasImage() && cWidth > 0 && cHeight > 0) {
326 RefPtr<Image> img = m_imageResource->image(cWidth, cHeight);
327 if (!img || img->isNull())
331 if (style()->highlight() != nullAtom && !paintInfo.context->paintingDisabled())
332 paintCustomHighlight(toPoint(paintOffset - location()), style()->highlight(), true);
335 LayoutSize contentSize(cWidth, cHeight);
336 LayoutPoint contentLocation = paintOffset;
337 contentLocation.move(leftBorder + leftPad, topBorder + topPad);
338 paintIntoRect(context, LayoutRect(contentLocation, contentSize));
342 void RenderImage::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
344 RenderReplaced::paint(paintInfo, paintOffset);
346 if (paintInfo.phase == PaintPhaseOutline)
347 paintAreaElementFocusRing(paintInfo);
350 void RenderImage::paintAreaElementFocusRing(PaintInfo& paintInfo)
352 Document* document = this->document();
354 if (document->printing() || !document->frame()->selection()->isFocusedAndActive())
357 if (paintInfo.context->paintingDisabled() && !paintInfo.context->updatingControlTints())
360 Node* focusedNode = document->focusedNode();
361 if (!focusedNode || !focusedNode->hasTagName(areaTag))
364 HTMLAreaElement* areaElement = static_cast<HTMLAreaElement*>(focusedNode);
365 if (areaElement->imageElement() != node())
368 // Even if the theme handles focus ring drawing for entire elements, it won't do it for
369 // an area within an image, so we don't call RenderTheme::supportsFocusRing here.
371 Path path = areaElement->computePath(this);
375 // FIXME: Do we need additional code to clip the path to the image's bounding box?
377 RenderStyle* areaElementStyle = areaElement->computedStyle();
378 unsigned short outlineWidth = areaElementStyle->outlineWidth();
382 paintInfo.context->drawFocusRing(path, outlineWidth,
383 areaElementStyle->outlineOffset(),
384 areaElementStyle->visitedDependentColor(CSSPropertyOutlineColor));
387 void RenderImage::areaElementFocusChanged(HTMLAreaElement* element)
389 ASSERT_UNUSED(element, element->imageElement() == node());
391 // It would be more efficient to only repaint the focus ring rectangle
392 // for the passed-in area element. That would require adding functions
393 // to the area element class.
397 void RenderImage::paintIntoRect(GraphicsContext* context, const IntRect& rect)
399 if (!m_imageResource->hasImage() || m_imageResource->errorOccurred() || rect.width() <= 0 || rect.height() <= 0)
402 RefPtr<Image> img = m_imageResource->image(rect.width(), rect.height());
403 if (!img || img->isNull())
406 HTMLImageElement* imageElt = (node() && node()->hasTagName(imgTag)) ? static_cast<HTMLImageElement*>(node()) : 0;
407 CompositeOperator compositeOperator = imageElt ? imageElt->compositeOperator() : CompositeSourceOver;
408 Image* image = m_imageResource->image().get();
409 bool useLowQualityScaling = shouldPaintAtLowQuality(context, image, image, rect.size());
410 context->drawImage(m_imageResource->image(rect.width(), rect.height()).get(), style()->colorSpace(), rect, compositeOperator, useLowQualityScaling);
413 bool RenderImage::backgroundIsObscured() const
415 if (!m_imageResource->hasImage() || m_imageResource->errorOccurred())
418 if (m_imageResource->cachedImage() && !m_imageResource->cachedImage()->isLoaded())
421 EFillBox backgroundClip = style()->backgroundClip();
423 // Background paints under borders.
424 if (backgroundClip == BorderFillBox && style()->hasBorder() && !borderObscuresBackground())
427 // Background shows in padding area.
428 if ((backgroundClip == BorderFillBox || backgroundClip == PaddingFillBox) && style()->hasPadding())
431 // Check for bitmap image with alpha.
432 Image* image = m_imageResource->image().get();
433 if (!image || !image->isBitmapImage() || image->currentFrameHasAlpha())
439 int RenderImage::minimumReplacedHeight() const
441 return m_imageResource->errorOccurred() ? intrinsicSize().height() : 0;
444 HTMLMapElement* RenderImage::imageMap() const
446 HTMLImageElement* i = node() && node()->hasTagName(imgTag) ? static_cast<HTMLImageElement*>(node()) : 0;
447 return i ? i->treeScope()->getImageMap(i->fastGetAttribute(usemapAttr)) : 0;
450 bool RenderImage::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, const LayoutPoint& pointInContainer, const LayoutPoint& accumulatedOffset, HitTestAction hitTestAction)
452 HitTestResult tempResult(result.point(), result.topPadding(), result.rightPadding(), result.bottomPadding(), result.leftPadding());
453 bool inside = RenderReplaced::nodeAtPoint(request, tempResult, pointInContainer, accumulatedOffset, hitTestAction);
455 if (tempResult.innerNode() && node()) {
456 if (HTMLMapElement* map = imageMap()) {
457 IntRect contentBox = contentBoxRect();
458 float scaleFactor = 1 / style()->effectiveZoom();
459 LayoutPoint mapLocation(pointInContainer.x() - accumulatedOffset.x() - this->x() - contentBox.x(), pointInContainer.y() - accumulatedOffset.y() - this->y() - contentBox.y());
460 mapLocation.scale(scaleFactor, scaleFactor);
462 if (map->mapMouseEvent(mapLocation, contentBox.size(), tempResult))
463 tempResult.setInnerNonSharedNode(node());
467 if (!inside && result.isRectBasedTest())
468 result.append(tempResult);
474 void RenderImage::updateAltText()
479 if (node()->hasTagName(inputTag))
480 m_altText = static_cast<HTMLInputElement*>(node())->altText();
481 else if (node()->hasTagName(imgTag))
482 m_altText = static_cast<HTMLImageElement*>(node())->altText();
485 LayoutUnit RenderImage::computeReplacedLogicalWidth(bool includeMaxWidth) const
487 // If we've got an explicit width/height assigned, propagate it to the image resource.
488 if (style()->logicalWidth().isFixed() && style()->logicalHeight().isFixed()) {
489 LayoutUnit width = RenderReplaced::computeReplacedLogicalWidth(includeMaxWidth);
490 m_imageResource->setContainerSizeForRenderer(IntSize(width, computeReplacedLogicalHeight()));
494 RenderBox* contentRenderer = embeddedContentBox();
495 bool hasRelativeWidth = contentRenderer ? contentRenderer->style()->width().isPercent() : m_imageResource->imageHasRelativeWidth();
496 bool hasRelativeHeight = contentRenderer ? contentRenderer->style()->height().isPercent() : m_imageResource->imageHasRelativeHeight();
498 LayoutSize containerSize;
499 if (hasRelativeWidth || hasRelativeHeight) {
500 // Propagate the containing block size to the image resource, otherwhise we can't compute our own intrinsic size, if it's relative.
501 RenderObject* containingBlock = isPositioned() ? container() : this->containingBlock();
502 if (containingBlock->isBox()) {
503 RenderBox* box = toRenderBox(containingBlock);
504 containerSize = LayoutSize(box->availableWidth(), box->availableHeight()); // Already contains zooming information.
507 // Propagate the current zoomed image size to the image resource, otherwhise the image size will remain the same on-screen.
508 CachedImage* cachedImage = m_imageResource->cachedImage();
509 if (cachedImage && cachedImage->image()) {
510 containerSize = cachedImage->image()->size();
511 // FIXME: Remove unnecessary rounding when layout is off ints: webkit.org/b/63656
512 containerSize.setWidth(static_cast<LayoutUnit>(containerSize.width() * style()->effectiveZoom()));
513 containerSize.setHeight(static_cast<LayoutUnit>(containerSize.height() * style()->effectiveZoom()));
517 if (!containerSize.isEmpty()) {
518 m_imageResource->setContainerSizeForRenderer(containerSize);
519 const_cast<RenderImage*>(this)->updateIntrinsicSizeIfNeeded(containerSize, false);
522 return RenderReplaced::computeReplacedLogicalWidth(includeMaxWidth);
525 void RenderImage::computeIntrinsicRatioInformation(FloatSize& intrinsicRatio, bool& isPercentageIntrinsicSize) const
527 // Assure this method is never used for SVGImages.
528 ASSERT(!embeddedContentBox());
529 isPercentageIntrinsicSize = false;
530 CachedImage* cachedImage = m_imageResource ? m_imageResource->cachedImage() : 0;
531 if (cachedImage && cachedImage->image())
532 intrinsicRatio = cachedImage->image()->size();
535 bool RenderImage::needsPreferredWidthsRecalculation() const
537 if (RenderReplaced::needsPreferredWidthsRecalculation())
539 return embeddedContentBox();
542 RenderBox* RenderImage::embeddedContentBox() const
544 if (!m_imageResource)
548 RefPtr<Image> image = m_imageResource->image();
549 if (image && image->isSVGImage())
550 return static_pointer_cast<SVGImage>(image)->embeddedContentBox();
556 } // namespace WebCore