int SVGInlineTextBox::offsetForPositionInFragment(const SVGTextFragment& fragment, float position, bool includePartialGlyphs) const { float scalingFactor = renderer().scalingFactor(); ASSERT(scalingFactor); TextRun textRun = constructTextRun(&renderer().style(), fragment); // Eventually handle lengthAdjust="spacingAndGlyphs". // FIXME: Handle vertical text. AffineTransform fragmentTransform; fragment.buildFragmentTransform(fragmentTransform); if (!fragmentTransform.isIdentity()) textRun.setHorizontalGlyphStretch(narrowPrecisionToFloat(fragmentTransform.xScale())); return fragment.characterOffset - start() + renderer().scaledFont().offsetForPosition(textRun, position * scalingFactor, includePartialGlyphs); }
std::unique_ptr<ImageBuffer> GraphicsContext::createCompatibleBuffer(const FloatSize& size, bool hasAlpha) const { // Make the buffer larger if the context's transform is scaling it so we need a higher // resolution than one pixel per unit. Also set up a corresponding scale factor on the // graphics context. AffineTransform transform = getCTM(DefinitelyIncludeDeviceScale); FloatSize scaledSize(static_cast<int>(ceil(size.width() * transform.xScale())), static_cast<int>(ceil(size.height() * transform.yScale()))); std::unique_ptr<ImageBuffer> buffer = ImageBuffer::createCompatibleBuffer(scaledSize, 1, ColorSpaceSRGB, *this, hasAlpha); if (!buffer) return nullptr; buffer->context().scale(FloatSize(scaledSize.width() / size.width(), scaledSize.height() / size.height())); return buffer; }
static bool rotationOfCharacterCallback(QueryData* queryData, const SVGTextFragment& fragment) { RotationOfCharacterData* data = static_cast<RotationOfCharacterData*>(queryData); int startPosition = data->position; int endPosition = startPosition + 1; if (!mapStartEndPositionsIntoFragmentCoordinates(queryData, fragment, startPosition, endPosition)) return false; if (!fragment.isTransformed()) { data->rotation = 0; } else { AffineTransform fragmentTransform = fragment.buildFragmentTransform(SVGTextFragment::TransformIgnoringTextLength); fragmentTransform.scale(1 / fragmentTransform.xScale(), 1 / fragmentTransform.yScale()); data->rotation = narrowPrecisionToFloat(rad2deg(atan2(fragmentTransform.b(), fragmentTransform.a()))); } return true; }
PassOwnPtr<ImageBuffer> GraphicsContext::createCompatibleBuffer(const IntSize& size) const { // Make the buffer larger if the context's transform is scaling it so we need a higher // resolution than one pixel per unit. Also set up a corresponding scale factor on the // graphics context. AffineTransform transform = getCTM(DefinitelyIncludeDeviceScale); IntSize scaledSize(static_cast<int>(ceil(size.width() * transform.xScale())), static_cast<int>(ceil(size.height() * transform.yScale()))); OwnPtr<ImageBuffer> buffer = ImageBuffer::create(scaledSize, 1, ColorSpaceDeviceRGB, isAcceleratedContext() ? Accelerated : Unaccelerated); if (!buffer) return nullptr; buffer->context()->scale(FloatSize(static_cast<float>(scaledSize.width()) / size.width(), static_cast<float>(scaledSize.height()) / size.height())); return buffer.release(); }
int SVGInlineTextBox::offsetForPositionInFragment(const SVGTextFragment& fragment, FloatWillBeLayoutUnit position, bool includePartialGlyphs) const { LayoutSVGInlineText& textLayoutObject = toLayoutSVGInlineText(this->layoutObject()); float scalingFactor = textLayoutObject.scalingFactor(); ASSERT(scalingFactor); const ComputedStyle& style = textLayoutObject.styleRef(); TextRun textRun = constructTextRun(style, fragment); // Eventually handle lengthAdjust="spacingAndGlyphs". // FIXME: Handle vertical text. AffineTransform fragmentTransform; fragment.buildFragmentTransform(fragmentTransform); if (!fragmentTransform.isIdentity()) textRun.setHorizontalGlyphStretch(narrowPrecisionToFloat(fragmentTransform.xScale())); return fragment.characterOffset - start() + textLayoutObject.scaledFont().offsetForPosition(textRun, position * scalingFactor, includePartialGlyphs); }
bool SVGTextQuery::rotationOfCharacterCallback(Data* queryData, const SVGTextFragment& fragment) const { RotationOfCharacterData* data = static_cast<RotationOfCharacterData*>(queryData); unsigned startPosition = data->position; unsigned endPosition = startPosition + 1; if (!mapStartEndPositionsIntoFragmentCoordinates(queryData, fragment, startPosition, endPosition)) return false; AffineTransform fragmentTransform; fragment.buildFragmentTransform(fragmentTransform, SVGTextFragment::TransformIgnoringTextLength); if (fragmentTransform.isIdentity()) data->rotation = 0; else { fragmentTransform.scale(1 / fragmentTransform.xScale(), 1 / fragmentTransform.yScale()); data->rotation = narrowPrecisionToFloat(rad2deg(atan2(fragmentTransform.b(), fragmentTransform.a()))); } return true; }
int SVGInlineTextBox::offsetForPositionInFragment(const SVGTextFragment& fragment, LayoutUnit position, bool includePartialGlyphs) const { LineLayoutSVGInlineText lineLayoutItem = LineLayoutSVGInlineText(this->lineLayoutItem()); float scalingFactor = lineLayoutItem.scalingFactor(); ASSERT(scalingFactor); const ComputedStyle& style = lineLayoutItem.styleRef(); TextRun textRun = constructTextRun(style, fragment); // Eventually handle lengthAdjust="spacingAndGlyphs". // FIXME: Handle vertical text. if (fragment.isTransformed()) { AffineTransform fragmentTransform = fragment.buildFragmentTransform(); textRun.setHorizontalGlyphStretch(narrowPrecisionToFloat(fragmentTransform.xScale())); } return fragment.characterOffset - start() + lineLayoutItem.scaledFont().offsetForPosition(textRun, position * scalingFactor, includePartialGlyphs); }
static void paintFilteredContent(const LayoutObject& object, GraphicsContext& context, FilterData* filterData) { ASSERT(filterData->m_state == FilterData::ReadyToPaint); ASSERT(filterData->filter->sourceGraphic()); filterData->m_state = FilterData::PaintingFilter; SkiaImageFilterBuilder builder; RefPtr<SkImageFilter> imageFilter = builder.build(filterData->filter->lastEffect(), ColorSpaceDeviceRGB); FloatRect boundaries = filterData->filter->filterRegion(); context.save(); // Clip drawing of filtered image to the minimum required paint rect. FilterEffect* lastEffect = filterData->filter->lastEffect(); context.clipRect(lastEffect->determineAbsolutePaintRect(lastEffect->maxEffectRect())); #ifdef CHECK_CTM_FOR_TRANSFORMED_IMAGEFILTER // TODO: Remove this workaround once skew/rotation support is added in Skia // (https://code.google.com/p/skia/issues/detail?id=3288, crbug.com/446935). // If the CTM contains rotation or shearing, apply the filter to // the unsheared/unrotated matrix, and do the shearing/rotation // as a final pass. AffineTransform ctm = SVGLayoutSupport::deprecatedCalculateTransformToLayer(&object); if (ctm.b() || ctm.c()) { AffineTransform scaleAndTranslate; scaleAndTranslate.translate(ctm.e(), ctm.f()); scaleAndTranslate.scale(ctm.xScale(), ctm.yScale()); ASSERT(scaleAndTranslate.isInvertible()); AffineTransform shearAndRotate = scaleAndTranslate.inverse(); shearAndRotate.multiply(ctm); context.concatCTM(shearAndRotate.inverse()); imageFilter = builder.buildTransform(shearAndRotate, imageFilter.get()); } #endif context.beginLayer(1, SkXfermode::kSrcOver_Mode, &boundaries, ColorFilterNone, imageFilter.get()); context.endLayer(); context.restore(); filterData->m_state = FilterData::ReadyToPaint; }
void GeneratorGeneratedImage::drawPattern(GraphicsContext* destContext, const FloatRect& srcRect, const AffineTransform& patternTransform, const FloatPoint& phase, ColorSpace styleColorSpace, CompositeOperator compositeOp, const FloatRect& destRect) { // Allow the generator to provide visually-equivalent tiling parameters for better performance. IntSize adjustedSize = m_size; FloatRect adjustedSrcRect = srcRect; m_generator->adjustParametersForTiledDrawing(adjustedSize, adjustedSrcRect); // Factor in the destination context's scale to generate at the best resolution AffineTransform destContextCTM = destContext->getCTM(); double xScale = fabs(destContextCTM.xScale()); double yScale = fabs(destContextCTM.yScale()); AffineTransform adjustedPatternCTM = patternTransform; adjustedPatternCTM.scale(1.0 / xScale, 1.0 / yScale); adjustedSrcRect.scale(xScale, yScale); unsigned generatorHash = m_generator->hash(); if (!m_cachedImageBuffer || m_cachedGeneratorHash != generatorHash || m_cachedAdjustedSize != adjustedSize || !destContext->isCompatibleWithBuffer(m_cachedImageBuffer.get())) { // Create a BitmapImage and call drawPattern on it. m_cachedImageBuffer = destContext->createCompatibleBuffer(adjustedSize); if (!m_cachedImageBuffer) return; // Fill with the generated image. GraphicsContext* graphicsContext = m_cachedImageBuffer->context(); graphicsContext->fillRect(FloatRect(FloatPoint(), adjustedSize), *m_generator.get()); m_cachedGeneratorHash = generatorHash; m_cachedAdjustedSize = adjustedSize; } // Tile the image buffer into the context. m_cachedImageBuffer->drawPattern(destContext, adjustedSrcRect, adjustedPatternCTM, phase, styleColorSpace, compositeOp, destRect); m_cacheTimer.restart(); }
static void affineTransformDecompose(const AffineTransform& matrix, double sr[9]) { AffineTransform m(matrix); // Compute scaling factors double sx = matrix.xScale(); double sy = matrix.yScale(); // Compute cross product of transformed unit vectors. If negative, // one axis was flipped. if (m.a() * m.d() - m.c() * m.b() < 0.0) { // Flip axis with minimum unit vector dot product if (m.a() < m.d()) sx = -sx; else sy = -sy; } // Remove scale from matrix m.scale(1.0 / sx, 1.0 / sy); // Compute rotation double angle = atan2(m.b(), m.a()); // Remove rotation from matrix m.rotate(rad2deg(-angle)); // Return results sr[0] = sx; sr[1] = sy; sr[2] = angle; sr[3] = m.a(); sr[4] = m.b(); sr[5] = m.c(); sr[6] = m.d(); sr[7] = m.e(); sr[8] = m.f(); }
void GradientImage::drawPattern(GraphicsContext& destContext, const FloatRect& srcRect, const AffineTransform& patternTransform, const FloatPoint& phase, const FloatSize& spacing, CompositeOperator compositeOp, const FloatRect& destRect, BlendMode blendMode) { // Allow the generator to provide visually-equivalent tiling parameters for better performance. FloatSize adjustedSize = size(); FloatRect adjustedSrcRect = srcRect; m_gradient->adjustParametersForTiledDrawing(adjustedSize, adjustedSrcRect, spacing); // Factor in the destination context's scale to generate at the best resolution AffineTransform destContextCTM = destContext.getCTM(GraphicsContext::DefinitelyIncludeDeviceScale); double xScale = fabs(destContextCTM.xScale()); double yScale = fabs(destContextCTM.yScale()); AffineTransform adjustedPatternCTM = patternTransform; adjustedPatternCTM.scale(1.0 / xScale, 1.0 / yScale); adjustedSrcRect.scale(xScale, yScale); unsigned generatorHash = m_gradient->hash(); if (!m_cachedImageBuffer || m_cachedGeneratorHash != generatorHash || m_cachedAdjustedSize != adjustedSize || !m_cachedImageBuffer->isCompatibleWithContext(destContext)) { m_cachedImageBuffer = ImageBuffer::createCompatibleBuffer(adjustedSize, destContext, m_gradient->hasAlpha()); if (!m_cachedImageBuffer) return; // Fill with the generated image. m_cachedImageBuffer->context().fillRect(FloatRect(FloatPoint(), adjustedSize), *m_gradient); m_cachedGeneratorHash = generatorHash; m_cachedAdjustedSize = adjustedSize; if (destContext.drawLuminanceMask()) m_cachedImageBuffer->convertToLuminanceMask(); } destContext.setDrawLuminanceMask(false); // Tile the image buffer into the context. m_cachedImageBuffer->drawPattern(destContext, adjustedSrcRect, adjustedPatternCTM, phase, spacing, compositeOp, destRect, blendMode); }
float SVGRenderingContext::calculateScreenFontSizeScalingFactor(const RenderObject& renderer) { AffineTransform ctm = calculateTransformationToOutermostCoordinateSystem(renderer); return narrowPrecisionToFloat(sqrt((pow(ctm.xScale(), 2) + pow(ctm.yScale(), 2)) / 2)); }
FloatSize GraphicsContext::scaleFactor() const { AffineTransform transform = getCTM(GraphicsContext::DefinitelyIncludeDeviceScale); return FloatSize(transform.xScale(), transform.yScale()); }
static bool scalesMatch(AffineTransform a, AffineTransform b) { return a.xScale() == b.xScale() && a.yScale() == b.yScale(); }
bool RenderSVGResourceFilter::applyResource(RenderElement& renderer, const RenderStyle&, GraphicsContext*& context, unsigned short resourceMode) { ASSERT(context); ASSERT_UNUSED(resourceMode, resourceMode == ApplyToDefaultMode); if (m_filter.contains(&renderer)) { FilterData* filterData = m_filter.get(&renderer); if (filterData->state == FilterData::PaintingSource || filterData->state == FilterData::Applying) filterData->state = FilterData::CycleDetected; return false; // Already built, or we're in a cycle, or we're marked for removal. Regardless, just do nothing more now. } auto filterData = std::make_unique<FilterData>(); FloatRect targetBoundingBox = renderer.objectBoundingBox(); filterData->boundaries = SVGLengthContext::resolveRectangle<SVGFilterElement>(&filterElement(), filterElement().filterUnits(), targetBoundingBox); if (filterData->boundaries.isEmpty()) return false; // Determine absolute transformation matrix for filter. AffineTransform absoluteTransform; SVGRenderingContext::calculateTransformationToOutermostCoordinateSystem(renderer, absoluteTransform); if (!absoluteTransform.isInvertible()) return false; // Eliminate shear of the absolute transformation matrix, to be able to produce unsheared tile images for feTile. filterData->shearFreeAbsoluteTransform = AffineTransform(absoluteTransform.xScale(), 0, 0, absoluteTransform.yScale(), 0, 0); // Determine absolute boundaries of the filter and the drawing region. FloatRect absoluteFilterBoundaries = filterData->shearFreeAbsoluteTransform.mapRect(filterData->boundaries); filterData->drawingRegion = renderer.strokeBoundingBox(); filterData->drawingRegion.intersect(filterData->boundaries); FloatRect absoluteDrawingRegion = filterData->shearFreeAbsoluteTransform.mapRect(filterData->drawingRegion); // Create the SVGFilter object. bool primitiveBoundingBoxMode = filterElement().primitiveUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX; filterData->filter = SVGFilter::create(filterData->shearFreeAbsoluteTransform, absoluteDrawingRegion, targetBoundingBox, filterData->boundaries, primitiveBoundingBoxMode); // Create all relevant filter primitives. filterData->builder = buildPrimitives(filterData->filter.get()); if (!filterData->builder) return false; // Calculate the scale factor for the use of filterRes. // Also see http://www.w3.org/TR/SVG/filters.html#FilterEffectsRegion FloatSize scale(1, 1); if (filterElement().hasAttribute(SVGNames::filterResAttr)) { scale.setWidth(filterElement().filterResX() / absoluteFilterBoundaries.width()); scale.setHeight(filterElement().filterResY() / absoluteFilterBoundaries.height()); } if (scale.isEmpty()) return false; // Determine scale factor for filter. The size of intermediate ImageBuffers shouldn't be bigger than kMaxFilterSize. FloatRect tempSourceRect = absoluteDrawingRegion; tempSourceRect.scale(scale.width(), scale.height()); fitsInMaximumImageSize(tempSourceRect.size(), scale); // Set the scale level in SVGFilter. filterData->filter->setFilterResolution(scale); static const unsigned maxTotalOfEffectInputs = 100; FilterEffect* lastEffect = filterData->builder->lastEffect(); if (!lastEffect || lastEffect->totalNumberOfEffectInputs() > maxTotalOfEffectInputs) return false; RenderSVGResourceFilterPrimitive::determineFilterPrimitiveSubregion(*lastEffect); FloatRect subRegion = lastEffect->maxEffectRect(); // At least one FilterEffect has a too big image size, // recalculate the effect sizes with new scale factors. if (!fitsInMaximumImageSize(subRegion.size(), scale)) { filterData->filter->setFilterResolution(scale); RenderSVGResourceFilterPrimitive::determineFilterPrimitiveSubregion(*lastEffect); } // If the drawingRegion is empty, we have something like <g filter=".."/>. // Even if the target objectBoundingBox() is empty, we still have to draw the last effect result image in postApplyResource. if (filterData->drawingRegion.isEmpty()) { ASSERT(!m_filter.contains(&renderer)); filterData->savedContext = context; m_filter.set(&renderer, WTF::move(filterData)); return false; } // Change the coordinate transformation applied to the filtered element to reflect the resolution of the filter. AffineTransform effectiveTransform; effectiveTransform.scale(scale.width(), scale.height()); effectiveTransform.multiply(filterData->shearFreeAbsoluteTransform); std::unique_ptr<ImageBuffer> sourceGraphic; RenderingMode renderingMode = renderer.frame().settings().acceleratedFiltersEnabled() ? Accelerated : Unaccelerated; if (!SVGRenderingContext::createImageBuffer(filterData->drawingRegion, effectiveTransform, sourceGraphic, ColorSpaceLinearRGB, renderingMode)) { ASSERT(!m_filter.contains(&renderer)); filterData->savedContext = context; m_filter.set(&renderer, WTF::move(filterData)); return false; } // Set the rendering mode from the page's settings. filterData->filter->setRenderingMode(renderingMode); GraphicsContext* sourceGraphicContext = sourceGraphic->context(); ASSERT(sourceGraphicContext); filterData->sourceGraphicBuffer = WTF::move(sourceGraphic); filterData->savedContext = context; context = sourceGraphicContext; ASSERT(!m_filter.contains(&renderer)); m_filter.set(&renderer, WTF::move(filterData)); return true; }
bool RenderSVGResourceFilter::applyResource(RenderObject* object, RenderStyle*, GraphicsContext*& context, unsigned short resourceMode) { ASSERT(object); ASSERT(context); ASSERT_UNUSED(resourceMode, resourceMode == ApplyToDefaultMode); // Returning false here, to avoid drawings onto the context. We just want to // draw the stored filter output, not the unfiltered object as well. if (m_filter.contains(object)) { FilterData* filterData = m_filter.get(object); if (filterData->builded) return false; delete m_filter.take(object); // Oops, have to rebuild, go through normal code path } OwnPtr<FilterData> filterData(adoptPtr(new FilterData)); FloatRect targetBoundingBox = object->objectBoundingBox(); SVGFilterElement* filterElement = static_cast<SVGFilterElement*>(node()); filterData->boundaries = SVGLengthContext::resolveRectangle<SVGFilterElement>(filterElement, filterElement->filterUnits(), targetBoundingBox); if (filterData->boundaries.isEmpty()) return false; // Determine absolute transformation matrix for filter. AffineTransform absoluteTransform; SVGImageBufferTools::calculateTransformationToOutermostSVGCoordinateSystem(object, absoluteTransform); if (!absoluteTransform.isInvertible()) return false; // Eliminate shear of the absolute transformation matrix, to be able to produce unsheared tile images for feTile. filterData->shearFreeAbsoluteTransform = AffineTransform(absoluteTransform.xScale(), 0, 0, absoluteTransform.yScale(), 0, 0); // Determine absolute boundaries of the filter and the drawing region. FloatRect absoluteFilterBoundaries = filterData->shearFreeAbsoluteTransform.mapRect(filterData->boundaries); FloatRect drawingRegion = object->strokeBoundingBox(); drawingRegion.intersect(filterData->boundaries); FloatRect absoluteDrawingRegion = filterData->shearFreeAbsoluteTransform.mapRect(drawingRegion); // Create the SVGFilter object. bool primitiveBoundingBoxMode = filterElement->primitiveUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX; filterData->filter = SVGFilter::create(filterData->shearFreeAbsoluteTransform, absoluteDrawingRegion, targetBoundingBox, filterData->boundaries, primitiveBoundingBoxMode); // Create all relevant filter primitives. filterData->builder = buildPrimitives(filterData->filter.get()); if (!filterData->builder) return false; // Calculate the scale factor for the use of filterRes. // Also see http://www.w3.org/TR/SVG/filters.html#FilterEffectsRegion FloatSize scale(1, 1); if (filterElement->hasAttribute(SVGNames::filterResAttr)) { scale.setWidth(filterElement->filterResX() / absoluteFilterBoundaries.width()); scale.setHeight(filterElement->filterResY() / absoluteFilterBoundaries.height()); } if (scale.isEmpty()) return false; // Determine scale factor for filter. The size of intermediate ImageBuffers shouldn't be bigger than kMaxFilterSize. FloatRect tempSourceRect = absoluteDrawingRegion; tempSourceRect.scale(scale.width(), scale.height()); fitsInMaximumImageSize(tempSourceRect.size(), scale); // Set the scale level in SVGFilter. filterData->filter->setFilterResolution(scale); FilterEffect* lastEffect = filterData->builder->lastEffect(); if (!lastEffect) return false; RenderSVGResourceFilterPrimitive::determineFilterPrimitiveSubregion(lastEffect); FloatRect subRegion = lastEffect->maxEffectRect(); // At least one FilterEffect has a too big image size, // recalculate the effect sizes with new scale factors. if (!fitsInMaximumImageSize(subRegion.size(), scale)) { filterData->filter->setFilterResolution(scale); RenderSVGResourceFilterPrimitive::determineFilterPrimitiveSubregion(lastEffect); } // If the drawingRegion is empty, we have something like <g filter=".."/>. // Even if the target objectBoundingBox() is empty, we still have to draw the last effect result image in postApplyResource. if (drawingRegion.isEmpty()) { ASSERT(!m_filter.contains(object)); filterData->savedContext = context; m_filter.set(object, filterData.leakPtr()); return false; } // Change the coordinate transformation applied to the filtered element to reflect the resolution of the filter. AffineTransform effectiveTransform; effectiveTransform.scale(scale.width(), scale.height()); effectiveTransform.multiply(filterData->shearFreeAbsoluteTransform); OwnPtr<ImageBuffer> sourceGraphic; RenderingMode renderingMode = object->document()->page()->settings()->acceleratedFiltersEnabled() ? Accelerated : Unaccelerated; if (!SVGImageBufferTools::createImageBuffer(drawingRegion, effectiveTransform, sourceGraphic, ColorSpaceLinearRGB, renderingMode)) { ASSERT(!m_filter.contains(object)); filterData->savedContext = context; m_filter.set(object, filterData.leakPtr()); return false; } // Set the rendering mode from the page's settings. filterData->filter->setRenderingMode(renderingMode); GraphicsContext* sourceGraphicContext = sourceGraphic->context(); ASSERT(sourceGraphicContext); filterData->sourceGraphicBuffer = sourceGraphic.release(); filterData->savedContext = context; context = sourceGraphicContext; ASSERT(!m_filter.contains(object)); m_filter.set(object, filterData.leakPtr()); return true; }
void NativeImageSkia::drawPattern( GraphicsContext* context, const FloatRect& floatSrcRect, const FloatSize& scale, const FloatPoint& phase, CompositeOperator compositeOp, const FloatRect& destRect, WebBlendMode blendMode, const IntSize& repeatSpacing) const { FloatRect normSrcRect = floatSrcRect; normSrcRect.intersect(FloatRect(0, 0, bitmap().width(), bitmap().height())); if (destRect.isEmpty() || normSrcRect.isEmpty()) return; // nothing to draw SkMatrix totalMatrix = context->getTotalMatrix(); AffineTransform ctm = context->getCTM(); SkScalar ctmScaleX = ctm.xScale(); SkScalar ctmScaleY = ctm.yScale(); totalMatrix.preScale(scale.width(), scale.height()); // Figure out what size the bitmap will be in the destination. The // destination rect is the bounds of the pattern, we need to use the // matrix to see how big it will be. SkRect destRectTarget; totalMatrix.mapRect(&destRectTarget, normSrcRect); float destBitmapWidth = SkScalarToFloat(destRectTarget.width()); float destBitmapHeight = SkScalarToFloat(destRectTarget.height()); bool isLazyDecoded = DeferredImageDecoder::isLazyDecoded(bitmap()); // Compute the resampling mode. InterpolationQuality resampling; if (context->isAccelerated()) resampling = InterpolationLow; else if (isLazyDecoded) resampling = InterpolationHigh; else resampling = computeInterpolationQuality(totalMatrix, normSrcRect.width(), normSrcRect.height(), destBitmapWidth, destBitmapHeight, isDataComplete()); resampling = limitInterpolationQuality(context, resampling); SkMatrix localMatrix; // We also need to translate it such that the origin of the pattern is the // origin of the destination rect, which is what WebKit expects. Skia uses // the coordinate system origin as the base for the pattern. If WebKit wants // a shifted image, it will shift it from there using the localMatrix. const float adjustedX = phase.x() + normSrcRect.x() * scale.width(); const float adjustedY = phase.y() + normSrcRect.y() * scale.height(); localMatrix.setTranslate(SkFloatToScalar(adjustedX), SkFloatToScalar(adjustedY)); RefPtr<SkShader> shader; SkFilterQuality filterLevel = static_cast<SkFilterQuality>(resampling); // Bicubic filter is only applied to defer-decoded images, see // NativeImageSkia::draw for details. if (resampling == InterpolationHigh && !isLazyDecoded) { // Do nice resampling. filterLevel = kNone_SkFilterQuality; float scaleX = destBitmapWidth / normSrcRect.width(); float scaleY = destBitmapHeight / normSrcRect.height(); SkRect scaledSrcRect; // Since we are resizing the bitmap, we need to remove the scale // applied to the pixels in the bitmap shader. This means we need // CTM * localMatrix to have identity scale. Since we // can't modify CTM (or the rectangle will be drawn in the wrong // place), we must set localMatrix's scale to the inverse of // CTM scale. localMatrix.preScale(ctmScaleX ? 1 / ctmScaleX : 1, ctmScaleY ? 1 / ctmScaleY : 1); // The image fragment generated here is not exactly what is // requested. The scale factor used is approximated and image // fragment is slightly larger to align to integer // boundaries. SkBitmap resampled = extractScaledImageFragment(normSrcRect, scaleX, scaleY, &scaledSrcRect); if (repeatSpacing.isZero()) { shader = adoptRef(SkShader::CreateBitmapShader(resampled, SkShader::kRepeat_TileMode, SkShader::kRepeat_TileMode, &localMatrix)); } else { shader = adoptRef(SkShader::CreateBitmapShader( createBitmapWithSpace(resampled, repeatSpacing.width() * ctmScaleX, repeatSpacing.height() * ctmScaleY), SkShader::kRepeat_TileMode, SkShader::kRepeat_TileMode, &localMatrix)); } } else { // Because no resizing occurred, the shader transform should be // set to the pattern's transform, which just includes scale. localMatrix.preScale(scale.width(), scale.height()); // No need to resample before drawing. SkBitmap srcSubset; bitmap().extractSubset(&srcSubset, enclosingIntRect(normSrcRect)); if (repeatSpacing.isZero()) { shader = adoptRef(SkShader::CreateBitmapShader(srcSubset, SkShader::kRepeat_TileMode, SkShader::kRepeat_TileMode, &localMatrix)); } else { shader = adoptRef(SkShader::CreateBitmapShader( createBitmapWithSpace(srcSubset, repeatSpacing.width() * ctmScaleX, repeatSpacing.height() * ctmScaleY), SkShader::kRepeat_TileMode, SkShader::kRepeat_TileMode, &localMatrix)); } } SkPaint paint; paint.setShader(shader.get()); paint.setXfermodeMode(WebCoreCompositeToSkiaComposite(compositeOp, blendMode)); paint.setColorFilter(context->colorFilter()); paint.setFilterQuality(filterLevel); context->drawRect(destRect, paint); }