9b3757814a55c40778057aac309d1c6c866265b3
[WebKit.git] / Source / WebCore / rendering / RenderImage.cpp
1 /*
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.
10  *
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.
15  *
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.
20  *
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.
25  *
26  */
27
28 #include "config.h"
29 #include "RenderImage.h"
30
31 #include "BitmapImage.h"
32 #include "FontCache.h"
33 #include "Frame.h"
34 #include "FrameSelection.h"
35 #include "GraphicsContext.h"
36 #include "HTMLAreaElement.h"
37 #include "HTMLImageElement.h"
38 #include "HTMLInputElement.h"
39 #include "HTMLMapElement.h"
40 #include "HTMLNames.h"
41 #include "HitTestResult.h"
42 #include "Page.h"
43 #include "RenderLayer.h"
44 #include "RenderView.h"
45 #include "SVGImage.h"
46 #include <wtf/UnusedParam.h>
47
48 using namespace std;
49
50 namespace WebCore {
51
52 using namespace HTMLNames;
53
54 RenderImage::RenderImage(Node* node)
55     : RenderReplaced(node, IntSize())
56     , m_needsToSetSizeForAltText(false)
57     , m_didIncrementVisuallyNonEmptyPixelCount(false)
58     , m_isGeneratedContent(false)
59 {
60     updateAltText();
61 }
62
63 RenderImage::~RenderImage()
64 {
65     ASSERT(m_imageResource);
66     m_imageResource->shutdown();
67 }
68
69 void RenderImage::setImageResource(PassOwnPtr<RenderImageResource> imageResource)
70 {
71     ASSERT(!m_imageResource);
72     m_imageResource = imageResource;
73     m_imageResource->initialize(this);
74 }
75
76 // If we'll be displaying either alt text or an image, add some padding.
77 static const unsigned short paddingWidth = 4;
78 static const unsigned short paddingHeight = 4;
79
80 // Alt text is restricted to this maximum size, in pixels.  These are
81 // signed integers because they are compared with other signed values.
82 static const float maxAltTextWidth = 1024;
83 static const int maxAltTextHeight = 256;
84
85 IntSize RenderImage::imageSizeForError(CachedImage* newImage) const
86 {
87     ASSERT_ARG(newImage, newImage);
88     ASSERT_ARG(newImage, newImage->imageForRenderer(this));
89
90     IntSize imageSize;
91     if (newImage->willPaintBrokenImage()) {
92         float deviceScaleFactor = WebCore::deviceScaleFactor(frame());
93         pair<Image*, float> brokenImageAndImageScaleFactor = newImage->brokenImage(deviceScaleFactor);
94         imageSize = brokenImageAndImageScaleFactor.first->size();
95         imageSize.scale(1 / brokenImageAndImageScaleFactor.second);
96     } else
97         imageSize = newImage->imageForRenderer(this)->size();
98
99     // imageSize() returns 0 for the error image. We need the true size of the
100     // error image, so we have to get it by grabbing image() directly.
101     return IntSize(paddingWidth + imageSize.width() * style()->effectiveZoom(), paddingHeight + imageSize.height() * style()->effectiveZoom());
102 }
103
104 // Sets the image height and width to fit the alt text.  Returns true if the
105 // image size changed.
106 bool RenderImage::setImageSizeForAltText(CachedImage* newImage /* = 0 */)
107 {
108     IntSize imageSize;
109     if (newImage && newImage->imageForRenderer(this))
110         imageSize = imageSizeForError(newImage);
111     else if (!m_altText.isEmpty() || newImage) {
112         // If we'll be displaying either text or an image, add a little padding.
113         imageSize = IntSize(paddingWidth, paddingHeight);
114     }
115
116     // we have an alt and the user meant it (its not a text we invented)
117     if (!m_altText.isEmpty()) {
118         FontCachePurgePreventer fontCachePurgePreventer;
119
120         const Font& font = style()->font();
121         IntSize textSize(min(font.width(RenderBlock::constructTextRun(this, font, m_altText, style())), maxAltTextWidth), min(font.fontMetrics().height(), maxAltTextHeight));
122         imageSize = imageSize.expandedTo(textSize);
123     }
124
125     if (imageSize == intrinsicSize())
126         return false;
127
128     setIntrinsicSize(imageSize);
129     return true;
130 }
131
132 void RenderImage::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
133 {
134     RenderReplaced::styleDidChange(diff, oldStyle);
135     if (m_needsToSetSizeForAltText) {
136         if (!m_altText.isEmpty() && setImageSizeForAltText(m_imageResource->cachedImage()))
137             imageDimensionsChanged(true /* imageSizeChanged */);
138         m_needsToSetSizeForAltText = false;
139     }
140 }
141
142 void RenderImage::imageChanged(WrappedImagePtr newImage, const IntRect* rect)
143 {
144     if (documentBeingDestroyed())
145         return;
146
147     if (hasBoxDecorations() || hasMask())
148         RenderReplaced::imageChanged(newImage, rect);
149     
150     if (!m_imageResource)
151         return;
152
153     if (newImage != m_imageResource->imagePtr() || !newImage)
154         return;
155     
156     if (!m_didIncrementVisuallyNonEmptyPixelCount) {
157         view()->frameView()->incrementVisuallyNonEmptyPixelCount(m_imageResource->imageSize(1.0f));
158         m_didIncrementVisuallyNonEmptyPixelCount = true;
159     }
160
161     bool imageSizeChanged = false;
162
163     // Set image dimensions, taking into account the size of the alt text.
164     if (m_imageResource->errorOccurred()) {
165         if (!m_altText.isEmpty() && document()->isPendingStyleRecalc()) {
166             ASSERT(node());
167             if (node()) {
168                 m_needsToSetSizeForAltText = true;
169                 node()->setNeedsStyleRecalc(SyntheticStyleChange);
170             }
171             return;
172         }
173         imageSizeChanged = setImageSizeForAltText(m_imageResource->cachedImage());
174     }
175
176     imageDimensionsChanged(imageSizeChanged, rect);
177 }
178
179 bool RenderImage::updateIntrinsicSizeIfNeeded(const IntSize& newSize, bool imageSizeChanged)
180 {
181     if (newSize == intrinsicSize() && !imageSizeChanged)
182         return false;
183     if (m_imageResource->errorOccurred())
184         return imageSizeChanged;
185     setIntrinsicSize(newSize);
186     return true;
187 }
188
189 void RenderImage::imageDimensionsChanged(bool imageSizeChanged, const IntRect* rect)
190 {
191     bool shouldRepaint = true;
192     if (updateIntrinsicSizeIfNeeded(m_imageResource->imageSize(style()->effectiveZoom()), imageSizeChanged)) {
193         // In the case of generated image content using :before/:after, we might not be in the
194         // render tree yet.  In that case, we don't need to worry about check for layout, since we'll get a
195         // layout when we get added in to the render tree hierarchy later.
196         if (containingBlock()) {
197             // lets see if we need to relayout at all..
198             int oldwidth = width();
199             int oldheight = height();
200             if (!preferredLogicalWidthsDirty())
201                 setPreferredLogicalWidthsDirty(true);
202             computeLogicalWidth();
203             computeLogicalHeight();
204
205             if (imageSizeChanged || width() != oldwidth || height() != oldheight) {
206                 shouldRepaint = false;
207                 if (!selfNeedsLayout())
208                     setNeedsLayout(true);
209             }
210
211             setWidth(oldwidth);
212             setHeight(oldheight);
213         }
214     }
215
216     if (shouldRepaint) {
217         LayoutRect repaintRect;
218         if (rect) {
219             // The image changed rect is in source image coordinates (pre-zooming),
220             // so map from the bounds of the image to the contentsBox.
221             repaintRect = enclosingIntRect(mapRect(*rect, FloatRect(FloatPoint(), m_imageResource->imageSize(1.0f)), contentBoxRect()));
222             // Guard against too-large changed rects.
223             repaintRect.intersect(contentBoxRect());
224         } else
225             repaintRect = contentBoxRect();
226         
227         repaintRectangle(repaintRect);
228
229 #if USE(ACCELERATED_COMPOSITING)
230         if (hasLayer()) {
231             // Tell any potential compositing layers that the image needs updating.
232             layer()->contentChanged(RenderLayer::ImageChanged);
233         }
234 #endif
235     }
236 }
237
238 void RenderImage::notifyFinished(CachedResource* newImage)
239 {
240     if (!m_imageResource)
241         return;
242     
243     if (documentBeingDestroyed())
244         return;
245
246 #if USE(ACCELERATED_COMPOSITING)
247     if (newImage == m_imageResource->cachedImage() && hasLayer()) {
248         // tell any potential compositing layers
249         // that the image is done and they can reference it directly.
250         layer()->contentChanged(RenderLayer::ImageChanged);
251     }
252 #else
253     UNUSED_PARAM(newImage);
254 #endif
255 }
256
257 void RenderImage::paintReplaced(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
258 {
259     LayoutUnit cWidth = contentWidth();
260     LayoutUnit cHeight = contentHeight();
261     LayoutUnit leftBorder = borderLeft();
262     LayoutUnit topBorder = borderTop();
263     LayoutUnit leftPad = paddingLeft();
264     LayoutUnit topPad = paddingTop();
265
266     GraphicsContext* context = paintInfo.context;
267
268     Page* page = 0;
269     if (Frame* frame = this->frame())
270         page = frame->page();
271
272     if (!m_imageResource->hasImage() || m_imageResource->errorOccurred()) {
273         if (paintInfo.phase == PaintPhaseSelection)
274             return;
275
276         if (page && paintInfo.phase == PaintPhaseForeground)
277             page->addRelevantUnpaintedObject(this, visualOverflowRect());
278
279         if (cWidth > 2 && cHeight > 2) {
280             // Draw an outline rect where the image should be.
281             context->setStrokeStyle(SolidStroke);
282             context->setStrokeColor(Color::lightGray, style()->colorSpace());
283             context->setFillColor(Color::transparent, style()->colorSpace());
284             context->drawRect(pixelSnappedIntRect(LayoutRect(paintOffset.x() + leftBorder + leftPad, paintOffset.y() + topBorder + topPad, cWidth, cHeight)));
285
286             bool errorPictureDrawn = false;
287             LayoutSize imageOffset;
288             // When calculating the usable dimensions, exclude the pixels of
289             // the ouline rect so the error image/alt text doesn't draw on it.
290             LayoutUnit usableWidth = cWidth - 2;
291             LayoutUnit usableHeight = cHeight - 2;
292
293             RefPtr<Image> image = m_imageResource->image();
294
295             if (m_imageResource->errorOccurred() && !image->isNull() && usableWidth >= image->width() && usableHeight >= image->height()) {
296                 float deviceScaleFactor = WebCore::deviceScaleFactor(frame());
297                 // Call brokenImage() explicitly to ensure we get the broken image icon at the appropriate resolution.
298                 pair<Image*, float> brokenImageAndImageScaleFactor = m_imageResource->cachedImage()->brokenImage(deviceScaleFactor);
299                 image = brokenImageAndImageScaleFactor.first;
300                 IntSize imageSize = image->size();
301                 imageSize.scale(1 / brokenImageAndImageScaleFactor.second);
302                 // Center the error image, accounting for border and padding.
303                 LayoutUnit centerX = (usableWidth - imageSize.width()) / 2;
304                 if (centerX < 0)
305                     centerX = 0;
306                 LayoutUnit centerY = (usableHeight - imageSize.height()) / 2;
307                 if (centerY < 0)
308                     centerY = 0;
309                 imageOffset = LayoutSize(leftBorder + leftPad + centerX + 1, topBorder + topPad + centerY + 1);
310                 context->drawImage(image.get(), style()->colorSpace(), IntRect(roundedIntPoint(paintOffset + imageOffset), imageSize), CompositeSourceOver, shouldRespectImageOrientation());
311                 errorPictureDrawn = true;
312             }
313
314             if (!m_altText.isEmpty()) {
315                 String text = document()->displayStringModifiedByEncoding(m_altText);
316                 context->setFillColor(style()->visitedDependentColor(CSSPropertyColor), style()->colorSpace());
317                 const Font& font = style()->font();
318                 const FontMetrics& fontMetrics = font.fontMetrics();
319                 LayoutUnit ascent = fontMetrics.ascent();
320                 LayoutPoint altTextOffset = paintOffset;
321                 altTextOffset.move(leftBorder + leftPad, topBorder + topPad + ascent);
322
323                 // Only draw the alt text if it'll fit within the content box,
324                 // and only if it fits above the error image.
325                 TextRun textRun = RenderBlock::constructTextRun(this, font, text, style());
326                 LayoutUnit textWidth = font.width(textRun);
327                 if (errorPictureDrawn) {
328                     if (usableWidth >= textWidth && fontMetrics.height() <= imageOffset.height())
329                         context->drawText(font, textRun, altTextOffset);
330                 } else if (usableWidth >= textWidth && cHeight >= fontMetrics.height())
331                     context->drawText(font, textRun, altTextOffset);
332             }
333         }
334     } else if (m_imageResource->hasImage() && cWidth > 0 && cHeight > 0) {
335         RefPtr<Image> img = m_imageResource->image(cWidth, cHeight);
336         if (!img || img->isNull()) {
337             if (page && paintInfo.phase == PaintPhaseForeground)
338                 page->addRelevantUnpaintedObject(this, visualOverflowRect());
339             return;
340         }
341
342 #if PLATFORM(MAC)
343         if (style()->highlight() != nullAtom && !paintInfo.context->paintingDisabled())
344             paintCustomHighlight(toPoint(paintOffset - location()), style()->highlight(), true);
345 #endif
346
347         LayoutSize contentSize(cWidth, cHeight);
348         LayoutPoint contentLocation = paintOffset;
349         contentLocation.move(leftBorder + leftPad, topBorder + topPad);
350         paintIntoRect(context, LayoutRect(contentLocation, contentSize));
351         
352         if (cachedImage() && page && paintInfo.phase == PaintPhaseForeground) {
353             // For now, count images as unpainted if they are still progressively loading. We may want 
354             // to refine this in the future to account for the portion of the image that has painted.
355             if (cachedImage()->isLoading())
356                 page->addRelevantUnpaintedObject(this, LayoutRect(contentLocation, contentSize));
357             else
358                 page->addRelevantRepaintedObject(this, LayoutRect(contentLocation, contentSize));
359         }
360     }
361 }
362
363 void RenderImage::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
364 {
365     RenderReplaced::paint(paintInfo, paintOffset);
366     
367     if (paintInfo.phase == PaintPhaseOutline)
368         paintAreaElementFocusRing(paintInfo);
369 }
370     
371 void RenderImage::paintAreaElementFocusRing(PaintInfo& paintInfo)
372 {
373     Document* document = this->document();
374     
375     if (document->printing() || !document->frame()->selection()->isFocusedAndActive())
376         return;
377     
378     if (paintInfo.context->paintingDisabled() && !paintInfo.context->updatingControlTints())
379         return;
380
381     Node* focusedNode = document->focusedNode();
382     if (!focusedNode || !focusedNode->hasTagName(areaTag))
383         return;
384
385     HTMLAreaElement* areaElement = static_cast<HTMLAreaElement*>(focusedNode);
386     if (areaElement->imageElement() != node())
387         return;
388
389     // Even if the theme handles focus ring drawing for entire elements, it won't do it for
390     // an area within an image, so we don't call RenderTheme::supportsFocusRing here.
391
392     Path path = areaElement->computePath(this);
393     if (path.isEmpty())
394         return;
395
396     // FIXME: Do we need additional code to clip the path to the image's bounding box?
397
398     RenderStyle* areaElementStyle = areaElement->computedStyle();
399     unsigned short outlineWidth = areaElementStyle->outlineWidth();
400     if (!outlineWidth)
401         return;
402
403     paintInfo.context->drawFocusRing(path, outlineWidth,
404         areaElementStyle->outlineOffset(),
405         areaElementStyle->visitedDependentColor(CSSPropertyOutlineColor));
406 }
407
408 void RenderImage::areaElementFocusChanged(HTMLAreaElement* element)
409 {
410     ASSERT_UNUSED(element, element->imageElement() == node());
411
412     // It would be more efficient to only repaint the focus ring rectangle
413     // for the passed-in area element. That would require adding functions
414     // to the area element class.
415     repaint();
416 }
417
418 void RenderImage::paintIntoRect(GraphicsContext* context, const LayoutRect& rect)
419 {
420     IntRect alignedRect = pixelSnappedIntRect(rect);
421     if (!m_imageResource->hasImage() || m_imageResource->errorOccurred() || alignedRect.width() <= 0 || alignedRect.height() <= 0)
422         return;
423
424     RefPtr<Image> img = m_imageResource->image(alignedRect.width(), alignedRect.height());
425     if (!img || img->isNull())
426         return;
427
428     HTMLImageElement* imageElt = (node() && node()->hasTagName(imgTag)) ? static_cast<HTMLImageElement*>(node()) : 0;
429     CompositeOperator compositeOperator = imageElt ? imageElt->compositeOperator() : CompositeSourceOver;
430     Image* image = m_imageResource->image().get();
431     bool useLowQualityScaling = shouldPaintAtLowQuality(context, image, image, alignedRect.size());
432     context->drawImage(m_imageResource->image(alignedRect.width(), alignedRect.height()).get(), style()->colorSpace(), alignedRect, compositeOperator, shouldRespectImageOrientation(), useLowQualityScaling);
433 }
434
435 bool RenderImage::backgroundIsObscured() const
436 {
437     if (!m_imageResource->hasImage() || m_imageResource->errorOccurred())
438         return false;
439
440     if (m_imageResource->cachedImage() && !m_imageResource->cachedImage()->isLoaded())
441         return false;
442
443     EFillBox backgroundClip = style()->backgroundClip();
444
445     // Background paints under borders.
446     if (backgroundClip == BorderFillBox && style()->hasBorder() && !borderObscuresBackground())
447         return false;
448
449     // Background shows in padding area.
450     if ((backgroundClip == BorderFillBox || backgroundClip == PaddingFillBox) && style()->hasPadding())
451         return false;
452
453     // Check for bitmap image with alpha.
454     Image* image = m_imageResource->image().get();
455     if (!image || !image->isBitmapImage() || image->currentFrameHasAlpha())
456         return false;
457         
458     return true;
459 }
460
461 int RenderImage::minimumReplacedHeight() const
462 {
463     return m_imageResource->errorOccurred() ? intrinsicSize().height() : 0;
464 }
465
466 HTMLMapElement* RenderImage::imageMap() const
467 {
468     HTMLImageElement* i = node() && node()->hasTagName(imgTag) ? static_cast<HTMLImageElement*>(node()) : 0;
469     return i ? i->treeScope()->getImageMap(i->fastGetAttribute(usemapAttr)) : 0;
470 }
471
472 bool RenderImage::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, const LayoutPoint& pointInContainer, const LayoutPoint& accumulatedOffset, HitTestAction hitTestAction)
473 {
474     HitTestResult tempResult(result.point(), result.topPadding(), result.rightPadding(), result.bottomPadding(), result.leftPadding(), result.shadowContentFilterPolicy());
475     bool inside = RenderReplaced::nodeAtPoint(request, tempResult, pointInContainer, accumulatedOffset, hitTestAction);
476
477     if (tempResult.innerNode() && node()) {
478         if (HTMLMapElement* map = imageMap()) {
479             LayoutRect contentBox = contentBoxRect();
480             float scaleFactor = 1 / style()->effectiveZoom();
481             LayoutPoint mapLocation(pointInContainer.x() - accumulatedOffset.x() - this->x() - contentBox.x(), pointInContainer.y() - accumulatedOffset.y() - this->y() - contentBox.y());
482             mapLocation.scale(scaleFactor, scaleFactor);
483             
484             if (map->mapMouseEvent(mapLocation, contentBox.size(), tempResult))
485                 tempResult.setInnerNonSharedNode(node());
486         }
487     }
488
489     if (!inside && result.isRectBasedTest())
490         result.append(tempResult);
491     if (inside)
492         result = tempResult;
493     return inside;
494 }
495
496 void RenderImage::updateAltText()
497 {
498     if (!node())
499         return;
500
501     if (node()->hasTagName(inputTag))
502         m_altText = static_cast<HTMLInputElement*>(node())->altText();
503     else if (node()->hasTagName(imgTag))
504         m_altText = static_cast<HTMLImageElement*>(node())->altText();
505 }
506
507 void RenderImage::layout()
508 {
509     RenderReplaced::layout();
510
511     // Propagate container size to image resource.
512     IntSize containerSize(contentWidth(), contentHeight());
513     if (!containerSize.isEmpty())
514         m_imageResource->setContainerSizeForRenderer(containerSize);
515 }
516
517 void RenderImage::computeIntrinsicRatioInformation(FloatSize& intrinsicSize, double& intrinsicRatio, bool& isPercentageIntrinsicSize) const
518 {
519     RenderReplaced::computeIntrinsicRatioInformation(intrinsicSize, intrinsicRatio, isPercentageIntrinsicSize);
520
521     // Our intrinsicSize is empty if we're rendering generated images with relative width/height. Figure out the right intrinsic size to use.
522     if (intrinsicSize.isEmpty() && (m_imageResource->imageHasRelativeWidth() || m_imageResource->imageHasRelativeHeight())) {
523         RenderObject* containingBlock = isPositioned() ? container() : this->containingBlock();
524         if (containingBlock->isBox()) {
525             RenderBox* box = toRenderBox(containingBlock);
526             intrinsicSize.setWidth(box->availableLogicalWidth());
527             intrinsicSize.setHeight(box->availableLogicalHeight());
528         }
529     }
530     // Don't compute an intrinsic ratio to preserve historical WebKit behavior if we're painting alt text and/or a broken image.
531     if (m_imageResource && m_imageResource->errorOccurred()) {
532         intrinsicRatio = 1;
533         return;
534     }
535 }
536
537 bool RenderImage::needsPreferredWidthsRecalculation() const
538 {
539     if (RenderReplaced::needsPreferredWidthsRecalculation())
540         return true;
541     return embeddedContentBox();
542 }
543
544 RenderBox* RenderImage::embeddedContentBox() const
545 {
546     if (!m_imageResource)
547         return 0;
548
549 #if ENABLE(SVG)
550     CachedImage* cachedImage = m_imageResource->cachedImage();
551     if (cachedImage && cachedImage->image() && cachedImage->image()->isSVGImage())
552         return static_cast<SVGImage*>(cachedImage->image())->embeddedContentBox();
553 #endif
554
555     return 0;
556 }
557
558 } // namespace WebCore