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