void LowLevelGraphicsPostScriptRenderer::drawImage (const Image& sourceImage, const AffineTransform& transform) { const int w = sourceImage.getWidth(); const int h = sourceImage.getHeight(); writeClip(); out << "gsave "; writeTransform (transform.translated ((float) stateStack.getLast()->xOffset, (float) stateStack.getLast()->yOffset) .scaled (1.0f, -1.0f)); RectangleList<int> imageClip; sourceImage.createSolidAreaMask (imageClip, 0.5f); out << "newpath "; int itemsOnLine = 0; for (const Rectangle<int>* i = imageClip.begin(), * const e = imageClip.end(); i != e; ++i) { if (++itemsOnLine == 6) { out << '\n'; itemsOnLine = 0; } out << i->getX() << ' ' << i->getY() << ' ' << i->getWidth() << ' ' << i->getHeight() << " pr "; } out << " clip newpath\n"; out << w << ' ' << h << " scale\n"; out << w << ' ' << h << " 8 [" << w << " 0 0 -" << h << ' ' << (int) 0 << ' ' << h << " ]\n"; writeImage (sourceImage, 0, 0, w, h); out << "false 3 colorimage grestore\n"; needToClip = true; }
TEST(TransparencyWin, DISABLED_TranslateScaleOpaqueCompositeLayer) { OwnPtr<ImageBuffer> src(ImageBuffer::create(IntSize(16, 16), 1)); // The background is white on top with red on bottom. Color white(0xFFFFFFFF); FloatRect topRect(0, 0, 16, 8); src->context()->fillRect(topRect, white); Color red(0xFFFF0000); FloatRect bottomRect(0, 8, 16, 8); src->context()->fillRect(bottomRect, red); src->context()->save(); // Translate left by one pixel. AffineTransform left; left.translate(-1, 0); // Scale by 2x. AffineTransform scale; scale.scale(2.0); src->context()->concatCTM(scale); // Then translate up by one pixel (which will actually be 2 due to scaling). AffineTransform up; up.translate(0, -1); src->context()->concatCTM(up); // Now draw 50% red square. { // Create a transparency helper inset one pixel in the buffer. The // coordinates are before transforming into this space, and maps to // IntRect(1, 1, 14, 14). TransparencyWin helper; helper.init(src->context(), TransparencyWin::OpaqueCompositeLayer, TransparencyWin::KeepTransform, IntRect(1, -15, 14, 14)); // Fill with red. helper.context()->fillRect(helper.drawRect(), Color(0x7f7f0000)); clearTopLayerAlphaChannel(helper.context()); helper.composite(); } }
void drawPatternToCairoContext(cairo_t* cr, cairo_surface_t* image, const IntSize& imageSize, const FloatRect& tileRect, const AffineTransform& patternTransform, const FloatPoint& phase, cairo_operator_t op, const FloatRect& destRect) { // Avoid NaN if (!isfinite(phase.x()) || !isfinite(phase.y())) return; cairo_save(cr); RefPtr<cairo_surface_t> clippedImageSurface = 0; if (tileRect.size() != imageSize) { IntRect imageRect = enclosingIntRect(tileRect); clippedImageSurface = adoptRef(cairo_image_surface_create(CAIRO_FORMAT_ARGB32, imageRect.width(), imageRect.height())); RefPtr<cairo_t> clippedImageContext = adoptRef(cairo_create(clippedImageSurface.get())); cairo_set_source_surface(clippedImageContext.get(), image, -tileRect.x(), -tileRect.y()); cairo_paint(clippedImageContext.get()); image = clippedImageSurface.get(); } cairo_pattern_t* pattern = cairo_pattern_create_for_surface(image); cairo_pattern_set_extend(pattern, CAIRO_EXTEND_REPEAT); cairo_matrix_t patternMatrix = cairo_matrix_t(patternTransform); cairo_matrix_t phaseMatrix = {1, 0, 0, 1, phase.x() + tileRect.x() * patternTransform.a(), phase.y() + tileRect.y() * patternTransform.d()}; cairo_matrix_t combined; cairo_matrix_multiply(&combined, &patternMatrix, &phaseMatrix); cairo_matrix_invert(&combined); cairo_pattern_set_matrix(pattern, &combined); cairo_set_operator(cr, op); cairo_set_source(cr, pattern); cairo_pattern_destroy(pattern); cairo_rectangle(cr, destRect.x(), destRect.y(), destRect.width(), destRect.height()); cairo_fill(cr); cairo_restore(cr); }
PassOwnPtr<ImageBuffer> RenderSVGResourcePattern::createTileImage(const PatternAttributes& attributes, const FloatRect& tileBoundaries, const FloatRect& absoluteTileBoundaries, const AffineTransform& tileImageTransform, FloatRect& clampedAbsoluteTileBoundaries) const { clampedAbsoluteTileBoundaries = SVGImageBufferTools::clampedAbsoluteTargetRect(absoluteTileBoundaries); OwnPtr<ImageBuffer> tileImage; if (!SVGImageBufferTools::createImageBuffer(absoluteTileBoundaries, clampedAbsoluteTileBoundaries, tileImage, ColorSpaceDeviceRGB)) return nullptr; GraphicsContext* tileImageContext = tileImage->context(); ASSERT(tileImageContext); // The image buffer represents the final rendered size, so the content has to be scaled (to avoid pixelation). tileImageContext->scale(FloatSize(clampedAbsoluteTileBoundaries.width() / tileBoundaries.width(), clampedAbsoluteTileBoundaries.height() / tileBoundaries.height())); // Apply tile image transformations. if (!tileImageTransform.isIdentity()) tileImageContext->concatCTM(tileImageTransform); AffineTransform contentTransformation; if (attributes.boundingBoxModeContent()) contentTransformation = tileImageTransform; // Draw the content into the ImageBuffer. for (Node* node = attributes.patternContentElement()->firstChild(); node; node = node->nextSibling()) { if (!node->isSVGElement() || !static_cast<SVGElement*>(node)->isStyled() || !node->renderer()) continue; SVGImageBufferTools::renderSubtreeToImageBuffer(tileImage.get(), node->renderer(), contentTransformation); } return tileImage.release(); }
void AffineTransform::blend(const AffineTransform& from, double progress) { DecomposedType srA, srB; from.decompose(srA); this->decompose(srB); // If x-axis of one is flipped, and y-axis of the other, convert to an unflipped rotation. if ((srA.scaleX < 0 && srB.scaleY < 0) || (srA.scaleY < 0 && srB.scaleX < 0)) { srA.scaleX = -srA.scaleX; srA.scaleY = -srA.scaleY; srA.angle += srA.angle < 0 ? piDouble : -piDouble; } // Don't rotate the long way around. srA.angle = fmod(srA.angle, 2 * piDouble); srB.angle = fmod(srB.angle, 2 * piDouble); if (fabs(srA.angle - srB.angle) > piDouble) { if (srA.angle > srB.angle) srA.angle -= piDouble * 2; else srB.angle -= piDouble * 2; } srA.scaleX += progress * (srB.scaleX - srA.scaleX); srA.scaleY += progress * (srB.scaleY - srA.scaleY); srA.angle += progress * (srB.angle - srA.angle); srA.remainderA += progress * (srB.remainderA - srA.remainderA); srA.remainderB += progress * (srB.remainderB - srA.remainderB); srA.remainderC += progress * (srB.remainderC - srA.remainderC); srA.remainderD += progress * (srB.remainderD - srA.remainderD); srA.translateX += progress * (srB.translateX - srA.translateX); srA.translateY += progress * (srB.translateY - srA.translateY); this->recompose(srA); }
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; }
std::unique_ptr<ImageBuffer> RenderSVGResourcePattern::createTileImage(const PatternAttributes& attributes, const FloatRect& tileBoundaries, const FloatRect& absoluteTileBoundaries, const AffineTransform& tileImageTransform, FloatRect& clampedAbsoluteTileBoundaries) const { clampedAbsoluteTileBoundaries = SVGRenderingContext::clampedAbsoluteTargetRect(absoluteTileBoundaries); std::unique_ptr<ImageBuffer> tileImage; if (!SVGRenderingContext::createImageBufferForPattern(absoluteTileBoundaries, clampedAbsoluteTileBoundaries, tileImage, ColorSpaceDeviceRGB, Unaccelerated)) return nullptr; GraphicsContext* tileImageContext = tileImage->context(); ASSERT(tileImageContext); // The image buffer represents the final rendered size, so the content has to be scaled (to avoid pixelation). tileImageContext->scale(FloatSize(clampedAbsoluteTileBoundaries.width() / tileBoundaries.width(), clampedAbsoluteTileBoundaries.height() / tileBoundaries.height())); // Apply tile image transformations. if (!tileImageTransform.isIdentity()) tileImageContext->concatCTM(tileImageTransform); AffineTransform contentTransformation; if (attributes.patternContentUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) contentTransformation = tileImageTransform; // Draw the content into the ImageBuffer. auto children = childrenOfType<SVGElement>(*attributes.patternContentElement()); for (auto it = children.begin(), end = children.end(); it != end; ++it) { const SVGElement& child = *it; if (!child.renderer()) continue; if (child.renderer()->needsLayout()) return nullptr; SVGRenderingContext::renderSubtreeToImageBuffer(tileImage.get(), *child.renderer(), contentTransformation); } return tileImage; }
void drawImage (const Image& image, const AffineTransform& transform) { renderingTarget->SetTransform (transformToMatrix (transform.followedBy (currentState->transform))); D2D1_SIZE_U size; size.width = image.getWidth(); size.height = image.getHeight(); D2D1_BITMAP_PROPERTIES bp = D2D1::BitmapProperties(); Image img (image.convertedToFormat (Image::ARGB)); Image::BitmapData bd (img, Image::BitmapData::readOnly); bp.pixelFormat = renderingTarget->GetPixelFormat(); bp.pixelFormat.alphaMode = D2D1_ALPHA_MODE_PREMULTIPLIED; { ComSmartPtr <ID2D1Bitmap> tempBitmap; renderingTarget->CreateBitmap (size, bd.data, bd.lineStride, bp, tempBitmap.resetAndGetPointerAddress()); if (tempBitmap != nullptr) renderingTarget->DrawBitmap (tempBitmap); } renderingTarget->SetTransform (D2D1::IdentityMatrix()); }
void Image::drawPattern(GraphicsContext *gc, const FloatRect& srcRect, const AffineTransform& patternTransform, const FloatPoint& phase, ColorSpace, CompositeOperator, const FloatRect& destRect, BlendMode) { JNIEnv* env = WebCore_GetJavaEnv(); if (!gc || gc->paintingDisabled() || srcRect.isEmpty()) { return; } NativeImagePtr currFrame = nativeImageForCurrentFrame(); if (!currFrame) { return; } TransformationMatrix tm = patternTransform.toTransformationMatrix(); static jmethodID mid = env->GetMethodID(PG_GetGraphicsManagerClass(env), "createTransform", "(DDDDDD)Lcom/sun/webkit/graphics/WCTransform;"); ASSERT(mid); JLObject transform(env->CallObjectMethod(PL_GetGraphicsManager(env), mid, tm.a(), tm.b(), tm.c(), tm.d(), tm.e(), tm.f())); ASSERT(transform); CheckAndClearException(env); gc->platformContext()->rq().freeSpace(13 * 4) << (jint)com_sun_webkit_graphics_GraphicsDecoder_DRAWPATTERN << currFrame << srcRect.x() << srcRect.y() << srcRect.width() << srcRect.height() << RQRef::create(transform) << phase.x() << phase.y() << destRect.x() << destRect.y() << destRect.width() << destRect.height(); if (imageObserver()) imageObserver()->didDraw(this); }
SkMatrix affineTransformToSkMatrix(const AffineTransform& source) { SkMatrix result; result.setScaleX(WebCoreDoubleToSkScalar(source.a())); result.setSkewX(WebCoreDoubleToSkScalar(source.c())); result.setTranslateX(WebCoreDoubleToSkScalar(source.e())); result.setScaleY(WebCoreDoubleToSkScalar(source.d())); result.setSkewY(WebCoreDoubleToSkScalar(source.b())); result.setTranslateY(WebCoreDoubleToSkScalar(source.f())); // FIXME: Set perspective properly. result.setPerspX(0); result.setPerspY(0); result.set(SkMatrix::kMPersp2, SK_Scalar1); return result; }
// static void Shader::affineTo4x4(const AffineTransform& transform, float mat[16]) { mat[0] = transform.a(); mat[1] = transform.b(); mat[2] = 0.0f; mat[3] = 0.0f; mat[4] = transform.c(); mat[5] = transform.d(); mat[6] = 0.0f; mat[7] = 0.0f; mat[8] = 0.0f; mat[9] = 0.0f; mat[10] = 1.0f; mat[11] = 0.0f; mat[12] = transform.e(); mat[13] = transform.f(); mat[14] = 0.0f; mat[15] = 1.0f; }
AffineTransform SVGSVGElement::localCoordinateSpaceTransform(SVGLocatable::CTMScope mode) const { AffineTransform viewBoxTransform; if (attributes()->getAttributeItem(SVGNames::viewBoxAttr)) viewBoxTransform = viewBoxToViewTransform(width().value(this), height().value(this)); AffineTransform transform; if (!isOutermostSVG()) transform.translate(x().value(this), y().value(this)); else if (mode == SVGLocatable::ScreenScope) { if (RenderObject* renderer = this->renderer()) { // Translate in our CSS parent coordinate space // FIXME: This doesn't work correctly with CSS transforms. FloatPoint location = renderer->localToAbsolute(FloatPoint(), false, true); // Be careful here! localToAbsolute() includes the x/y offset coming from the viewBoxToViewTransform(), because // RenderSVGRoot::localToBorderBoxTransform() (called through mapLocalToContainer(), called from localToAbsolute()) // also takes the viewBoxToViewTransform() into account, so we have to subtract it here (original cause of bug #27183) transform.translate(location.x() - viewBoxTransform.e(), location.y() - viewBoxTransform.f()); } } return transform.multLeft(viewBoxTransform); }
AffineTransform getTransform() { const float hw = 0.5f * getWidth(); const float hh = 0.5f * getHeight(); AffineTransform t; if (controls.animateRotation.getToggleState()) t = t.rotated (rotation.getValue() * float_Pi * 2.0f); if (controls.animateSize.getToggleState()) t = t.scaled (0.3f + size.getValue() * 2.0f); if (controls.animatePosition.getToggleState()) t = t.translated (hw + hw * (offsetX.getValue() - 0.5f), hh + hh * (offsetY.getValue() - 0.5f)); else t = t.translated (hw, hh); if (controls.animateShear.getToggleState()) t = t.sheared (shear.getValue() * 2.0f - 1.0f, 0.0f); return t; }
void SVGTextRunRenderingContext::drawSVGGlyphs(GraphicsContext* context, const TextRun& run, const SimpleFontData* fontData, const GlyphBuffer& glyphBuffer, int from, int numGlyphs, const FloatPoint& point) const { SVGFontElement* fontElement = 0; SVGFontFaceElement* fontFaceElement = 0; const SVGFontData* svgFontData = svgFontAndFontFaceElementForFontData(fontData, fontFaceElement, fontElement); if (!fontElement || !fontFaceElement) return; // We can only paint SVGFonts if a context is available. RenderSVGResource* activePaintingResource = activePaintingResourceFromRun(run); RenderObject* renderObject = renderObjectFromRun(run); RenderObject* parentRenderObject = firstParentRendererForNonTextNode(renderObject); RenderStyle* parentRenderObjectStyle = 0; ASSERT(renderObject); if (!activePaintingResource) { // TODO: We're only supporting simple filled HTML text so far. RenderSVGResourceSolidColor* solidPaintingResource = RenderSVGResource::sharedSolidPaintingResource(); solidPaintingResource->setColor(context->fillColor()); activePaintingResource = solidPaintingResource; } bool isVerticalText = false; if (parentRenderObject) { parentRenderObjectStyle = parentRenderObject->style(); ASSERT(parentRenderObjectStyle); isVerticalText = parentRenderObjectStyle->svgStyle()->isVerticalWritingMode(); } float scale = scaleEmToUnits(fontData->platformData().size(), fontFaceElement->unitsPerEm()); ASSERT(activePaintingResource); FloatPoint glyphOrigin; glyphOrigin.setX(svgFontData->horizontalOriginX() * scale); glyphOrigin.setY(svgFontData->horizontalOriginY() * scale); FloatPoint currentPoint = point; RenderSVGResourceMode resourceMode = context->textDrawingMode() == TextModeStroke ? ApplyToStrokeMode : ApplyToFillMode; for (int i = 0; i < numGlyphs; ++i) { Glyph glyph = glyphBuffer.glyphAt(from + i); if (!glyph) continue; float advance = glyphBuffer.advanceAt(from + i); SVGGlyph svgGlyph = fontElement->svgGlyphForGlyph(glyph); ASSERT(!svgGlyph.isPartOfLigature); ASSERT(svgGlyph.tableEntry == glyph); SVGGlyphElement::inheritUnspecifiedAttributes(svgGlyph, svgFontData); // FIXME: Support arbitary SVG content as glyph (currently limited to <glyph d="..."> situations). if (svgGlyph.pathData.isEmpty()) { if (isVerticalText) currentPoint.move(0, advance); else currentPoint.move(advance, 0); continue; } context->save(); if (isVerticalText) { glyphOrigin.setX(svgGlyph.verticalOriginX * scale); glyphOrigin.setY(svgGlyph.verticalOriginY * scale); } AffineTransform glyphPathTransform; glyphPathTransform.translate(currentPoint.x() + glyphOrigin.x(), currentPoint.y() + glyphOrigin.y()); glyphPathTransform.scale(scale, -scale); Path glyphPath = svgGlyph.pathData; glyphPath.transform(glyphPathTransform); if (activePaintingResource->applyResource(parentRenderObject, parentRenderObjectStyle, context, resourceMode)) { if (renderObject && renderObject->isSVGInlineText()) { const RenderSVGInlineText* textRenderer = toRenderSVGInlineText(renderObject); context->setStrokeThickness(context->strokeThickness() * textRenderer->scalingFactor()); } activePaintingResource->postApplyResource(parentRenderObject, context, resourceMode, &glyphPath, 0); } context->restore(); if (isVerticalText) currentPoint.move(0, advance); else currentPoint.move(advance, 0); } }
void RenderSVGResourceMasker::createMaskImage(MaskerData* maskerData, const SVGMaskElement* maskElement, RenderObject* object) { FloatRect objectBoundingBox = object->objectBoundingBox(); // Mask rect clipped with clippingBoundingBox and filterBoundingBox as long as they are present. maskerData->maskRect = object->repaintRectInLocalCoordinates(); if (maskerData->maskRect.isEmpty()) { maskerData->emptyMask = true; return; } if (m_maskBoundaries.isEmpty()) calculateMaskContentRepaintRect(); FloatRect repaintRect = m_maskBoundaries; AffineTransform contextTransform; // We need to scale repaintRect for objectBoundingBox to get the drawing area. if (maskElement->maskContentUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { contextTransform.scaleNonUniform(objectBoundingBox.width(), objectBoundingBox.height()); FloatPoint contextAdjustment = repaintRect.location(); repaintRect = contextTransform.mapRect(repaintRect); repaintRect.move(objectBoundingBox.x(), objectBoundingBox.y()); contextTransform.translate(-contextAdjustment.x(), -contextAdjustment.y()); } repaintRect.intersect(maskerData->maskRect); maskerData->maskRect = repaintRect; IntRect maskImageRect = enclosingIntRect(maskerData->maskRect); maskImageRect.setLocation(IntPoint()); // Don't create ImageBuffers with image size of 0 if (maskImageRect.isEmpty()) { maskerData->emptyMask = true; return; } // FIXME: This changes color space to linearRGB, the default color space // for masking operations in SVG. We need a switch for the other color-space // attribute values sRGB, inherit and auto. maskerData->maskImage = ImageBuffer::create(maskImageRect.size(), LinearRGB); if (!maskerData->maskImage) return; GraphicsContext* maskImageContext = maskerData->maskImage->context(); ASSERT(maskImageContext); maskImageContext->save(); if (maskElement->maskContentUnits() == SVGUnitTypes::SVG_UNIT_TYPE_USERSPACEONUSE) maskImageContext->translate(-maskerData->maskRect.x(), -maskerData->maskRect.y()); maskImageContext->concatCTM(contextTransform); // draw the content into the ImageBuffer for (Node* node = maskElement->firstChild(); node; node = node->nextSibling()) { RenderObject* renderer = node->renderer(); if (!node->isSVGElement() || !static_cast<SVGElement*>(node)->isStyled() || !renderer) continue; RenderStyle* style = renderer->style(); if (!style || style->display() == NONE || style->visibility() != VISIBLE) continue; renderSubtreeToImage(maskerData->maskImage.get(), renderer); } maskImageContext->restore(); // create the luminance mask RefPtr<ImageData> imageData(maskerData->maskImage->getUnmultipliedImageData(maskImageRect)); CanvasPixelArray* srcPixelArray(imageData->data()); for (unsigned pixelOffset = 0; pixelOffset < srcPixelArray->length(); pixelOffset += 4) { unsigned char a = srcPixelArray->get(pixelOffset + 3); if (!a) continue; unsigned char r = srcPixelArray->get(pixelOffset); unsigned char g = srcPixelArray->get(pixelOffset + 1); unsigned char b = srcPixelArray->get(pixelOffset + 2); double luma = (r * 0.2125 + g * 0.7154 + b * 0.0721) * ((double)a / 255.0); srcPixelArray->set(pixelOffset + 3, luma); } maskerData->maskImage->putUnmultipliedImageData(imageData.get(), maskImageRect, IntPoint()); }
float SVGRenderingContext::calculateScreenFontSizeScalingFactor(const RenderObject& renderer) { AffineTransform ctm = calculateTransformationToOutermostCoordinateSystem(renderer); return narrowPrecisionToFloat(sqrt((pow(ctm.xScale(), 2) + pow(ctm.yScale(), 2)) / 2)); }
// DragTo void DragSideState::DragTo(BPoint current, uint32 modifiers) { BRect oldBox = fParent->Box(); if (oldBox.Width() == 0.0 || oldBox.Height() == 0.0) return; // TODO: some of this can be combined into less steps BRect newBox = oldBox; fOldTransform.InverseTransform(¤t); switch (fSide) { case LEFT_SIDE: newBox.left = current.x - fOffsetFromSide; break; case RIGHT_SIDE: newBox.right = current.x - fOffsetFromSide; break; case TOP_SIDE: newBox.top = current.y - fOffsetFromSide; break; case BOTTOM_SIDE: newBox.bottom = current.y - fOffsetFromSide; break; } if (!(modifiers & B_SHIFT_KEY)) { // keep the x and y scale the same double xScale = newBox.Width() / oldBox.Width(); double yScale = newBox.Height() / oldBox.Height(); if (modifiers & B_COMMAND_KEY) { xScale = snap_scale(xScale); yScale = snap_scale(yScale); } if (fSide == LEFT_SIDE || fSide == RIGHT_SIDE) yScale = yScale > 0.0 ? fabs(xScale) : -fabs(xScale); else xScale = xScale > 0.0 ? fabs(yScale) : -fabs(yScale); switch (fSide) { case LEFT_SIDE: { newBox.left = oldBox.right - oldBox.Width() * xScale; float middle = (oldBox.top + oldBox.bottom) / 2.0; float newHeight = oldBox.Height() * yScale; newBox.top = middle - newHeight / 2.0; newBox.bottom = middle + newHeight / 2.0; break; } case RIGHT_SIDE: { newBox.right = oldBox.left + oldBox.Width() * xScale; float middle = (oldBox.top + oldBox.bottom) / 2.0; float newHeight = oldBox.Height() * yScale; newBox.top = middle - newHeight / 2.0; newBox.bottom = middle + newHeight / 2.0; break; } case TOP_SIDE: { newBox.top = oldBox.bottom - oldBox.Height() * yScale; float middle = (oldBox.left + oldBox.right) / 2.0; float newWidth = oldBox.Width() * xScale; newBox.left = middle - newWidth / 2.0; newBox.right = middle + newWidth / 2.0; break; } case BOTTOM_SIDE: { newBox.bottom = oldBox.top + oldBox.Height() * yScale; float middle = (oldBox.left + oldBox.right) / 2.0; float newWidth = oldBox.Width() * xScale; newBox.left = middle - newWidth / 2.0; newBox.right = middle + newWidth / 2.0; break; } } } // build a matrix that performs just the // distortion of the box with the opposite // corner of the one being dragged staying fixed AffineTransform s; s.rect_to_rect(oldBox.left, oldBox.top, oldBox.right, oldBox.bottom, newBox.left, newBox.top, newBox.right, newBox.bottom); // construct a transformation that // * excludes the effect of the fParant->Pivot() // * includes the effect of the changed scaling and translation // (see DragCornerState::DragTo() for explaination) AffineTransform t; BPoint pivot(fParent->Pivot()); t.TranslateBy(pivot.x, pivot.y); t.Multiply(s); t.Multiply(fOldTransform); t.TranslateBy(-pivot.x, -pivot.y); // get the translation double translationX; double translationY; t.translation(&translationX, &translationY); // get the scale double newScaleX; double newScaleY; t.scaling(&newScaleX, &newScaleY); // operating on just the affine parameters is much more precise fParent->SetTranslationAndScale(BPoint(translationX, translationY), newScaleX, newScaleY); }
// DragTo void DragCornerState::DragTo(BPoint current, uint32 modifiers) { BRect oldBox = fParent->Box(); if (oldBox.Width() == 0.0 || oldBox.Height() == 0.0) return; // TODO: some of this can be combined into less steps BRect newBox = oldBox; fOldTransform.InverseTransform(¤t); switch (fCorner) { case LEFT_TOP_CORNER: newBox.left = current.x - fOffsetFromCorner.x; newBox.top = current.y - fOffsetFromCorner.y; break; case RIGHT_TOP_CORNER: newBox.right = current.x - fOffsetFromCorner.x; newBox.top = current.y - fOffsetFromCorner.y; break; case LEFT_BOTTOM_CORNER: newBox.left = current.x - fOffsetFromCorner.x; newBox.bottom = current.y - fOffsetFromCorner.y; break; case RIGHT_BOTTOM_CORNER: newBox.right = current.x - fOffsetFromCorner.x; newBox.bottom = current.y - fOffsetFromCorner.y; break; } if (!(modifiers & B_SHIFT_KEY)) { // keep the x and y scale the same double xScale = newBox.Width() / oldBox.Width(); double yScale = newBox.Height() / oldBox.Height(); if (modifiers & B_COMMAND_KEY) { xScale = snap_scale(xScale); yScale = snap_scale(yScale); } if (fabs(xScale) > fabs(yScale)) yScale = yScale > 0.0 ? fabs(xScale) : -fabs(xScale); else xScale = xScale > 0.0 ? fabs(yScale) : -fabs(yScale); switch (fCorner) { case LEFT_TOP_CORNER: newBox.left = oldBox.right - oldBox.Width() * xScale; newBox.top = oldBox.bottom - oldBox.Height() * yScale; break; case RIGHT_TOP_CORNER: newBox.right = oldBox.left + oldBox.Width() * xScale; newBox.top = oldBox.bottom - oldBox.Height() * yScale; break; case LEFT_BOTTOM_CORNER: newBox.left = oldBox.right - oldBox.Width() * xScale; newBox.bottom = oldBox.top + oldBox.Height() * yScale; break; case RIGHT_BOTTOM_CORNER: newBox.right = oldBox.left + oldBox.Width() * xScale; newBox.bottom = oldBox.top + oldBox.Height() * yScale; break; } } // build a matrix that performs just the // distortion of the box with the opposite // corner of the one being dragged staying fixed AffineTransform s; s.rect_to_rect(oldBox.left, oldBox.top, oldBox.right, oldBox.bottom, newBox.left, newBox.top, newBox.right, newBox.bottom); // construct a transformation that // * excludes the effect of the fParant->Pivot() // * includes the effect of the changed scaling and translation AffineTransform t; BPoint pivot(fParent->Pivot()); t.TranslateBy(pivot.x, pivot.y); t.Multiply(s); // at this point, the matrix is // similar/compatible to the original // matrix (fOldTransform), and also // contains the pivot t.Multiply(fOldTransform); // here both matrices are "merged" // -> in effect this means that the // scale and the translation to // keep the object fixed at the corner // opposite to the one being dragged // were transfered to the parent matrix t.TranslateBy(-pivot.x, -pivot.y); // and now the pivot is removed // (see AdvancedTransform::_UpdateMatrix() // for how the pivot is applied) // get the translation double translationX; double translationY; t.translation(&translationX, &translationY); // get the scale double newScaleX; double newScaleY; t.scaling(&newScaleX, &newScaleY); // operating on just the affine parameters is much more precise fParent->SetTranslationAndScale(BPoint(translationX, translationY), newScaleX, newScaleY); }
void SVGInlineTextBox::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset, LayoutUnit, LayoutUnit) { ASSERT(paintInfo.shouldPaintWithinRoot(renderer())); ASSERT(paintInfo.phase == PaintPhaseForeground || paintInfo.phase == PaintPhaseSelection); ASSERT(truncation() == cNoTruncation); if (renderer()->style()->visibility() != VISIBLE) return; // Note: We're explicitely not supporting composition & custom underlines and custom highlighters - unlike InlineTextBox. // If we ever need that for SVG, it's very easy to refactor and reuse the code. RenderObject* parentRenderer = parent()->renderer(); ASSERT(parentRenderer); bool paintSelectedTextOnly = paintInfo.phase == PaintPhaseSelection; bool hasSelection = !parentRenderer->document().printing() && selectionState() != RenderObject::SelectionNone; if (!hasSelection && paintSelectedTextOnly) return; RenderSVGInlineText* textRenderer = toRenderSVGInlineText(this->textRenderer()); ASSERT(textRenderer); if (!textShouldBePainted(textRenderer)) return; RenderStyle* style = parentRenderer->style(); ASSERT(style); paintDocumentMarkers(paintInfo.context, paintOffset, style, textRenderer->scaledFont(), true); const SVGRenderStyle* svgStyle = style->svgStyle(); ASSERT(svgStyle); bool hasFill = svgStyle->hasFill(); bool hasVisibleStroke = svgStyle->hasVisibleStroke(); RenderStyle* selectionStyle = style; if (hasSelection) { selectionStyle = parentRenderer->getCachedPseudoStyle(SELECTION); if (selectionStyle) { const SVGRenderStyle* svgSelectionStyle = selectionStyle->svgStyle(); ASSERT(svgSelectionStyle); if (!hasFill) hasFill = svgSelectionStyle->hasFill(); if (!hasVisibleStroke) hasVisibleStroke = svgSelectionStyle->hasVisibleStroke(); } else selectionStyle = style; } if (textRenderer->frame() && textRenderer->frame()->view() && textRenderer->frame()->view()->paintBehavior() & PaintBehaviorRenderingSVGMask) { hasFill = true; hasVisibleStroke = false; } AffineTransform fragmentTransform; unsigned textFragmentsSize = m_textFragments.size(); for (unsigned i = 0; i < textFragmentsSize; ++i) { SVGTextFragment& fragment = m_textFragments.at(i); ASSERT(!m_paintingResource); GraphicsContextStateSaver stateSaver(*paintInfo.context, false); fragment.buildFragmentTransform(fragmentTransform); if (!fragmentTransform.isIdentity()) { stateSaver.save(); paintInfo.context->concatCTM(fragmentTransform); } // Spec: All text decorations except line-through should be drawn before the text is filled and stroked; thus, the text is rendered on top of these decorations. unsigned decorations = style->textDecorationsInEffect(); if (decorations & TextDecorationUnderline) paintDecoration(paintInfo.context, TextDecorationUnderline, fragment); if (decorations & TextDecorationOverline) paintDecoration(paintInfo.context, TextDecorationOverline, fragment); for (int i = 0; i < 3; i++) { switch (svgStyle->paintOrderType(i)) { case PT_FILL: // Fill text if (hasFill) { m_paintingResourceMode = ApplyToFillMode | ApplyToTextMode; paintText(paintInfo.context, style, selectionStyle, fragment, hasSelection, paintSelectedTextOnly); } break; case PT_STROKE: // Stroke text if (hasVisibleStroke) { m_paintingResourceMode = ApplyToStrokeMode | ApplyToTextMode; paintText(paintInfo.context, style, selectionStyle, fragment, hasSelection, paintSelectedTextOnly); } break; case PT_MARKERS: // Markers don't apply to text break; default: ASSERT_NOT_REACHED(); break; } } // Spec: Line-through should be drawn after the text is filled and stroked; thus, the line-through is rendered on top of the text. if (decorations & TextDecorationLineThrough) paintDecoration(paintInfo.context, TextDecorationLineThrough, fragment); m_paintingResourceMode = ApplyToDefaultMode; } ASSERT(!m_paintingResource); }
static inline void normalizeTransform(AffineTransform& transform) { // Obtain consistent numerical results for the AffineTransform on both 32/64bit platforms. // Tested with SnowLeopard on Core Duo vs. Core 2 Duo. static const float s_floatEpsilon = std::numeric_limits<float>::epsilon(); if (fabs(transform.a() - 1) <= s_floatEpsilon) transform.setA(1); else if (fabs(transform.a() + 1) <= s_floatEpsilon) transform.setA(-1); if (fabs(transform.d() - 1) <= s_floatEpsilon) transform.setD(1); else if (fabs(transform.d() + 1) <= s_floatEpsilon) transform.setD(-1); if (fabs(transform.e()) <= s_floatEpsilon) transform.setE(0); if (fabs(transform.f()) <= s_floatEpsilon) transform.setF(0); }
PatternData* RenderSVGResourcePattern::buildPattern(RenderElement& renderer, unsigned short resourceMode, GraphicsContext& context) { PatternData* currentData = m_patternMap.get(&renderer); if (currentData && currentData->pattern) return currentData; if (m_shouldCollectPatternAttributes) { patternElement().synchronizeAnimatedSVGAttribute(anyQName()); m_attributes = PatternAttributes(); patternElement().collectPatternAttributes(m_attributes); m_shouldCollectPatternAttributes = false; } // If we couldn't determine the pattern content element root, stop here. if (!m_attributes.patternContentElement()) return nullptr; // An empty viewBox disables rendering. if (m_attributes.hasViewBox() && m_attributes.viewBox().isEmpty()) return nullptr; // Compute all necessary transformations to build the tile image & the pattern. FloatRect tileBoundaries; AffineTransform tileImageTransform; if (!buildTileImageTransform(renderer, m_attributes, patternElement(), tileBoundaries, tileImageTransform)) return nullptr; AffineTransform absoluteTransformIgnoringRotation = SVGRenderingContext::calculateTransformationToOutermostCoordinateSystem(renderer); // Ignore 2D rotation, as it doesn't affect the size of the tile. SVGRenderingContext::clear2DRotation(absoluteTransformIgnoringRotation); FloatRect absoluteTileBoundaries = absoluteTransformIgnoringRotation.mapRect(tileBoundaries); FloatRect clampedAbsoluteTileBoundaries; // Scale the tile size to match the scale level of the patternTransform. absoluteTileBoundaries.scale(static_cast<float>(m_attributes.patternTransform().xScale()), static_cast<float>(m_attributes.patternTransform().yScale())); // Build tile image. auto tileImage = createTileImage(m_attributes, tileBoundaries, absoluteTileBoundaries, tileImageTransform, clampedAbsoluteTileBoundaries, context.isAcceleratedContext() ? Accelerated : Unaccelerated); if (!tileImage) return nullptr; RefPtr<Image> copiedImage = tileImage->copyImage(CopyBackingStore); if (!copiedImage) return nullptr; // Build pattern. auto patternData = std::make_unique<PatternData>(); patternData->pattern = Pattern::create(copiedImage, true, true); // Compute pattern space transformation. const IntSize tileImageSize = tileImage->logicalSize(); patternData->transform.translate(tileBoundaries.x(), tileBoundaries.y()); patternData->transform.scale(tileBoundaries.width() / tileImageSize.width(), tileBoundaries.height() / tileImageSize.height()); AffineTransform patternTransform = m_attributes.patternTransform(); if (!patternTransform.isIdentity()) patternData->transform = patternTransform * patternData->transform; // Account for text drawing resetting the context to non-scaled, see SVGInlineTextBox::paintTextWithShadows. if (resourceMode & ApplyToTextMode) { AffineTransform additionalTextTransformation; if (shouldTransformOnTextPainting(renderer, additionalTextTransformation)) patternData->transform *= additionalTextTransformation; } patternData->pattern->setPatternSpaceTransform(patternData->transform); // Various calls above may trigger invalidations in some fringe cases (ImageBuffer allocation // failures in the SVG image cache for example). To avoid having our PatternData deleted by // removeAllClientsFromCache(), we only make it visible in the cache at the very end. return m_patternMap.set(&renderer, WTF::move(patternData)).iterator->value.get(); }
PassOwnPtr<ImageBuffer> RenderSVGResourcePattern::createTileImage(PatternData* patternData, const SVGPatternElement* patternElement, RenderObject* object) const { PatternAttributes attributes = patternElement->collectPatternProperties(); // If we couldn't determine the pattern content element root, stop here. if (!attributes.patternContentElement()) return 0; FloatRect objectBoundingBox = object->objectBoundingBox(); FloatRect patternBoundaries = calculatePatternBoundaries(attributes, objectBoundingBox, patternElement); AffineTransform patternTransform = attributes.patternTransform(); AffineTransform viewBoxCTM = patternElement->viewBoxToViewTransform(patternElement->viewBox(), patternElement->preserveAspectRatio(), patternBoundaries.width(), patternBoundaries.height()); FloatRect patternBoundariesIncludingOverflow = calculatePatternBoundariesIncludingOverflow(attributes, objectBoundingBox, viewBoxCTM, patternBoundaries); IntSize imageSize(lroundf(patternBoundariesIncludingOverflow.width()), lroundf(patternBoundariesIncludingOverflow.height())); // FIXME: We should be able to clip this more, needs investigation clampImageBufferSizeToViewport(object->document()->view(), imageSize); // Don't create ImageBuffers with image size of 0 if (imageSize.isEmpty()) return 0; OwnPtr<ImageBuffer> tileImage = ImageBuffer::create(imageSize); GraphicsContext* context = tileImage->context(); ASSERT(context); context->save(); // Translate to pattern start origin if (patternBoundariesIncludingOverflow.location() != patternBoundaries.location()) { context->translate(patternBoundaries.x() - patternBoundariesIncludingOverflow.x(), patternBoundaries.y() - patternBoundariesIncludingOverflow.y()); patternBoundaries.setLocation(patternBoundariesIncludingOverflow.location()); } // Process viewBox or boundingBoxModeContent correction if (!viewBoxCTM.isIdentity()) context->concatCTM(viewBoxCTM); else if (attributes.boundingBoxModeContent()) { context->translate(objectBoundingBox.x(), objectBoundingBox.y()); context->scale(FloatSize(objectBoundingBox.width(), objectBoundingBox.height())); } // Render subtree into ImageBuffer for (Node* node = attributes.patternContentElement()->firstChild(); node; node = node->nextSibling()) { if (!node->isSVGElement() || !static_cast<SVGElement*>(node)->isStyled() || !node->renderer()) continue; renderSubtreeToImage(tileImage.get(), node->renderer()); } patternData->boundaries = patternBoundaries; // Compute pattern transformation patternData->transform.translate(patternBoundaries.x(), patternBoundaries.y()); patternData->transform.multiply(patternTransform); context->restore(); return tileImage.release(); }
bool SVGClipPainter::prepareEffect(const LayoutObject& target, const FloatRect& targetBoundingBox, const FloatRect& paintInvalidationRect, GraphicsContext* context, ClipperState& clipperState) { ASSERT(context); ASSERT(clipperState == ClipperNotApplied); ASSERT_WITH_SECURITY_IMPLICATION(!m_clip.needsLayout()); m_clip.clearInvalidationMask(); if (paintInvalidationRect.isEmpty() || m_clip.hasCycle()) return false; SVGClipExpansionCycleHelper inClipExpansionChange(m_clip); AffineTransform animatedLocalTransform = toSVGClipPathElement(m_clip.element())->calculateAnimatedLocalTransform(); // When drawing a clip for non-SVG elements, the CTM does not include the zoom factor. // In this case, we need to apply the zoom scale explicitly - but only for clips with // userSpaceOnUse units (the zoom is accounted for objectBoundingBox-resolved lengths). if (!target.isSVG() && m_clip.clipPathUnits() == SVGUnitTypes::SVG_UNIT_TYPE_USERSPACEONUSE) { ASSERT(m_clip.style()); animatedLocalTransform.scale(m_clip.style()->effectiveZoom()); } // First, try to apply the clip as a clipPath. Path clipPath; if (m_clip.asPath(animatedLocalTransform, targetBoundingBox, clipPath)) { clipperState = ClipperAppliedPath; ASSERT(context->displayItemList()); context->displayItemList()->createAndAppend<BeginClipPathDisplayItem>(target, clipPath); return true; } // Fall back to masking. clipperState = ClipperAppliedMask; // Begin compositing the clip mask. CompositingRecorder::beginCompositing(*context, target, SkXfermode::kSrcOver_Mode, 1, &paintInvalidationRect); { TransformRecorder recorder(*context, target, animatedLocalTransform); // clipPath can also be clipped by another clipPath. SVGResources* resources = SVGResourcesCache::cachedResourcesForLayoutObject(&m_clip); LayoutSVGResourceClipper* clipPathClipper = resources ? resources->clipper() : 0; ClipperState clipPathClipperState = ClipperNotApplied; if (clipPathClipper && !SVGClipPainter(*clipPathClipper).prepareEffect(m_clip, targetBoundingBox, paintInvalidationRect, context, clipPathClipperState)) { // End the clip mask's compositor. CompositingRecorder::endCompositing(*context, target); return false; } drawClipMaskContent(context, target, targetBoundingBox, paintInvalidationRect); if (clipPathClipper) SVGClipPainter(*clipPathClipper).finishEffect(m_clip, context, clipPathClipperState); } // Masked content layer start. CompositingRecorder::beginCompositing(*context, target, SkXfermode::kSrcIn_Mode, 1, &paintInvalidationRect); return true; }
void CanvasRenderingContext2DState::setTransform(const AffineTransform& transform) { m_isTransformInvertible = transform.isInvertible(); m_transform = transform; }
void SimilarityTransform::applyToImage(const IplImage * sourceIm, IplImage * destIm) const { AffineTransform * pAT = getAffineTransform(); pAT->applyToImage(sourceIm, destIm); delete pAT; }
FloatPoint FloatPoint::matrixTransform(const AffineTransform& transform) const { double newX, newY; transform.map(static_cast<double>(m_x), static_cast<double>(m_y), newX, newY); return narrowPrecision(newX, newY); }
void SimilarityTransform::applyToPoints(const CvMat * positions, CvMat * newPositions) const { AffineTransform * pAT = getAffineTransform(); pAT->applyToPoints(positions, newPositions); delete pAT; }
PassRefPtr<const SkPicture> LayoutSVGResourceClipper::createContentPicture(AffineTransform& contentTransformation, const FloatRect& targetBoundingBox, GraphicsContext& context) { ASSERT(frame()); if (clipPathUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { contentTransformation.translate(targetBoundingBox.x(), targetBoundingBox.y()); contentTransformation.scaleNonUniform(targetBoundingBox.width(), targetBoundingBox.height()); } if (m_clipContentPicture) return m_clipContentPicture; SubtreeContentTransformScope contentTransformScope(contentTransformation); // Using strokeBoundingBox (instead of paintInvalidationRectInLocalCoordinates) to avoid the intersection // with local clips/mask, which may yield incorrect results when mixing objectBoundingBox and // userSpaceOnUse units (http://crbug.com/294900). FloatRect bounds = strokeBoundingBox(); SkPictureBuilder pictureBuilder(bounds, nullptr, &context); for (SVGElement* childElement = Traversal<SVGElement>::firstChild(*element()); childElement; childElement = Traversal<SVGElement>::nextSibling(*childElement)) { LayoutObject* layoutObject = childElement->layoutObject(); if (!layoutObject) continue; const ComputedStyle* style = layoutObject->style(); if (!style || style->display() == NONE || style->visibility() != VISIBLE) continue; bool isUseElement = isSVGUseElement(*childElement); if (isUseElement) { const SVGGraphicsElement* clippingElement = toSVGUseElement(*childElement).targetGraphicsElementForClipping(); if (!clippingElement) continue; layoutObject = clippingElement->layoutObject(); if (!layoutObject) continue; } // Only shapes, paths and texts are allowed for clipping. if (!layoutObject->isSVGShape() && !layoutObject->isSVGText()) continue; if (isUseElement) layoutObject = childElement->layoutObject(); // Switch to a paint behavior where all children of this <clipPath> will be laid out using special constraints: // - fill-opacity/stroke-opacity/opacity set to 1 // - masker/filter not applied when laying out the children // - fill is set to the initial fill paint server (solid, black) // - stroke is set to the initial stroke paint server (none) PaintInfo info(pictureBuilder.context(), LayoutRect::infiniteIntRect(), PaintPhaseForeground, GlobalPaintNormalPhase, PaintLayerPaintingRenderingClipPathAsMask); layoutObject->paint(info, IntPoint()); } m_clipContentPicture = pictureBuilder.endRecording(); return m_clipContentPicture; }
bool SVGRenderSupport::paintInfoIntersectsRepaintRect(const FloatRect& localRepaintRect, const AffineTransform& localTransform, const PaintInfo& paintInfo) { return localTransform.mapRect(localRepaintRect).intersects(paintInfo.rect); }
AffineTransform SVGPreserveAspectRatio::getCTM(float logicalX, float logicalY, float logicalWidth, float logicalHeight, float physicalWidth, float physicalHeight) const { AffineTransform transform; if (m_align == SVG_PRESERVEASPECTRATIO_UNKNOWN) return transform; double extendedLogicalX = logicalX; double extendedLogicalY = logicalY; double extendedLogicalWidth = logicalWidth; double extendedLogicalHeight = logicalHeight; double extendedPhysicalWidth = physicalWidth; double extendedPhysicalHeight = physicalHeight; double logicalRatio = extendedLogicalWidth / extendedLogicalHeight; double physicalRatio = extendedPhysicalWidth / extendedPhysicalHeight; if (m_align == SVG_PRESERVEASPECTRATIO_NONE) { transform.scaleNonUniform(extendedPhysicalWidth / extendedLogicalWidth, extendedPhysicalHeight / extendedLogicalHeight); transform.translate(-extendedLogicalX, -extendedLogicalY); return transform; } if ((logicalRatio < physicalRatio && (m_meetOrSlice == SVG_MEETORSLICE_MEET)) || (logicalRatio >= physicalRatio && (m_meetOrSlice == SVG_MEETORSLICE_SLICE))) { transform.scaleNonUniform(extendedPhysicalHeight / extendedLogicalHeight, extendedPhysicalHeight / extendedLogicalHeight); if (m_align == SVG_PRESERVEASPECTRATIO_XMINYMIN || m_align == SVG_PRESERVEASPECTRATIO_XMINYMID || m_align == SVG_PRESERVEASPECTRATIO_XMINYMAX) transform.translate(-extendedLogicalX, -extendedLogicalY); else if (m_align == SVG_PRESERVEASPECTRATIO_XMIDYMIN || m_align == SVG_PRESERVEASPECTRATIO_XMIDYMID || m_align == SVG_PRESERVEASPECTRATIO_XMIDYMAX) transform.translate(-extendedLogicalX - (extendedLogicalWidth - extendedPhysicalWidth * extendedLogicalHeight / extendedPhysicalHeight) / 2, -extendedLogicalY); else transform.translate(-extendedLogicalX - (extendedLogicalWidth - extendedPhysicalWidth * extendedLogicalHeight / extendedPhysicalHeight), -extendedLogicalY); return transform; } transform.scaleNonUniform(extendedPhysicalWidth / extendedLogicalWidth, extendedPhysicalWidth / extendedLogicalWidth); if (m_align == SVG_PRESERVEASPECTRATIO_XMINYMIN || m_align == SVG_PRESERVEASPECTRATIO_XMIDYMIN || m_align == SVG_PRESERVEASPECTRATIO_XMAXYMIN) transform.translate(-extendedLogicalX, -extendedLogicalY); else if (m_align == SVG_PRESERVEASPECTRATIO_XMINYMID || m_align == SVG_PRESERVEASPECTRATIO_XMIDYMID || m_align == SVG_PRESERVEASPECTRATIO_XMAXYMID) transform.translate(-extendedLogicalX, -extendedLogicalY - (extendedLogicalHeight - extendedPhysicalHeight * extendedLogicalWidth / extendedPhysicalWidth) / 2); else transform.translate(-extendedLogicalX, -extendedLogicalY - (extendedLogicalHeight - extendedPhysicalHeight * extendedLogicalWidth / extendedPhysicalWidth)); return transform; }