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