summaryrefslogtreecommitdiffstats
path: root/WebCore/rendering/RenderImage.cpp
diff options
context:
space:
mode:
authorThe Android Open Source Project <initial-contribution@android.com>2009-03-03 19:30:52 -0800
committerThe Android Open Source Project <initial-contribution@android.com>2009-03-03 19:30:52 -0800
commit8e35f3cfc7fba1d1c829dc557ebad6409cbe16a2 (patch)
tree11425ea0b299d6fb89c6d3618a22d97d5bf68d0f /WebCore/rendering/RenderImage.cpp
parent648161bb0edfc3d43db63caed5cc5213bc6cb78f (diff)
downloadexternal_webkit-8e35f3cfc7fba1d1c829dc557ebad6409cbe16a2.zip
external_webkit-8e35f3cfc7fba1d1c829dc557ebad6409cbe16a2.tar.gz
external_webkit-8e35f3cfc7fba1d1c829dc557ebad6409cbe16a2.tar.bz2
auto import from //depot/cupcake/@135843
Diffstat (limited to 'WebCore/rendering/RenderImage.cpp')
-rw-r--r--WebCore/rendering/RenderImage.cpp593
1 files changed, 593 insertions, 0 deletions
diff --git a/WebCore/rendering/RenderImage.cpp b/WebCore/rendering/RenderImage.cpp
new file mode 100644
index 0000000..604c407
--- /dev/null
+++ b/WebCore/rendering/RenderImage.cpp
@@ -0,0 +1,593 @@
+/*
+ * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
+ * (C) 1999 Antti Koivisto (koivisto@kde.org)
+ * (C) 2000 Dirk Mueller (mueller@kde.org)
+ * (C) 2006 Allan Sandfeld Jensen (kde@carewolf.com)
+ * (C) 2006 Samuel Weinig (sam.weinig@gmail.com)
+ * Copyright (C) 2003, 2004, 2005, 2006, 2008 Apple Inc. All rights reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this library; see the file COPYING.LIB. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "config.h"
+#include "RenderImage.h"
+
+#include "BitmapImage.h"
+#include "Document.h"
+#include "GraphicsContext.h"
+#include "HTMLImageElement.h"
+#include "HTMLInputElement.h"
+#include "HTMLMapElement.h"
+#include "HTMLNames.h"
+#include "HitTestResult.h"
+#include "Page.h"
+#include "RenderView.h"
+#ifdef ANDROID_LAYOUT
+#include "Settings.h"
+#endif
+
+#include "SystemTime.h"
+
+using namespace std;
+
+namespace WebCore {
+
+static const double cInterpolationCutoff = 800. * 800.;
+static const double cLowQualityTimeThreshold = 0.050; // 50 ms
+
+class RenderImageScaleData {
+public:
+ RenderImageScaleData(RenderImage* image, const IntSize& size, double time, bool lowQualityScale)
+ : m_size(size)
+ , m_time(time)
+ , m_lowQualityScale(lowQualityScale)
+ , m_highQualityRepaintTimer(image, &RenderImage::highQualityRepaintTimerFired)
+ {
+ }
+
+ ~RenderImageScaleData()
+ {
+ m_highQualityRepaintTimer.stop();
+ }
+
+ const IntSize& size() const { return m_size; }
+ double time() const { return m_time; }
+ bool useLowQualityScale() const { return m_lowQualityScale; }
+ Timer<RenderImage>& hiqhQualityRepaintTimer() { return m_highQualityRepaintTimer; }
+
+ void setSize(const IntSize& s) { m_size = s; }
+ void setTime(double t) { m_time = t; }
+ void setUseLowQualityScale(bool b)
+ {
+ m_highQualityRepaintTimer.stop();
+ m_lowQualityScale = b;
+ if (b)
+ m_highQualityRepaintTimer.startOneShot(cLowQualityTimeThreshold);
+ }
+
+private:
+ IntSize m_size;
+ double m_time;
+ bool m_lowQualityScale;
+ Timer<RenderImage> m_highQualityRepaintTimer;
+};
+
+class RenderImageScaleObserver
+{
+public:
+ static bool shouldImagePaintAtLowQuality(RenderImage*, const IntSize&);
+
+ static void imageDestroyed(RenderImage* image)
+ {
+ if (gImages) {
+ RenderImageScaleData* data = gImages->take(image);
+ delete data;
+ if (gImages->size() == 0) {
+ delete gImages;
+ gImages = 0;
+ }
+ }
+ }
+
+ static void highQualityRepaintTimerFired(RenderImage* image)
+ {
+ RenderImageScaleObserver::imageDestroyed(image);
+ image->repaint();
+ }
+
+ static HashMap<RenderImage*, RenderImageScaleData*>* gImages;
+};
+
+bool RenderImageScaleObserver::shouldImagePaintAtLowQuality(RenderImage* image, const IntSize& size)
+{
+ // If the image is not a bitmap image, then none of this is relevant and we just paint at high
+ // quality.
+ if (!image->image() || !image->image()->isBitmapImage())
+ return false;
+
+ // Make sure to use the unzoomed image size, since if a full page zoom is in effect, the image
+ // is actually being scaled.
+ IntSize imageSize(image->image()->width(), image->image()->height());
+
+ // Look ourselves up in the hashtable.
+ RenderImageScaleData* data = 0;
+ if (gImages)
+ data = gImages->get(image);
+
+ if (imageSize == size) {
+ // There is no scale in effect. If we had a scale in effect before, we can just delete this data.
+ if (data) {
+ gImages->remove(image);
+ delete data;
+ }
+ return false;
+ }
+
+ // There is no need to hash scaled images that always use low quality mode when the page demands it. This is the iChat case.
+ if (image->document()->page()->inLowQualityImageInterpolationMode()) {
+ double totalPixels = static_cast<double>(image->image()->width()) * static_cast<double>(image->image()->height());
+ if (totalPixels > cInterpolationCutoff)
+ return true;
+ }
+
+ // If there is no data yet, we will paint the first scale at high quality and record the paint time in case a second scale happens
+ // very soon.
+ if (!data) {
+ data = new RenderImageScaleData(image, size, currentTime(), false);
+ if (!gImages)
+ gImages = new HashMap<RenderImage*, RenderImageScaleData*>;
+ gImages->set(image, data);
+ return false;
+ }
+
+ // We are scaled, but we painted already at this size, so just keep using whatever mode we last painted with.
+ if (data->size() == size)
+ return data->useLowQualityScale();
+
+ // We have data and our size just changed. If this change happened quickly, go into low quality mode and then set a repaint
+ // timer to paint in high quality mode. Otherwise it is ok to just paint in high quality mode.
+ double newTime = currentTime();
+ data->setUseLowQualityScale(newTime - data->time() < cLowQualityTimeThreshold);
+ data->setTime(newTime);
+ data->setSize(size);
+ return data->useLowQualityScale();
+}
+
+HashMap<RenderImage*, RenderImageScaleData*>* RenderImageScaleObserver::gImages = 0;
+
+void RenderImage::highQualityRepaintTimerFired(Timer<RenderImage>* timer)
+{
+ RenderImageScaleObserver::highQualityRepaintTimerFired(this);
+}
+
+using namespace HTMLNames;
+
+RenderImage::RenderImage(Node* node)
+ : RenderReplaced(node, IntSize(0, 0))
+ , m_cachedImage(0)
+{
+ updateAltText();
+}
+
+RenderImage::~RenderImage()
+{
+ if (m_cachedImage)
+ m_cachedImage->removeClient(this);
+ RenderImageScaleObserver::imageDestroyed(this);
+}
+
+void RenderImage::setCachedImage(CachedImage* newImage)
+{
+ if (m_cachedImage == newImage)
+ return;
+ if (m_cachedImage)
+ m_cachedImage->removeClient(this);
+ m_cachedImage = newImage;
+ if (m_cachedImage) {
+ m_cachedImage->addClient(this);
+ if (m_cachedImage->errorOccurred())
+ imageChanged(m_cachedImage.get());
+ }
+}
+
+// If we'll be displaying either alt text or an image, add some padding.
+static const unsigned short paddingWidth = 4;
+static const unsigned short paddingHeight = 4;
+
+// Alt text is restricted to this maximum size, in pixels. These are
+// signed integers because they are compared with other signed values.
+static const int maxAltTextWidth = 1024;
+static const int maxAltTextHeight = 256;
+
+// Sets the image height and width to fit the alt text. Returns true if the
+// image size changed.
+bool RenderImage::setImageSizeForAltText(CachedImage* newImage /* = 0 */)
+{
+ int imageWidth = 0;
+ int imageHeight = 0;
+
+ // If we'll be displaying either text or an image, add a little padding.
+ if (!m_altText.isEmpty() || newImage) {
+ imageWidth = paddingWidth;
+ imageHeight = paddingHeight;
+ }
+
+ if (newImage) {
+ // imageSize() returns 0 for the error image. We need the true size of the
+ // error image, so we have to get it by grabbing image() directly.
+ imageWidth += newImage->image()->width() * style()->effectiveZoom();
+ imageHeight += newImage->image()->height() * style()->effectiveZoom();
+ }
+
+ // we have an alt and the user meant it (its not a text we invented)
+ if (!m_altText.isEmpty()) {
+ const Font& font = style()->font();
+ imageWidth = max(imageWidth, min(font.width(TextRun(m_altText.characters(), m_altText.length())), maxAltTextWidth));
+ imageHeight = max(imageHeight, min(font.height(), maxAltTextHeight));
+ }
+
+ IntSize imageSize = IntSize(imageWidth, imageHeight);
+ if (imageSize == intrinsicSize())
+ return false;
+
+ setIntrinsicSize(imageSize);
+ return true;
+}
+
+void RenderImage::imageChanged(WrappedImagePtr newImage)
+{
+ if (documentBeingDestroyed())
+ return;
+
+ if (hasBoxDecorations() || hasMask())
+ RenderReplaced::imageChanged(newImage);
+
+ if (newImage != imagePtr() || !newImage)
+ return;
+
+ bool imageSizeChanged = false;
+
+ // Set image dimensions, taking into account the size of the alt text.
+ if (errorOccurred())
+ imageSizeChanged = setImageSizeForAltText(m_cachedImage.get());
+
+ bool shouldRepaint = true;
+
+ // Image dimensions have been changed, see what needs to be done
+ if (imageSize(style()->effectiveZoom()) != intrinsicSize() || imageSizeChanged) {
+ if (!errorOccurred())
+ setIntrinsicSize(imageSize(style()->effectiveZoom()));
+
+ // In the case of generated image content using :before/:after, we might not be in the
+ // render tree yet. In that case, we don't need to worry about check for layout, since we'll get a
+ // layout when we get added in to the render tree hierarchy later.
+ if (containingBlock()) {
+ // lets see if we need to relayout at all..
+ int oldwidth = m_width;
+ int oldheight = m_height;
+ if (!prefWidthsDirty())
+ setPrefWidthsDirty(true);
+ calcWidth();
+ calcHeight();
+
+ if (imageSizeChanged || m_width != oldwidth || m_height != oldheight) {
+ shouldRepaint = false;
+ if (!selfNeedsLayout())
+ setNeedsLayout(true);
+ }
+
+ m_width = oldwidth;
+ m_height = oldheight;
+ }
+ }
+
+ if (shouldRepaint) {
+ // FIXME: We always just do a complete repaint, since we always pass in the full image
+ // rect at the moment anyway.
+ repaintRectangle(contentBox());
+ }
+}
+
+void RenderImage::resetAnimation()
+{
+ if (m_cachedImage) {
+ image()->resetAnimation();
+ if (!needsLayout())
+ repaint();
+ }
+}
+
+void RenderImage::paintReplaced(PaintInfo& paintInfo, int tx, int ty)
+{
+ int cWidth = contentWidth();
+ int cHeight = contentHeight();
+ int leftBorder = borderLeft();
+ int topBorder = borderTop();
+ int leftPad = paddingLeft();
+ int topPad = paddingTop();
+
+ if (document()->printing() && !view()->printImages())
+ return;
+
+ GraphicsContext* context = paintInfo.context;
+
+ if (!hasImage() || errorOccurred()) {
+ if (paintInfo.phase == PaintPhaseSelection)
+ return;
+
+ if (cWidth > 2 && cHeight > 2) {
+ // Draw an outline rect where the image should be.
+ context->setStrokeStyle(SolidStroke);
+ context->setStrokeColor(Color::lightGray);
+ context->setFillColor(Color::transparent);
+ context->drawRect(IntRect(tx + leftBorder + leftPad, ty + topBorder + topPad, cWidth, cHeight));
+
+ bool errorPictureDrawn = false;
+ int imageX = 0;
+ int imageY = 0;
+ // When calculating the usable dimensions, exclude the pixels of
+ // the ouline rect so the error image/alt text doesn't draw on it.
+ int usableWidth = cWidth - 2;
+ int usableHeight = cHeight - 2;
+
+ if (errorOccurred() && !image()->isNull() && (usableWidth >= image()->width()) && (usableHeight >= image()->height())) {
+ // Center the error image, accounting for border and padding.
+ int centerX = (usableWidth - image()->width()) / 2;
+ if (centerX < 0)
+ centerX = 0;
+ int centerY = (usableHeight - image()->height()) / 2;
+ if (centerY < 0)
+ centerY = 0;
+ imageX = leftBorder + leftPad + centerX + 1;
+ imageY = topBorder + topPad + centerY + 1;
+ context->drawImage(image(), IntPoint(tx + imageX, ty + imageY));
+ errorPictureDrawn = true;
+ }
+
+ if (!m_altText.isEmpty()) {
+ String text = m_altText;
+ text.replace('\\', backslashAsCurrencySymbol());
+ context->setFont(style()->font());
+ context->setFillColor(style()->color());
+ int ax = tx + leftBorder + leftPad;
+ int ay = ty + topBorder + topPad;
+ const Font& font = style()->font();
+ int ascent = font.ascent();
+
+ // Only draw the alt text if it'll fit within the content box,
+ // and only if it fits above the error image.
+ TextRun textRun(text.characters(), text.length());
+ int textWidth = font.width(textRun);
+ if (errorPictureDrawn) {
+ if (usableWidth >= textWidth && font.height() <= imageY)
+ context->drawText(textRun, IntPoint(ax, ay + ascent));
+ } else if (usableWidth >= textWidth && cHeight >= font.height())
+ context->drawText(textRun, IntPoint(ax, ay + ascent));
+ }
+ }
+ } else if (hasImage() && cWidth > 0 && cHeight > 0) {
+ Image* img = image(cWidth, cHeight);
+ if (!img || img->isNull())
+ return;
+
+#if PLATFORM(MAC)
+ if (style()->highlight() != nullAtom && !paintInfo.context->paintingDisabled())
+ paintCustomHighlight(tx - m_x, ty - m_y, style()->highlight(), true);
+#endif
+
+ IntSize contentSize(cWidth, cHeight);
+ bool useLowQualityScaling = RenderImageScaleObserver::shouldImagePaintAtLowQuality(this, contentSize);
+ IntRect rect(IntPoint(tx + leftBorder + leftPad, ty + topBorder + topPad), contentSize);
+ HTMLImageElement* imageElt = (element() && element()->hasTagName(imgTag)) ? static_cast<HTMLImageElement*>(element()) : 0;
+ CompositeOperator compositeOperator = imageElt ? imageElt->compositeOperator() : CompositeSourceOver;
+ context->drawImage(image(cWidth, cHeight), rect, compositeOperator, useLowQualityScaling);
+ }
+}
+
+int RenderImage::minimumReplacedHeight() const
+{
+ return errorOccurred() ? intrinsicSize().height() : 0;
+}
+
+HTMLMapElement* RenderImage::imageMap()
+{
+ HTMLImageElement* i = element() && element()->hasTagName(imgTag) ? static_cast<HTMLImageElement*>(element()) : 0;
+ return i ? i->document()->getImageMap(i->useMap()) : 0;
+}
+
+bool RenderImage::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, int _x, int _y, int _tx, int _ty, HitTestAction hitTestAction)
+{
+ bool inside = RenderReplaced::nodeAtPoint(request, result, _x, _y, _tx, _ty, hitTestAction);
+
+ if (inside && element()) {
+ int tx = _tx + m_x;
+ int ty = _ty + m_y;
+
+ HTMLMapElement* map = imageMap();
+ if (map) {
+ // we're a client side image map
+ inside = map->mapMouseEvent(_x - tx, _y - ty, IntSize(contentWidth(), contentHeight()), result);
+ result.setInnerNonSharedNode(element());
+ }
+ }
+
+ return inside;
+}
+
+void RenderImage::updateAltText()
+{
+ if (!element())
+ return;
+
+ if (element()->hasTagName(inputTag))
+ m_altText = static_cast<HTMLInputElement*>(element())->altText();
+ else if (element()->hasTagName(imgTag))
+ m_altText = static_cast<HTMLImageElement*>(element())->altText();
+}
+
+bool RenderImage::isWidthSpecified() const
+{
+ switch (style()->width().type()) {
+ case Fixed:
+ case Percent:
+ return true;
+ case Auto:
+ case Relative: // FIXME: Shouldn't this case return true?
+ case Static:
+ case Intrinsic:
+ case MinIntrinsic:
+ return false;
+ }
+ ASSERT(false);
+ return false;
+}
+
+bool RenderImage::isHeightSpecified() const
+{
+ switch (style()->height().type()) {
+ case Fixed:
+ case Percent:
+ return true;
+ case Auto:
+ case Relative: // FIXME: Shouldn't this case return true?
+ case Static:
+ case Intrinsic:
+ case MinIntrinsic:
+ return false;
+ }
+ ASSERT(false);
+ return false;
+}
+
+int RenderImage::calcReplacedWidth(bool includeMaxWidth) const
+{
+ if (imageHasRelativeWidth())
+ if (RenderObject* cb = isPositioned() ? container() : containingBlock())
+ setImageContainerSize(IntSize(cb->availableWidth(), cb->availableHeight()));
+
+ int width;
+ if (isWidthSpecified())
+ width = calcReplacedWidthUsing(style()->width());
+ else if (usesImageContainerSize())
+ width = imageSize(style()->effectiveZoom()).width();
+ else if (imageHasRelativeWidth())
+ width = 0; // If the image is relatively-sized, set the width to 0 until there is a set container size.
+ else
+ width = calcAspectRatioWidth();
+
+ int minW = calcReplacedWidthUsing(style()->minWidth());
+ int maxW = !includeMaxWidth || style()->maxWidth().isUndefined() ? width : calcReplacedWidthUsing(style()->maxWidth());
+
+#ifdef ANDROID_LAYOUT
+ width = max(minW, min(width, maxW));
+ // in SSR mode, we will fit the image to its container width
+ if (document()->settings()->layoutAlgorithm() == Settings::kLayoutSSR) {
+ int cw = containingBlockWidth();
+ if (cw && width>cw)
+ width = cw;
+ }
+ return width;
+#else
+ return max(minW, min(width, maxW));
+#endif
+}
+
+int RenderImage::calcReplacedHeight() const
+{
+ int height;
+ if (isHeightSpecified())
+ height = calcReplacedHeightUsing(style()->height());
+ else if (usesImageContainerSize())
+ height = imageSize(style()->effectiveZoom()).height();
+ else if (imageHasRelativeHeight())
+ height = 0; // If the image is relatively-sized, set the height to 0 until there is a set container size.
+ else
+ height = calcAspectRatioHeight();
+
+ int minH = calcReplacedHeightUsing(style()->minHeight());
+ int maxH = style()->maxHeight().isUndefined() ? height : calcReplacedHeightUsing(style()->maxHeight());
+
+#ifdef ANDROID_LAYOUT
+ height = max(minH, min(height, maxH));
+ // in SSR mode, we will fit the image to its container width
+ if (height && document()->settings()->layoutAlgorithm() == Settings::kLayoutSSR) {
+ int width;
+ if (isWidthSpecified())
+ width = calcReplacedWidthUsing(style()->width());
+ else
+ width = calcAspectRatioWidth();
+ int minW = calcReplacedWidthUsing(style()->minWidth());
+ int maxW = style()->maxWidth().value() == undefinedLength ? width :
+ calcReplacedWidthUsing(style()->maxWidth());
+ width = max(minW, min(width, maxW));
+
+ int cw = containingBlockWidth();
+ if (cw && width && width>cw)
+ height = cw * height / width; // preserve aspect ratio
+ }
+ return height;
+#else
+ return max(minH, min(height, maxH));
+#endif
+}
+
+int RenderImage::calcAspectRatioWidth() const
+{
+ IntSize size = intrinsicSize();
+ if (!size.height())
+ return 0;
+ if (!hasImage() || errorOccurred())
+ return size.width(); // Don't bother scaling.
+ return RenderReplaced::calcReplacedHeight() * size.width() / size.height();
+}
+
+int RenderImage::calcAspectRatioHeight() const
+{
+ IntSize size = intrinsicSize();
+ if (!size.width())
+ return 0;
+ if (!hasImage() || errorOccurred())
+ return size.height(); // Don't bother scaling.
+ return RenderReplaced::calcReplacedWidth() * size.height() / size.width();
+}
+
+void RenderImage::calcPrefWidths()
+{
+ ASSERT(prefWidthsDirty());
+
+ int paddingAndBorders = paddingLeft() + paddingRight() + borderLeft() + borderRight();
+ m_maxPrefWidth = calcReplacedWidth(false) + paddingAndBorders;
+
+ if (style()->maxWidth().isFixed() && style()->maxWidth().value() != undefinedLength)
+ m_maxPrefWidth = min(m_maxPrefWidth, style()->maxWidth().value() + (style()->boxSizing() == CONTENT_BOX ? paddingAndBorders : 0));
+
+ if (style()->width().isPercent() || style()->height().isPercent() ||
+ style()->maxWidth().isPercent() || style()->maxHeight().isPercent() ||
+ style()->minWidth().isPercent() || style()->minHeight().isPercent())
+ m_minPrefWidth = 0;
+ else
+ m_minPrefWidth = m_maxPrefWidth;
+
+ setPrefWidthsDirty(false);
+}
+
+Image* RenderImage::nullImage()
+{
+ return Image::nullImage();
+}
+
+} // namespace WebCore