1// Copyright 2014 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "config.h" 6#include "core/paint/ImagePainter.h" 7 8#include "core/dom/Document.h" 9#include "core/dom/Element.h" 10#include "core/editing/FrameSelection.h" 11#include "core/frame/LocalFrame.h" 12#include "core/html/HTMLAreaElement.h" 13#include "core/html/HTMLImageElement.h" 14#include "core/inspector/InspectorInstrumentation.h" 15#include "core/page/Page.h" 16#include "core/paint/BoxPainter.h" 17#include "core/rendering/PaintInfo.h" 18#include "core/rendering/RenderImage.h" 19#include "core/rendering/RenderReplaced.h" 20#include "core/rendering/TextRunConstructor.h" 21#include "platform/geometry/LayoutPoint.h" 22#include "platform/graphics/GraphicsContextStateSaver.h" 23#include "platform/graphics/Path.h" 24 25namespace blink { 26 27void ImagePainter::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset) 28{ 29 m_renderImage.RenderReplaced::paint(paintInfo, paintOffset); 30 31 if (paintInfo.phase == PaintPhaseOutline) 32 paintAreaElementFocusRing(paintInfo); 33} 34 35void ImagePainter::paintAreaElementFocusRing(PaintInfo& paintInfo) 36{ 37 Document& document = m_renderImage.document(); 38 39 if (document.printing() || !document.frame()->selection().isFocusedAndActive()) 40 return; 41 42 Element* focusedElement = document.focusedElement(); 43 if (!isHTMLAreaElement(focusedElement)) 44 return; 45 46 HTMLAreaElement& areaElement = toHTMLAreaElement(*focusedElement); 47 if (areaElement.imageElement() != m_renderImage.node()) 48 return; 49 50 // Even if the theme handles focus ring drawing for entire elements, it won't do it for 51 // an area within an image, so we don't call RenderTheme::supportsFocusRing here. 52 53 Path path = areaElement.computePath(&m_renderImage); 54 if (path.isEmpty()) 55 return; 56 57 RenderStyle* areaElementStyle = areaElement.computedStyle(); 58 unsigned short outlineWidth = areaElementStyle->outlineWidth(); 59 if (!outlineWidth) 60 return; 61 62 // FIXME: Clip path instead of context when Skia pathops is ready. 63 // https://crbug.com/251206 64 GraphicsContextStateSaver savedContext(*paintInfo.context); 65 paintInfo.context->clip(m_renderImage.absoluteContentBox()); 66 paintInfo.context->drawFocusRing(path, outlineWidth, 67 areaElementStyle->outlineOffset(), 68 m_renderImage.resolveColor(areaElementStyle, CSSPropertyOutlineColor)); 69} 70 71void ImagePainter::paintReplaced(PaintInfo& paintInfo, const LayoutPoint& paintOffset) 72{ 73 LayoutUnit cWidth = m_renderImage.contentWidth(); 74 LayoutUnit cHeight = m_renderImage.contentHeight(); 75 76 GraphicsContext* context = paintInfo.context; 77 78 if (!m_renderImage.imageResource()->hasImage() || m_renderImage.imageResource()->errorOccurred()) { 79 if (paintInfo.phase == PaintPhaseSelection) 80 return; 81 82 if (cWidth > 2 && cHeight > 2) { 83 const int borderWidth = 1; 84 85 LayoutUnit leftBorder = m_renderImage.borderLeft(); 86 LayoutUnit topBorder = m_renderImage.borderTop(); 87 LayoutUnit leftPad = m_renderImage.paddingLeft(); 88 LayoutUnit topPad = m_renderImage.paddingTop(); 89 90 // Draw an outline rect where the image should be. 91 context->setStrokeStyle(SolidStroke); 92 context->setStrokeColor(Color::lightGray); 93 context->setFillColor(Color::transparent); 94 context->drawRect(pixelSnappedIntRect(LayoutRect(paintOffset.x() + leftBorder + leftPad, paintOffset.y() + topBorder + topPad, cWidth, cHeight))); 95 96 bool errorPictureDrawn = false; 97 LayoutSize imageOffset; 98 // When calculating the usable dimensions, exclude the pixels of 99 // the ouline rect so the error image/alt text doesn't draw on it. 100 LayoutUnit usableWidth = cWidth - 2 * borderWidth; 101 LayoutUnit usableHeight = cHeight - 2 * borderWidth; 102 103 RefPtr<Image> image = m_renderImage.imageResource()->image(); 104 105 if (m_renderImage.imageResource()->errorOccurred() && !image->isNull() && usableWidth >= image->width() && usableHeight >= image->height()) { 106 float deviceScaleFactor = blink::deviceScaleFactor(m_renderImage.frame()); 107 // Call brokenImage() explicitly to ensure we get the broken image icon at the appropriate resolution. 108 pair<Image*, float> brokenImageAndImageScaleFactor = ImageResource::brokenImage(deviceScaleFactor); 109 image = brokenImageAndImageScaleFactor.first; 110 IntSize imageSize = image->size(); 111 imageSize.scale(1 / brokenImageAndImageScaleFactor.second); 112 // Center the error image, accounting for border and padding. 113 LayoutUnit centerX = (usableWidth - imageSize.width()) / 2; 114 if (centerX < 0) 115 centerX = 0; 116 LayoutUnit centerY = (usableHeight - imageSize.height()) / 2; 117 if (centerY < 0) 118 centerY = 0; 119 imageOffset = LayoutSize(leftBorder + leftPad + centerX + borderWidth, topBorder + topPad + centerY + borderWidth); 120 context->drawImage(image.get(), pixelSnappedIntRect(LayoutRect(paintOffset + imageOffset, imageSize)), CompositeSourceOver, m_renderImage.shouldRespectImageOrientation()); 121 errorPictureDrawn = true; 122 } 123 124 if (!m_renderImage.altText().isEmpty()) { 125 const Font& font = m_renderImage.style()->font(); 126 const FontMetrics& fontMetrics = font.fontMetrics(); 127 LayoutUnit ascent = fontMetrics.ascent(); 128 LayoutPoint textRectOrigin = paintOffset; 129 textRectOrigin.move(leftBorder + leftPad + (RenderImage::paddingWidth / 2) - borderWidth, topBorder + topPad + (RenderImage::paddingHeight / 2) - borderWidth); 130 LayoutPoint textOrigin(textRectOrigin.x(), textRectOrigin.y() + ascent); 131 132 // Only draw the alt text if it'll fit within the content box, 133 // and only if it fits above the error image. 134 TextRun textRun = constructTextRun(&m_renderImage, font, m_renderImage.altText(), m_renderImage.style(), TextRun::AllowTrailingExpansion | TextRun::ForbidLeadingExpansion, DefaultTextRunFlags | RespectDirection); 135 float textWidth = font.width(textRun); 136 TextRunPaintInfo textRunPaintInfo(textRun); 137 textRunPaintInfo.bounds = FloatRect(textRectOrigin, FloatSize(textWidth, fontMetrics.height())); 138 context->setFillColor(m_renderImage.resolveColor(CSSPropertyColor)); 139 if (textRun.direction() == RTL) { 140 int availableWidth = cWidth - static_cast<int>(RenderImage::paddingWidth); 141 textOrigin.move(availableWidth - ceilf(textWidth), 0); 142 } 143 if (errorPictureDrawn) { 144 if (usableWidth >= textWidth && fontMetrics.height() <= imageOffset.height()) 145 context->drawBidiText(font, textRunPaintInfo, textOrigin); 146 } else if (usableWidth >= textWidth && usableHeight >= fontMetrics.height()) { 147 context->drawBidiText(font, textRunPaintInfo, textOrigin); 148 } 149 } 150 } 151 } else if (m_renderImage.imageResource()->hasImage() && cWidth > 0 && cHeight > 0) { 152 LayoutRect contentRect = m_renderImage.contentBoxRect(); 153 contentRect.moveBy(paintOffset); 154 LayoutRect paintRect = m_renderImage.replacedContentRect(); 155 paintRect.moveBy(paintOffset); 156 bool clip = !contentRect.contains(paintRect); 157 if (clip) { 158 context->save(); 159 context->clip(contentRect); 160 } 161 162 paintIntoRect(context, paintRect); 163 164 if (clip) 165 context->restore(); 166 } 167} 168 169void ImagePainter::paintIntoRect(GraphicsContext* context, const LayoutRect& rect) 170{ 171 IntRect alignedRect = pixelSnappedIntRect(rect); 172 if (!m_renderImage.imageResource()->hasImage() || m_renderImage.imageResource()->errorOccurred() || alignedRect.width() <= 0 || alignedRect.height() <= 0) 173 return; 174 175 RefPtr<Image> img = m_renderImage.imageResource()->image(alignedRect.width(), alignedRect.height()); 176 if (!img || img->isNull()) 177 return; 178 179 HTMLImageElement* imageElt = isHTMLImageElement(m_renderImage.node()) ? toHTMLImageElement(m_renderImage.node()) : 0; 180 CompositeOperator compositeOperator = imageElt ? imageElt->compositeOperator() : CompositeSourceOver; 181 Image* image = img.get(); 182 InterpolationQuality interpolationQuality = BoxPainter::chooseInterpolationQuality(m_renderImage, context, image, image, alignedRect.size()); 183 184 TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), "PaintImage", "data", InspectorPaintImageEvent::data(m_renderImage)); 185 // FIXME(361045): remove InspectorInstrumentation calls once DevTools Timeline migrates to tracing. 186 InspectorInstrumentation::willPaintImage(&m_renderImage); 187 InterpolationQuality previousInterpolationQuality = context->imageInterpolationQuality(); 188 context->setImageInterpolationQuality(interpolationQuality); 189 context->drawImage(image, alignedRect, compositeOperator, m_renderImage.shouldRespectImageOrientation()); 190 context->setImageInterpolationQuality(previousInterpolationQuality); 191 InspectorInstrumentation::didPaintImage(&m_renderImage); 192} 193 194} // namespace blink 195