diff options
Diffstat (limited to 'image/OrientedImage.cpp')
-rw-r--r-- | image/OrientedImage.cpp | 349 |
1 files changed, 349 insertions, 0 deletions
diff --git a/image/OrientedImage.cpp b/image/OrientedImage.cpp new file mode 100644 index 0000000000..1e6157facf --- /dev/null +++ b/image/OrientedImage.cpp @@ -0,0 +1,349 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "OrientedImage.h" + +#include <algorithm> + +#include "gfx2DGlue.h" +#include "gfxContext.h" +#include "gfxDrawable.h" +#include "gfxPlatform.h" +#include "gfxUtils.h" +#include "ImageRegion.h" +#include "mozilla/SVGImageContext.h" + +using std::swap; + +namespace mozilla { + +using namespace gfx; + +namespace image { + +NS_IMETHODIMP +OrientedImage::GetWidth(int32_t* aWidth) { + if (mOrientation.SwapsWidthAndHeight()) { + return InnerImage()->GetHeight(aWidth); + } else { + return InnerImage()->GetWidth(aWidth); + } +} + +NS_IMETHODIMP +OrientedImage::GetHeight(int32_t* aHeight) { + if (mOrientation.SwapsWidthAndHeight()) { + return InnerImage()->GetWidth(aHeight); + } else { + return InnerImage()->GetHeight(aHeight); + } +} + +nsresult OrientedImage::GetNativeSizes(nsTArray<IntSize>& aNativeSizes) { + nsresult rv = InnerImage()->GetNativeSizes(aNativeSizes); + + if (mOrientation.SwapsWidthAndHeight()) { + auto i = aNativeSizes.Length(); + while (i > 0) { + --i; + swap(aNativeSizes[i].width, aNativeSizes[i].height); + } + } + + return rv; +} + +NS_IMETHODIMP +OrientedImage::GetIntrinsicSize(nsSize* aSize) { + nsresult rv = InnerImage()->GetIntrinsicSize(aSize); + + if (mOrientation.SwapsWidthAndHeight()) { + swap(aSize->width, aSize->height); + } + + return rv; +} + +Maybe<AspectRatio> OrientedImage::GetIntrinsicRatio() { + Maybe<AspectRatio> ratio = InnerImage()->GetIntrinsicRatio(); + if (ratio && mOrientation.SwapsWidthAndHeight()) { + ratio = Some(ratio->Inverted()); + } + return ratio; +} + +already_AddRefed<SourceSurface> OrientedImage::OrientSurface( + Orientation aOrientation, SourceSurface* aSurface) { + MOZ_ASSERT(aSurface); + + // If the image does not require any re-orientation, return aSurface itself. + if (aOrientation.IsIdentity()) { + return do_AddRef(aSurface); + } + + // Determine the size of the new surface. + nsIntSize originalSize = aSurface->GetSize(); + nsIntSize targetSize = originalSize; + if (aOrientation.SwapsWidthAndHeight()) { + swap(targetSize.width, targetSize.height); + } + + // Create our drawable. + RefPtr<gfxDrawable> drawable = new gfxSurfaceDrawable(aSurface, originalSize); + + // Determine an appropriate format for the surface. + gfx::SurfaceFormat surfaceFormat = IsOpaque(aSurface->GetFormat()) + ? gfx::SurfaceFormat::OS_RGBX + : gfx::SurfaceFormat::OS_RGBA; + + // Create the new surface to draw into. + RefPtr<DrawTarget> target = + gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget( + targetSize, surfaceFormat); + if (!target || !target->IsValid()) { + NS_ERROR("Could not create a DrawTarget"); + return nullptr; + } + + // Draw. + RefPtr<gfxContext> ctx = gfxContext::CreateOrNull(target); + MOZ_ASSERT(ctx); // already checked the draw target above + ctx->Multiply(OrientationMatrix(aOrientation, originalSize)); + gfxUtils::DrawPixelSnapped(ctx, drawable, SizeDouble(originalSize), + ImageRegion::Create(originalSize), surfaceFormat, + SamplingFilter::LINEAR); + + return target->Snapshot(); +} + +NS_IMETHODIMP_(already_AddRefed<SourceSurface>) +OrientedImage::GetFrame(uint32_t aWhichFrame, uint32_t aFlags) { + // Get a SourceSurface for the inner image then orient it according to + // mOrientation. + RefPtr<SourceSurface> innerSurface = + InnerImage()->GetFrame(aWhichFrame, aFlags); + NS_ENSURE_TRUE(innerSurface, nullptr); + + return OrientSurface(mOrientation, innerSurface); +} + +NS_IMETHODIMP_(already_AddRefed<SourceSurface>) +OrientedImage::GetFrameAtSize(const IntSize& aSize, uint32_t aWhichFrame, + uint32_t aFlags) { + // Get a SourceSurface for the inner image then orient it according to + // mOrientation. + IntSize innerSize = aSize; + if (mOrientation.SwapsWidthAndHeight()) { + swap(innerSize.width, innerSize.height); + } + RefPtr<SourceSurface> innerSurface = + InnerImage()->GetFrameAtSize(innerSize, aWhichFrame, aFlags); + NS_ENSURE_TRUE(innerSurface, nullptr); + + return OrientSurface(mOrientation, innerSurface); +} + +NS_IMETHODIMP_(bool) +OrientedImage::IsImageContainerAvailable(WindowRenderer* aRenderer, + uint32_t aFlags) { + if (mOrientation.IsIdentity()) { + return InnerImage()->IsImageContainerAvailable(aRenderer, aFlags); + } + return false; +} + +NS_IMETHODIMP_(ImgDrawResult) +OrientedImage::GetImageProvider(WindowRenderer* aRenderer, + const gfx::IntSize& aSize, + const SVGImageContext& aSVGContext, + const Maybe<ImageIntRegion>& aRegion, + uint32_t aFlags, + WebRenderImageProvider** aProvider) { + // XXX(seth): We currently don't have a way of orienting the result of + // GetImageContainer. We work around this by always returning null, but if it + // ever turns out that OrientedImage is widely used on codepaths that can + // actually benefit from GetImageContainer, it would be a good idea to fix + // that method for performance reasons. + + if (mOrientation.IsIdentity()) { + return InnerImage()->GetImageProvider(aRenderer, aSize, aSVGContext, + aRegion, aFlags, aProvider); + } + + return ImgDrawResult::NOT_SUPPORTED; +} + +struct MatrixBuilder { + explicit MatrixBuilder(bool aInvert) : mInvert(aInvert) {} + + gfxMatrix Build() { return mMatrix; } + + void Scale(gfxFloat aX, gfxFloat aY) { + if (mInvert) { + mMatrix *= gfxMatrix::Scaling(1.0 / aX, 1.0 / aY); + } else { + mMatrix.PreScale(aX, aY); + } + } + + void Rotate(gfxFloat aPhi) { + if (mInvert) { + mMatrix *= gfxMatrix::Rotation(-aPhi); + } else { + mMatrix.PreRotate(aPhi); + } + } + + void Translate(gfxPoint aDelta) { + if (mInvert) { + mMatrix *= gfxMatrix::Translation(-aDelta); + } else { + mMatrix.PreTranslate(aDelta); + } + } + + private: + gfxMatrix mMatrix; + bool mInvert; +}; + +gfxMatrix OrientedImage::OrientationMatrix(Orientation aOrientation, + const nsIntSize& aSize, + bool aInvert /* = false */) { + MatrixBuilder builder(aInvert); + + // Apply reflection, if present. (For a regular, non-flipFirst reflection, + // this logically happens second, but we apply it first because these + // transformations are all premultiplied.) A translation is necessary to place + // the image back in the first quadrant. + if (aOrientation.flip == Flip::Horizontal && !aOrientation.flipFirst) { + if (aOrientation.SwapsWidthAndHeight()) { + builder.Translate(gfxPoint(aSize.height, 0)); + } else { + builder.Translate(gfxPoint(aSize.width, 0)); + } + builder.Scale(-1.0, 1.0); + } + + // Apply rotation, if present. Again, a translation is used to place the + // image back in the first quadrant. + switch (aOrientation.rotation) { + case Angle::D0: + break; + case Angle::D90: + builder.Translate(gfxPoint(aSize.height, 0)); + builder.Rotate(-1.5 * M_PI); + break; + case Angle::D180: + builder.Translate(gfxPoint(aSize.width, aSize.height)); + builder.Rotate(-1.0 * M_PI); + break; + case Angle::D270: + builder.Translate(gfxPoint(0, aSize.width)); + builder.Rotate(-0.5 * M_PI); + break; + default: + MOZ_ASSERT(false, "Invalid rotation value"); + } + + // Apply a flipFirst reflection. + if (aOrientation.flip == Flip::Horizontal && aOrientation.flipFirst) { + builder.Translate(gfxPoint(aSize.width, 0.0)); + builder.Scale(-1.0, 1.0); + } + + return builder.Build(); +} + +NS_IMETHODIMP_(ImgDrawResult) +OrientedImage::Draw(gfxContext* aContext, const nsIntSize& aSize, + const ImageRegion& aRegion, uint32_t aWhichFrame, + SamplingFilter aSamplingFilter, + const SVGImageContext& aSVGContext, uint32_t aFlags, + float aOpacity) { + if (mOrientation.IsIdentity()) { + return InnerImage()->Draw(aContext, aSize, aRegion, aWhichFrame, + aSamplingFilter, aSVGContext, aFlags, aOpacity); + } + + // Update the image size to match the image's coordinate system. (This could + // be done using TransformBounds but since it's only a size a swap is enough.) + nsIntSize size(aSize); + if (mOrientation.SwapsWidthAndHeight()) { + swap(size.width, size.height); + } + + // Update the matrix so that we transform the image into the orientation + // expected by the caller before drawing. + gfxMatrix matrix(OrientationMatrix(size)); + gfxContextMatrixAutoSaveRestore saveMatrix(aContext); + aContext->Multiply(matrix); + + // The region is already in the orientation expected by the caller, but we + // need it to be in the image's coordinate system, so we transform it using + // the inverse of the orientation matrix. + gfxMatrix inverseMatrix(OrientationMatrix(size, /* aInvert = */ true)); + ImageRegion region(aRegion); + region.TransformBoundsBy(inverseMatrix); + + auto orientViewport = [&](const SVGImageContext& aOldContext) { + SVGImageContext context(aOldContext); + auto oldViewport = aOldContext.GetViewportSize(); + if (oldViewport && mOrientation.SwapsWidthAndHeight()) { + // Swap width and height: + CSSIntSize newViewport(oldViewport->height, oldViewport->width); + context.SetViewportSize(Some(newViewport)); + } + return context; + }; + + return InnerImage()->Draw(aContext, size, region, aWhichFrame, + aSamplingFilter, orientViewport(aSVGContext), + aFlags, aOpacity); +} + +nsIntSize OrientedImage::OptimalImageSizeForDest(const gfxSize& aDest, + uint32_t aWhichFrame, + SamplingFilter aSamplingFilter, + uint32_t aFlags) { + if (!mOrientation.SwapsWidthAndHeight()) { + return InnerImage()->OptimalImageSizeForDest(aDest, aWhichFrame, + aSamplingFilter, aFlags); + } + + // Swap the size for the calculation, then swap it back for the caller. + gfxSize destSize(aDest.height, aDest.width); + nsIntSize innerImageSize(InnerImage()->OptimalImageSizeForDest( + destSize, aWhichFrame, aSamplingFilter, aFlags)); + return nsIntSize(innerImageSize.height, innerImageSize.width); +} + +NS_IMETHODIMP_(nsIntRect) +OrientedImage::GetImageSpaceInvalidationRect(const nsIntRect& aRect) { + nsIntRect rect(InnerImage()->GetImageSpaceInvalidationRect(aRect)); + + if (mOrientation.IsIdentity()) { + return rect; + } + + nsIntSize innerSize; + nsresult rv = InnerImage()->GetWidth(&innerSize.width); + rv = NS_FAILED(rv) ? rv : InnerImage()->GetHeight(&innerSize.height); + if (NS_FAILED(rv)) { + // Fall back to identity if the width and height aren't available. + return rect; + } + + // Transform the invalidation rect into the correct orientation. + gfxMatrix matrix(OrientationMatrix(innerSize)); + gfxRect invalidRect(matrix.TransformBounds( + gfxRect(rect.X(), rect.Y(), rect.Width(), rect.Height()))); + + return IntRect::RoundOut(invalidRect.X(), invalidRect.Y(), + invalidRect.Width(), invalidRect.Height()); +} + +} // namespace image +} // namespace mozilla |