From 8e35f3cfc7fba1d1c829dc557ebad6409cbe16a2 Mon Sep 17 00:00:00 2001 From: The Android Open Source Project Date: Tue, 3 Mar 2009 19:30:52 -0800 Subject: auto import from //depot/cupcake/@135843 --- WebCore/rendering/RenderImage.cpp | 593 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 593 insertions(+) create mode 100644 WebCore/rendering/RenderImage.cpp (limited to 'WebCore/rendering/RenderImage.cpp') 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& 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 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* 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(image->image()->width()) * static_cast(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; + 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* RenderImageScaleObserver::gImages = 0; + +void RenderImage::highQualityRepaintTimerFired(Timer* 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(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(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(element())->altText(); + else if (element()->hasTagName(imgTag)) + m_altText = static_cast(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 -- cgit v1.1