summaryrefslogtreecommitdiffstats
path: root/image/OrientedImage.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'image/OrientedImage.cpp')
-rw-r--r--image/OrientedImage.cpp349
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