This source file includes following definitions.
- m_imageDevicePixelRatio
- createAnonymous
- setImageResource
- imageSizeForError
- setImageSizeForAltText
- styleDidChange
- imageChanged
- updateIntrinsicSizeIfNeeded
- updateInnerContentRect
- imageDimensionsChanged
- notifyFinished
- paintReplaced
- paint
- paintAreaElementFocusRing
- areaElementFocusChanged
- paintIntoRect
- boxShadowShouldBeAppliedToBackground
- foregroundIsKnownToBeOpaqueInRect
- computeBackgroundIsKnownToBeObscured
- minimumReplacedHeight
- imageMap
- nodeAtPoint
- updateAltText
- layout
- updateImageLoadingPriorities
- computeIntrinsicRatioInformation
- needsPreferredWidthsRecalculation
- embeddedContentBox
#include "config.h"
#include "core/rendering/RenderImage.h"
#include "HTMLNames.h"
#include "core/editing/FrameSelection.h"
#include "core/fetch/ImageResource.h"
#include "core/fetch/ResourceLoadPriorityOptimizer.h"
#include "core/fetch/ResourceLoader.h"
#include "core/frame/LocalFrame.h"
#include "core/html/HTMLAreaElement.h"
#include "core/html/HTMLImageElement.h"
#include "core/html/HTMLInputElement.h"
#include "core/html/HTMLMapElement.h"
#include "core/inspector/InspectorInstrumentation.h"
#include "core/rendering/HitTestResult.h"
#include "core/rendering/LayoutRectRecorder.h"
#include "core/rendering/PaintInfo.h"
#include "core/rendering/RenderView.h"
#include "core/svg/graphics/SVGImage.h"
#include "platform/fonts/Font.h"
#include "platform/fonts/FontCache.h"
#include "platform/graphics/GraphicsContext.h"
#include "platform/graphics/GraphicsContextStateSaver.h"
using namespace std;
namespace WebCore {
using namespace HTMLNames;
RenderImage::RenderImage(Element* element)
: RenderReplaced(element, IntSize())
, m_didIncrementVisuallyNonEmptyPixelCount(false)
, m_isGeneratedContent(false)
, m_imageDevicePixelRatio(1.0f)
{
updateAltText();
ResourceLoadPriorityOptimizer::resourceLoadPriorityOptimizer()->addRenderObject(this);
}
RenderImage* RenderImage::createAnonymous(Document* document)
{
RenderImage* image = new RenderImage(0);
image->setDocumentForAnonymous(document);
return image;
}
RenderImage::~RenderImage()
{
ASSERT(m_imageResource);
m_imageResource->shutdown();
}
void RenderImage::setImageResource(PassOwnPtr<RenderImageResource> imageResource)
{
ASSERT(!m_imageResource);
m_imageResource = imageResource;
m_imageResource->initialize(this);
}
static const unsigned short paddingWidth = 4;
static const unsigned short paddingHeight = 4;
static const float maxAltTextWidth = 1024;
static const int maxAltTextHeight = 256;
IntSize RenderImage::imageSizeForError(ImageResource* newImage) const
{
ASSERT_ARG(newImage, newImage);
ASSERT_ARG(newImage, newImage->imageForRenderer(this));
IntSize imageSize;
if (newImage->willPaintBrokenImage()) {
float deviceScaleFactor = WebCore::deviceScaleFactor(frame());
pair<Image*, float> brokenImageAndImageScaleFactor = ImageResource::brokenImage(deviceScaleFactor);
imageSize = brokenImageAndImageScaleFactor.first->size();
imageSize.scale(1 / brokenImageAndImageScaleFactor.second);
} else
imageSize = newImage->imageForRenderer(this)->size();
return IntSize(paddingWidth + imageSize.width() * style()->effectiveZoom(), paddingHeight + imageSize.height() * style()->effectiveZoom());
}
bool RenderImage::setImageSizeForAltText(ImageResource* newImage )
{
IntSize imageSize;
if (newImage && newImage->imageForRenderer(this))
imageSize = imageSizeForError(newImage);
else if (!m_altText.isEmpty() || newImage) {
imageSize = IntSize(paddingWidth, paddingHeight);
}
if (!m_altText.isEmpty()) {
FontCachePurgePreventer fontCachePurgePreventer;
const Font& font = style()->font();
IntSize paddedTextSize(paddingWidth + min(ceilf(font.width(RenderBlockFlow::constructTextRun(this, font, m_altText, style()))), maxAltTextWidth), paddingHeight + min(font.fontMetrics().height(), maxAltTextHeight));
imageSize = imageSize.expandedTo(paddedTextSize);
}
if (imageSize == intrinsicSize())
return false;
setIntrinsicSize(imageSize);
return true;
}
void RenderImage::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
{
RenderReplaced::styleDidChange(diff, oldStyle);
}
void RenderImage::imageChanged(WrappedImagePtr newImage, const IntRect* rect)
{
if (documentBeingDestroyed())
return;
if (hasBoxDecorations() || hasMask())
RenderReplaced::imageChanged(newImage, rect);
if (!m_imageResource)
return;
if (newImage != m_imageResource->imagePtr())
return;
if (m_imageResource->cachedImage() && m_imageResource->cachedImage()->hasDevicePixelRatioHeaderValue())
m_imageDevicePixelRatio = 1 / m_imageResource->cachedImage()->devicePixelRatioHeaderValue();
if (!m_didIncrementVisuallyNonEmptyPixelCount) {
view()->frameView()->incrementVisuallyNonEmptyPixelCount(flooredIntSize(m_imageResource->imageSize(1.0f)));
m_didIncrementVisuallyNonEmptyPixelCount = true;
}
bool imageSizeChanged = false;
if (m_imageResource->errorOccurred() || !newImage)
imageSizeChanged = setImageSizeForAltText(m_imageResource->cachedImage());
imageDimensionsChanged(imageSizeChanged, rect);
}
bool RenderImage::updateIntrinsicSizeIfNeeded(const LayoutSize& newSize, bool imageSizeChanged)
{
if (newSize == intrinsicSize() && !imageSizeChanged)
return false;
if (m_imageResource->errorOccurred() || !m_imageResource->hasImage())
return imageSizeChanged;
setIntrinsicSize(newSize);
return true;
}
void RenderImage::updateInnerContentRect()
{
LayoutRect containerRect = replacedContentRect();
IntSize containerSize(containerRect.width(), containerRect.height());
if (!containerSize.isEmpty())
m_imageResource->setContainerSizeForRenderer(containerSize);
}
void RenderImage::imageDimensionsChanged(bool imageSizeChanged, const IntRect* rect)
{
bool intrinsicSizeChanged = updateIntrinsicSizeIfNeeded(m_imageResource->intrinsicSize(style()->effectiveZoom()), imageSizeChanged);
if (!containingBlock())
return;
bool shouldRepaint = true;
if (intrinsicSizeChanged) {
if (!preferredLogicalWidthsDirty())
setPreferredLogicalWidthsDirty();
bool hasOverrideSize = hasOverrideHeight() || hasOverrideWidth();
if (!hasOverrideSize && !imageSizeChanged) {
LogicalExtentComputedValues computedValues;
computeLogicalWidth(computedValues);
LayoutUnit newWidth = computedValues.m_extent;
computeLogicalHeight(height(), 0, computedValues);
LayoutUnit newHeight = computedValues.m_extent;
imageSizeChanged = width() != newWidth || height() != newHeight;
}
bool containingBlockNeedsToRecomputePreferredSize =
style()->logicalWidth().isPercent()
|| style()->logicalMaxWidth().isPercent()
|| style()->logicalMinWidth().isPercent();
if (imageSizeChanged || hasOverrideSize || containingBlockNeedsToRecomputePreferredSize) {
shouldRepaint = false;
if (!selfNeedsLayout())
setNeedsLayout();
}
}
if (everHadLayout() && !selfNeedsLayout()) {
updateInnerContentRect();
}
if (shouldRepaint) {
LayoutRect repaintRect;
if (rect) {
repaintRect = enclosingIntRect(mapRect(*rect, FloatRect(FloatPoint(), m_imageResource->imageSize(1.0f)), contentBoxRect()));
repaintRect.intersect(contentBoxRect());
} else
repaintRect = contentBoxRect();
{
AllowRepaintScope scoper(frameView());
repaintRectangle(repaintRect);
}
contentChanged(ImageChanged);
}
}
void RenderImage::notifyFinished(Resource* newImage)
{
if (!m_imageResource)
return;
if (documentBeingDestroyed())
return;
invalidateBackgroundObscurationStatus();
if (newImage == m_imageResource->cachedImage()) {
contentChanged(ImageChanged);
}
}
void RenderImage::paintReplaced(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
{
LayoutUnit cWidth = contentWidth();
LayoutUnit cHeight = contentHeight();
LayoutUnit leftBorder = borderLeft();
LayoutUnit topBorder = borderTop();
LayoutUnit leftPad = paddingLeft();
LayoutUnit topPad = paddingTop();
GraphicsContext* context = paintInfo.context;
if (!m_imageResource->hasImage() || m_imageResource->errorOccurred()) {
if (paintInfo.phase == PaintPhaseSelection)
return;
if (cWidth > 2 && cHeight > 2) {
const int borderWidth = 1;
context->setStrokeStyle(SolidStroke);
context->setStrokeColor(Color::lightGray);
context->setFillColor(Color::transparent);
context->drawRect(pixelSnappedIntRect(LayoutRect(paintOffset.x() + leftBorder + leftPad, paintOffset.y() + topBorder + topPad, cWidth, cHeight)));
bool errorPictureDrawn = false;
LayoutSize imageOffset;
LayoutUnit usableWidth = cWidth - 2 * borderWidth;
LayoutUnit usableHeight = cHeight - 2 * borderWidth;
RefPtr<Image> image = m_imageResource->image();
if (m_imageResource->errorOccurred() && !image->isNull() && usableWidth >= image->width() && usableHeight >= image->height()) {
float deviceScaleFactor = WebCore::deviceScaleFactor(frame());
pair<Image*, float> brokenImageAndImageScaleFactor = ImageResource::brokenImage(deviceScaleFactor);
image = brokenImageAndImageScaleFactor.first;
IntSize imageSize = image->size();
imageSize.scale(1 / brokenImageAndImageScaleFactor.second);
LayoutUnit centerX = (usableWidth - imageSize.width()) / 2;
if (centerX < 0)
centerX = 0;
LayoutUnit centerY = (usableHeight - imageSize.height()) / 2;
if (centerY < 0)
centerY = 0;
imageOffset = LayoutSize(leftBorder + leftPad + centerX + borderWidth, topBorder + topPad + centerY + borderWidth);
context->drawImage(image.get(), pixelSnappedIntRect(LayoutRect(paintOffset + imageOffset, imageSize)), CompositeSourceOver, shouldRespectImageOrientation());
errorPictureDrawn = true;
}
if (!m_altText.isEmpty()) {
const Font& font = style()->font();
const FontMetrics& fontMetrics = font.fontMetrics();
LayoutUnit ascent = fontMetrics.ascent();
LayoutPoint textRectOrigin = paintOffset;
textRectOrigin.move(leftBorder + leftPad + (paddingWidth / 2) - borderWidth, topBorder + topPad + (paddingHeight / 2) - borderWidth);
LayoutPoint textOrigin(textRectOrigin.x(), textRectOrigin.y() + ascent);
TextRun textRun = RenderBlockFlow::constructTextRun(this, font, m_altText, style(), TextRun::AllowTrailingExpansion | TextRun::ForbidLeadingExpansion, DefaultTextRunFlags | RespectDirection);
float textWidth = font.width(textRun);
TextRunPaintInfo textRunPaintInfo(textRun);
textRunPaintInfo.bounds = FloatRect(textRectOrigin, FloatSize(textWidth, fontMetrics.height()));
context->setFillColor(resolveColor(CSSPropertyColor));
if (textRun.direction() == RTL) {
int availableWidth = cWidth - static_cast<int>(paddingWidth);
textOrigin.move(availableWidth - ceilf(textWidth), 0);
}
if (errorPictureDrawn) {
if (usableWidth >= textWidth && fontMetrics.height() <= imageOffset.height())
context->drawBidiText(font, textRunPaintInfo, textOrigin);
} else if (usableWidth >= textWidth && usableHeight >= fontMetrics.height()) {
context->drawBidiText(font, textRunPaintInfo, textOrigin);
}
}
}
} else if (m_imageResource->hasImage() && cWidth > 0 && cHeight > 0) {
RefPtr<Image> img = m_imageResource->image(cWidth, cHeight);
if (!img || img->isNull())
return;
LayoutRect contentRect = contentBoxRect();
contentRect.moveBy(paintOffset);
LayoutRect paintRect = replacedContentRect();
paintRect.moveBy(paintOffset);
bool clip = !contentRect.contains(paintRect);
if (clip) {
context->save();
context->clip(contentRect);
}
paintIntoRect(context, paintRect);
if (clip)
context->restore();
}
}
void RenderImage::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
{
RenderReplaced::paint(paintInfo, paintOffset);
if (paintInfo.phase == PaintPhaseOutline)
paintAreaElementFocusRing(paintInfo);
}
void RenderImage::paintAreaElementFocusRing(PaintInfo& paintInfo)
{
Document& document = this->document();
if (document.printing() || !document.frame()->selection().isFocusedAndActive())
return;
if (paintInfo.context->paintingDisabled() && !paintInfo.context->updatingControlTints())
return;
Element* focusedElement = document.focusedElement();
if (!isHTMLAreaElement(focusedElement))
return;
HTMLAreaElement& areaElement = toHTMLAreaElement(*focusedElement);
if (areaElement.imageElement() != node())
return;
Path path = areaElement.computePath(this);
if (path.isEmpty())
return;
RenderStyle* areaElementStyle = areaElement.computedStyle();
unsigned short outlineWidth = areaElementStyle->outlineWidth();
if (!outlineWidth)
return;
GraphicsContextStateSaver savedContext(*paintInfo.context);
paintInfo.context->clip(absoluteContentBox());
paintInfo.context->drawFocusRing(path, outlineWidth,
areaElementStyle->outlineOffset(),
resolveColor(areaElementStyle, CSSPropertyOutlineColor));
}
void RenderImage::areaElementFocusChanged(HTMLAreaElement* areaElement)
{
ASSERT(areaElement->imageElement() == node());
Path path = areaElement->computePath(this);
if (path.isEmpty())
return;
RenderStyle* areaElementStyle = areaElement->computedStyle();
unsigned short outlineWidth = areaElementStyle->outlineWidth();
IntRect repaintRect = enclosingIntRect(path.boundingRect());
repaintRect.moveBy(-absoluteContentBox().location());
repaintRect.inflate(outlineWidth);
repaintRectangle(repaintRect);
}
void RenderImage::paintIntoRect(GraphicsContext* context, const LayoutRect& rect)
{
IntRect alignedRect = pixelSnappedIntRect(rect);
if (!m_imageResource->hasImage() || m_imageResource->errorOccurred() || alignedRect.width() <= 0 || alignedRect.height() <= 0)
return;
RefPtr<Image> img = m_imageResource->image(alignedRect.width(), alignedRect.height());
if (!img || img->isNull())
return;
HTMLImageElement* imageElt = isHTMLImageElement(node()) ? toHTMLImageElement(node()) : 0;
CompositeOperator compositeOperator = imageElt ? imageElt->compositeOperator() : CompositeSourceOver;
Image* image = m_imageResource->image().get();
InterpolationQuality interpolationQuality = chooseInterpolationQuality(context, image, image, alignedRect.size());
InspectorInstrumentation::willPaintImage(this);
InterpolationQuality previousInterpolationQuality = context->imageInterpolationQuality();
context->setImageInterpolationQuality(interpolationQuality);
context->drawImage(m_imageResource->image(alignedRect.width(), alignedRect.height()).get(), alignedRect, compositeOperator, shouldRespectImageOrientation());
context->setImageInterpolationQuality(previousInterpolationQuality);
InspectorInstrumentation::didPaintImage(this);
}
bool RenderImage::boxShadowShouldBeAppliedToBackground(BackgroundBleedAvoidance bleedAvoidance, InlineFlowBox*) const
{
if (!RenderBoxModelObject::boxShadowShouldBeAppliedToBackground(bleedAvoidance))
return false;
return !const_cast<RenderImage*>(this)->backgroundIsKnownToBeObscured();
}
bool RenderImage::foregroundIsKnownToBeOpaqueInRect(const LayoutRect& localRect, unsigned) const
{
if (!m_imageResource->hasImage() || m_imageResource->errorOccurred())
return false;
if (m_imageResource->cachedImage() && !m_imageResource->cachedImage()->isLoaded())
return false;
if (!contentBoxRect().contains(localRect))
return false;
EFillBox backgroundClip = style()->backgroundClip();
if (backgroundClip == BorderFillBox && style()->hasBorder() && !borderObscuresBackground())
return false;
if ((backgroundClip == BorderFillBox || backgroundClip == PaddingFillBox) && style()->hasPadding())
return false;
if (style()->objectPosition() != RenderStyle::initialObjectPosition())
return false;
ObjectFit objectFit = style()->objectFit();
if (objectFit != ObjectFitFill && objectFit != ObjectFitCover)
return false;
return m_imageResource->cachedImage() && m_imageResource->cachedImage()->currentFrameKnownToBeOpaque(this);
}
bool RenderImage::computeBackgroundIsKnownToBeObscured()
{
if (!hasBackground())
return false;
LayoutRect paintedExtent;
if (!getBackgroundPaintedExtent(paintedExtent))
return false;
return foregroundIsKnownToBeOpaqueInRect(paintedExtent, 0);
}
LayoutUnit RenderImage::minimumReplacedHeight() const
{
return m_imageResource->errorOccurred() ? intrinsicSize().height() : LayoutUnit();
}
HTMLMapElement* RenderImage::imageMap() const
{
HTMLImageElement* i = isHTMLImageElement(node()) ? toHTMLImageElement(node()) : 0;
return i ? i->treeScope().getImageMap(i->fastGetAttribute(usemapAttr)) : 0;
}
bool RenderImage::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, HitTestAction hitTestAction)
{
HitTestResult tempResult(result.hitTestLocation());
bool inside = RenderReplaced::nodeAtPoint(request, tempResult, locationInContainer, accumulatedOffset, hitTestAction);
if (tempResult.innerNode() && node()) {
if (HTMLMapElement* map = imageMap()) {
LayoutRect contentBox = contentBoxRect();
float scaleFactor = 1 / style()->effectiveZoom();
LayoutPoint mapLocation = locationInContainer.point() - toLayoutSize(accumulatedOffset) - locationOffset() - toLayoutSize(contentBox.location());
mapLocation.scale(scaleFactor, scaleFactor);
if (map->mapMouseEvent(mapLocation, contentBox.size(), tempResult))
tempResult.setInnerNonSharedNode(node());
}
}
if (!inside && result.isRectBasedTest())
result.append(tempResult);
if (inside)
result = tempResult;
return inside;
}
void RenderImage::updateAltText()
{
if (!node())
return;
if (isHTMLInputElement(*node()))
m_altText = toHTMLInputElement(node())->altText();
else if (isHTMLImageElement(*node()))
m_altText = toHTMLImageElement(node())->altText();
}
void RenderImage::layout()
{
LayoutRectRecorder recorder(*this);
RenderReplaced::layout();
updateInnerContentRect();
}
bool RenderImage::updateImageLoadingPriorities()
{
if (!m_imageResource || !m_imageResource->cachedImage() || m_imageResource->cachedImage()->isLoaded())
return false;
LayoutRect viewBounds = viewRect();
LayoutRect objectBounds = absoluteContentBox();
bool isVisible;
if (!objectBounds.isEmpty())
isVisible = viewBounds.intersects(objectBounds);
else
isVisible = viewBounds.contains(objectBounds);
ResourceLoadPriorityOptimizer::VisibilityStatus status = isVisible ?
ResourceLoadPriorityOptimizer::Visible : ResourceLoadPriorityOptimizer::NotVisible;
LayoutRect screenArea;
if (!objectBounds.isEmpty()) {
screenArea = viewBounds;
screenArea.intersect(objectBounds);
}
ResourceLoadPriorityOptimizer::resourceLoadPriorityOptimizer()->notifyImageResourceVisibility(m_imageResource->cachedImage(), status, screenArea);
return true;
}
void RenderImage::computeIntrinsicRatioInformation(FloatSize& intrinsicSize, double& intrinsicRatio, bool& isPercentageIntrinsicSize) const
{
RenderReplaced::computeIntrinsicRatioInformation(intrinsicSize, intrinsicRatio, isPercentageIntrinsicSize);
if (intrinsicSize.isEmpty() && (m_imageResource->imageHasRelativeWidth() || m_imageResource->imageHasRelativeHeight())) {
RenderObject* containingBlock = isOutOfFlowPositioned() ? container() : this->containingBlock();
if (containingBlock->isBox()) {
RenderBox* box = toRenderBox(containingBlock);
intrinsicSize.setWidth(box->availableLogicalWidth().toFloat());
intrinsicSize.setHeight(box->availableLogicalHeight(IncludeMarginBorderPadding).toFloat());
}
}
if (m_imageResource && m_imageResource->errorOccurred() && !isVideo()) {
intrinsicRatio = 1;
return;
}
}
bool RenderImage::needsPreferredWidthsRecalculation() const
{
if (RenderReplaced::needsPreferredWidthsRecalculation())
return true;
return embeddedContentBox();
}
RenderBox* RenderImage::embeddedContentBox() const
{
if (!m_imageResource)
return 0;
ImageResource* cachedImage = m_imageResource->cachedImage();
if (cachedImage && cachedImage->image() && cachedImage->image()->isSVGImage())
return toSVGImage(cachedImage->image())->embeddedContentBox();
return 0;
}
}