1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 #include "OrientedImage.h"
7 
8 #include <algorithm>
9 
10 #include "gfx2DGlue.h"
11 #include "gfxContext.h"
12 #include "gfxDrawable.h"
13 #include "gfxPlatform.h"
14 #include "gfxUtils.h"
15 #include "ImageRegion.h"
16 #include "mozilla/SVGImageContext.h"
17 
18 using std::swap;
19 
20 namespace mozilla {
21 
22 using namespace gfx;
23 using layers::ImageContainer;
24 using layers::LayerManager;
25 
26 namespace image {
27 
28 NS_IMETHODIMP
GetWidth(int32_t * aWidth)29 OrientedImage::GetWidth(int32_t* aWidth) {
30   if (mOrientation.SwapsWidthAndHeight()) {
31     return InnerImage()->GetHeight(aWidth);
32   } else {
33     return InnerImage()->GetWidth(aWidth);
34   }
35 }
36 
37 NS_IMETHODIMP
GetHeight(int32_t * aHeight)38 OrientedImage::GetHeight(int32_t* aHeight) {
39   if (mOrientation.SwapsWidthAndHeight()) {
40     return InnerImage()->GetWidth(aHeight);
41   } else {
42     return InnerImage()->GetHeight(aHeight);
43   }
44 }
45 
GetNativeSizes(nsTArray<IntSize> & aNativeSizes) const46 nsresult OrientedImage::GetNativeSizes(nsTArray<IntSize>& aNativeSizes) const {
47   nsresult rv = InnerImage()->GetNativeSizes(aNativeSizes);
48 
49   if (mOrientation.SwapsWidthAndHeight()) {
50     auto i = aNativeSizes.Length();
51     while (i > 0) {
52       --i;
53       swap(aNativeSizes[i].width, aNativeSizes[i].height);
54     }
55   }
56 
57   return rv;
58 }
59 
60 NS_IMETHODIMP
GetIntrinsicSize(nsSize * aSize)61 OrientedImage::GetIntrinsicSize(nsSize* aSize) {
62   nsresult rv = InnerImage()->GetIntrinsicSize(aSize);
63 
64   if (mOrientation.SwapsWidthAndHeight()) {
65     swap(aSize->width, aSize->height);
66   }
67 
68   return rv;
69 }
70 
GetIntrinsicRatio()71 Maybe<AspectRatio> OrientedImage::GetIntrinsicRatio() {
72   Maybe<AspectRatio> ratio = InnerImage()->GetIntrinsicRatio();
73   if (ratio && mOrientation.SwapsWidthAndHeight()) {
74     ratio = Some(ratio->Inverted());
75   }
76   return ratio;
77 }
78 
OrientSurface(Orientation aOrientation,SourceSurface * aSurface)79 already_AddRefed<SourceSurface> OrientedImage::OrientSurface(
80     Orientation aOrientation, SourceSurface* aSurface) {
81   MOZ_ASSERT(aSurface);
82 
83   // If the image does not require any re-orientation, return aSurface itself.
84   if (aOrientation.IsIdentity()) {
85     return do_AddRef(aSurface);
86   }
87 
88   // Determine the size of the new surface.
89   nsIntSize originalSize = aSurface->GetSize();
90   nsIntSize targetSize = originalSize;
91   if (aOrientation.SwapsWidthAndHeight()) {
92     swap(targetSize.width, targetSize.height);
93   }
94 
95   // Create our drawable.
96   RefPtr<gfxDrawable> drawable = new gfxSurfaceDrawable(aSurface, originalSize);
97 
98   // Determine an appropriate format for the surface.
99   gfx::SurfaceFormat surfaceFormat = IsOpaque(aSurface->GetFormat())
100                                          ? gfx::SurfaceFormat::OS_RGBX
101                                          : gfx::SurfaceFormat::OS_RGBA;
102 
103   // Create the new surface to draw into.
104   RefPtr<DrawTarget> target =
105       gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
106           targetSize, surfaceFormat);
107   if (!target || !target->IsValid()) {
108     NS_ERROR("Could not create a DrawTarget");
109     return nullptr;
110   }
111 
112   // Draw.
113   RefPtr<gfxContext> ctx = gfxContext::CreateOrNull(target);
114   MOZ_ASSERT(ctx);  // already checked the draw target above
115   ctx->Multiply(OrientationMatrix(aOrientation, originalSize));
116   gfxUtils::DrawPixelSnapped(ctx, drawable, SizeDouble(originalSize),
117                              ImageRegion::Create(originalSize), surfaceFormat,
118                              SamplingFilter::LINEAR);
119 
120   return target->Snapshot();
121 }
122 
NS_IMETHODIMP_(already_AddRefed<SourceSurface>)123 NS_IMETHODIMP_(already_AddRefed<SourceSurface>)
124 OrientedImage::GetFrame(uint32_t aWhichFrame, uint32_t aFlags) {
125   // Get a SourceSurface for the inner image then orient it according to
126   // mOrientation.
127   RefPtr<SourceSurface> innerSurface =
128       InnerImage()->GetFrame(aWhichFrame, aFlags);
129   NS_ENSURE_TRUE(innerSurface, nullptr);
130 
131   return OrientSurface(mOrientation, innerSurface);
132 }
133 
NS_IMETHODIMP_(already_AddRefed<SourceSurface>)134 NS_IMETHODIMP_(already_AddRefed<SourceSurface>)
135 OrientedImage::GetFrameAtSize(const IntSize& aSize, uint32_t aWhichFrame,
136                               uint32_t aFlags) {
137   // Get a SourceSurface for the inner image then orient it according to
138   // mOrientation.
139   IntSize innerSize = aSize;
140   if (mOrientation.SwapsWidthAndHeight()) {
141     swap(innerSize.width, innerSize.height);
142   }
143   RefPtr<SourceSurface> innerSurface =
144       InnerImage()->GetFrameAtSize(innerSize, aWhichFrame, aFlags);
145   NS_ENSURE_TRUE(innerSurface, nullptr);
146 
147   return OrientSurface(mOrientation, innerSurface);
148 }
149 
NS_IMETHODIMP_(bool)150 NS_IMETHODIMP_(bool)
151 OrientedImage::IsImageContainerAvailable(LayerManager* aManager,
152                                          uint32_t aFlags) {
153   if (mOrientation.IsIdentity()) {
154     return InnerImage()->IsImageContainerAvailable(aManager, aFlags);
155   }
156   return false;
157 }
158 
NS_IMETHODIMP_(already_AddRefed<ImageContainer>)159 NS_IMETHODIMP_(already_AddRefed<ImageContainer>)
160 OrientedImage::GetImageContainer(LayerManager* aManager, uint32_t aFlags) {
161   // XXX(seth): We currently don't have a way of orienting the result of
162   // GetImageContainer. We work around this by always returning null, but if it
163   // ever turns out that OrientedImage is widely used on codepaths that can
164   // actually benefit from GetImageContainer, it would be a good idea to fix
165   // that method for performance reasons.
166 
167   if (mOrientation.IsIdentity()) {
168     return InnerImage()->GetImageContainer(aManager, aFlags);
169   }
170 
171   return nullptr;
172 }
173 
NS_IMETHODIMP_(bool)174 NS_IMETHODIMP_(bool)
175 OrientedImage::IsImageContainerAvailableAtSize(LayerManager* aManager,
176                                                const IntSize& aSize,
177                                                uint32_t aFlags) {
178   if (mOrientation.IsIdentity()) {
179     return InnerImage()->IsImageContainerAvailableAtSize(aManager, aSize,
180                                                          aFlags);
181   }
182   return false;
183 }
184 
NS_IMETHODIMP_(ImgDrawResult)185 NS_IMETHODIMP_(ImgDrawResult)
186 OrientedImage::GetImageContainerAtSize(
187     layers::LayerManager* aManager, const gfx::IntSize& aSize,
188     const Maybe<SVGImageContext>& aSVGContext,
189     const Maybe<ImageIntRegion>& aRegion, uint32_t aFlags,
190     layers::ImageContainer** aOutContainer) {
191   // XXX(seth): We currently don't have a way of orienting the result of
192   // GetImageContainer. We work around this by always returning null, but if it
193   // ever turns out that OrientedImage is widely used on codepaths that can
194   // actually benefit from GetImageContainer, it would be a good idea to fix
195   // that method for performance reasons.
196 
197   if (mOrientation.IsIdentity()) {
198     return InnerImage()->GetImageContainerAtSize(
199         aManager, aSize, aSVGContext, aRegion, aFlags, aOutContainer);
200   }
201 
202   return ImgDrawResult::NOT_SUPPORTED;
203 }
204 
205 struct MatrixBuilder {
MatrixBuildermozilla::image::MatrixBuilder206   explicit MatrixBuilder(bool aInvert) : mInvert(aInvert) {}
207 
Buildmozilla::image::MatrixBuilder208   gfxMatrix Build() { return mMatrix; }
209 
Scalemozilla::image::MatrixBuilder210   void Scale(gfxFloat aX, gfxFloat aY) {
211     if (mInvert) {
212       mMatrix *= gfxMatrix::Scaling(1.0 / aX, 1.0 / aY);
213     } else {
214       mMatrix.PreScale(aX, aY);
215     }
216   }
217 
Rotatemozilla::image::MatrixBuilder218   void Rotate(gfxFloat aPhi) {
219     if (mInvert) {
220       mMatrix *= gfxMatrix::Rotation(-aPhi);
221     } else {
222       mMatrix.PreRotate(aPhi);
223     }
224   }
225 
Translatemozilla::image::MatrixBuilder226   void Translate(gfxPoint aDelta) {
227     if (mInvert) {
228       mMatrix *= gfxMatrix::Translation(-aDelta);
229     } else {
230       mMatrix.PreTranslate(aDelta);
231     }
232   }
233 
234  private:
235   gfxMatrix mMatrix;
236   bool mInvert;
237 };
238 
OrientationMatrix(Orientation aOrientation,const nsIntSize & aSize,bool aInvert)239 gfxMatrix OrientedImage::OrientationMatrix(Orientation aOrientation,
240                                            const nsIntSize& aSize,
241                                            bool aInvert /* = false */) {
242   MatrixBuilder builder(aInvert);
243 
244   // Apply reflection, if present. (For a regular, non-flipFirst reflection,
245   // this logically happens second, but we apply it first because these
246   // transformations are all premultiplied.) A translation is necessary to place
247   // the image back in the first quadrant.
248   if (aOrientation.flip == Flip::Horizontal && !aOrientation.flipFirst) {
249     if (aOrientation.SwapsWidthAndHeight()) {
250       builder.Translate(gfxPoint(aSize.height, 0));
251     } else {
252       builder.Translate(gfxPoint(aSize.width, 0));
253     }
254     builder.Scale(-1.0, 1.0);
255   }
256 
257   // Apply rotation, if present. Again, a translation is used to place the
258   // image back in the first quadrant.
259   switch (aOrientation.rotation) {
260     case Angle::D0:
261       break;
262     case Angle::D90:
263       builder.Translate(gfxPoint(aSize.height, 0));
264       builder.Rotate(-1.5 * M_PI);
265       break;
266     case Angle::D180:
267       builder.Translate(gfxPoint(aSize.width, aSize.height));
268       builder.Rotate(-1.0 * M_PI);
269       break;
270     case Angle::D270:
271       builder.Translate(gfxPoint(0, aSize.width));
272       builder.Rotate(-0.5 * M_PI);
273       break;
274     default:
275       MOZ_ASSERT(false, "Invalid rotation value");
276   }
277 
278   // Apply a flipFirst reflection.
279   if (aOrientation.flip == Flip::Horizontal && aOrientation.flipFirst) {
280     builder.Translate(gfxPoint(aSize.width, 0.0));
281     builder.Scale(-1.0, 1.0);
282   }
283 
284   return builder.Build();
285 }
286 
NS_IMETHODIMP_(ImgDrawResult)287 NS_IMETHODIMP_(ImgDrawResult)
288 OrientedImage::Draw(gfxContext* aContext, const nsIntSize& aSize,
289                     const ImageRegion& aRegion, uint32_t aWhichFrame,
290                     SamplingFilter aSamplingFilter,
291                     const Maybe<SVGImageContext>& aSVGContext, uint32_t aFlags,
292                     float aOpacity) {
293   if (mOrientation.IsIdentity()) {
294     return InnerImage()->Draw(aContext, aSize, aRegion, aWhichFrame,
295                               aSamplingFilter, aSVGContext, aFlags, aOpacity);
296   }
297 
298   // Update the image size to match the image's coordinate system. (This could
299   // be done using TransformBounds but since it's only a size a swap is enough.)
300   nsIntSize size(aSize);
301   if (mOrientation.SwapsWidthAndHeight()) {
302     swap(size.width, size.height);
303   }
304 
305   // Update the matrix so that we transform the image into the orientation
306   // expected by the caller before drawing.
307   gfxMatrix matrix(OrientationMatrix(size));
308   gfxContextMatrixAutoSaveRestore saveMatrix(aContext);
309   aContext->Multiply(matrix);
310 
311   // The region is already in the orientation expected by the caller, but we
312   // need it to be in the image's coordinate system, so we transform it using
313   // the inverse of the orientation matrix.
314   gfxMatrix inverseMatrix(OrientationMatrix(size, /* aInvert = */ true));
315   ImageRegion region(aRegion);
316   region.TransformBoundsBy(inverseMatrix);
317 
318   auto orientViewport = [&](const SVGImageContext& aOldContext) {
319     SVGImageContext context(aOldContext);
320     auto oldViewport = aOldContext.GetViewportSize();
321     if (oldViewport && mOrientation.SwapsWidthAndHeight()) {
322       // Swap width and height:
323       CSSIntSize newViewport(oldViewport->height, oldViewport->width);
324       context.SetViewportSize(Some(newViewport));
325     }
326     return context;
327   };
328 
329   return InnerImage()->Draw(aContext, size, region, aWhichFrame,
330                             aSamplingFilter, aSVGContext.map(orientViewport),
331                             aFlags, aOpacity);
332 }
333 
OptimalImageSizeForDest(const gfxSize & aDest,uint32_t aWhichFrame,SamplingFilter aSamplingFilter,uint32_t aFlags)334 nsIntSize OrientedImage::OptimalImageSizeForDest(const gfxSize& aDest,
335                                                  uint32_t aWhichFrame,
336                                                  SamplingFilter aSamplingFilter,
337                                                  uint32_t aFlags) {
338   if (!mOrientation.SwapsWidthAndHeight()) {
339     return InnerImage()->OptimalImageSizeForDest(aDest, aWhichFrame,
340                                                  aSamplingFilter, aFlags);
341   }
342 
343   // Swap the size for the calculation, then swap it back for the caller.
344   gfxSize destSize(aDest.height, aDest.width);
345   nsIntSize innerImageSize(InnerImage()->OptimalImageSizeForDest(
346       destSize, aWhichFrame, aSamplingFilter, aFlags));
347   return nsIntSize(innerImageSize.height, innerImageSize.width);
348 }
349 
NS_IMETHODIMP_(nsIntRect)350 NS_IMETHODIMP_(nsIntRect)
351 OrientedImage::GetImageSpaceInvalidationRect(const nsIntRect& aRect) {
352   nsIntRect rect(InnerImage()->GetImageSpaceInvalidationRect(aRect));
353 
354   if (mOrientation.IsIdentity()) {
355     return rect;
356   }
357 
358   nsIntSize innerSize;
359   nsresult rv = InnerImage()->GetWidth(&innerSize.width);
360   rv = NS_FAILED(rv) ? rv : InnerImage()->GetHeight(&innerSize.height);
361   if (NS_FAILED(rv)) {
362     // Fall back to identity if the width and height aren't available.
363     return rect;
364   }
365 
366   // Transform the invalidation rect into the correct orientation.
367   gfxMatrix matrix(OrientationMatrix(innerSize));
368   gfxRect invalidRect(matrix.TransformBounds(
369       gfxRect(rect.X(), rect.Y(), rect.Width(), rect.Height())));
370 
371   return IntRect::RoundOut(invalidRect.X(), invalidRect.Y(),
372                            invalidRect.Width(), invalidRect.Height());
373 }
374 
375 }  // namespace image
376 }  // namespace mozilla
377