Bug 1608150 [wpt PR 21112] - Add missing space in `./wpt lint` command line docs...
[gecko.git] / image / OrientedImage.cpp
blob368789a068653247209a62f9f2528962e4ea8190
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/. */
6 #include "OrientedImage.h"
8 #include <algorithm>
10 #include "gfx2DGlue.h"
11 #include "gfxDrawable.h"
12 #include "gfxPlatform.h"
13 #include "gfxUtils.h"
14 #include "ImageRegion.h"
15 #include "SVGImageContext.h"
17 using std::swap;
19 namespace mozilla {
21 using namespace gfx;
22 using layers::ImageContainer;
23 using layers::LayerManager;
25 namespace image {
27 NS_IMETHODIMP
28 OrientedImage::GetWidth(int32_t* aWidth) {
29 if (mOrientation.SwapsWidthAndHeight()) {
30 return InnerImage()->GetHeight(aWidth);
31 } else {
32 return InnerImage()->GetWidth(aWidth);
36 NS_IMETHODIMP
37 OrientedImage::GetHeight(int32_t* aHeight) {
38 if (mOrientation.SwapsWidthAndHeight()) {
39 return InnerImage()->GetWidth(aHeight);
40 } else {
41 return InnerImage()->GetHeight(aHeight);
45 nsresult OrientedImage::GetNativeSizes(nsTArray<IntSize>& aNativeSizes) const {
46 nsresult rv = InnerImage()->GetNativeSizes(aNativeSizes);
48 if (mOrientation.SwapsWidthAndHeight()) {
49 auto i = aNativeSizes.Length();
50 while (i > 0) {
51 --i;
52 swap(aNativeSizes[i].width, aNativeSizes[i].height);
56 return rv;
59 NS_IMETHODIMP
60 OrientedImage::GetIntrinsicSize(nsSize* aSize) {
61 nsresult rv = InnerImage()->GetIntrinsicSize(aSize);
63 if (mOrientation.SwapsWidthAndHeight()) {
64 swap(aSize->width, aSize->height);
67 return rv;
70 Maybe<AspectRatio> OrientedImage::GetIntrinsicRatio() {
71 Maybe<AspectRatio> ratio = InnerImage()->GetIntrinsicRatio();
72 if (ratio && mOrientation.SwapsWidthAndHeight()) {
73 ratio = Some(ratio->Inverted());
75 return ratio;
78 NS_IMETHODIMP_(already_AddRefed<SourceSurface>)
79 OrientedImage::GetFrame(uint32_t aWhichFrame, uint32_t aFlags) {
80 nsresult rv;
82 if (mOrientation.IsIdentity()) {
83 return InnerImage()->GetFrame(aWhichFrame, aFlags);
86 // Get the underlying dimensions.
87 IntSize size;
88 rv = InnerImage()->GetWidth(&size.width);
89 NS_ENSURE_SUCCESS(rv, nullptr);
90 rv = InnerImage()->GetHeight(&size.height);
91 NS_ENSURE_SUCCESS(rv, nullptr);
93 // Determine an appropriate format for the surface.
94 gfx::SurfaceFormat surfaceFormat;
95 if (InnerImage()->WillDrawOpaqueNow()) {
96 surfaceFormat = gfx::SurfaceFormat::OS_RGBX;
97 } else {
98 surfaceFormat = gfx::SurfaceFormat::OS_RGBA;
101 // Create a surface to draw into.
102 RefPtr<DrawTarget> target =
103 gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
104 size, surfaceFormat);
105 if (!target || !target->IsValid()) {
106 NS_ERROR("Could not create a DrawTarget");
107 return nullptr;
110 // Create our drawable.
111 RefPtr<SourceSurface> innerSurface =
112 InnerImage()->GetFrame(aWhichFrame, aFlags);
113 NS_ENSURE_TRUE(innerSurface, nullptr);
114 RefPtr<gfxDrawable> drawable = new gfxSurfaceDrawable(innerSurface, size);
116 // Draw.
117 RefPtr<gfxContext> ctx = gfxContext::CreateOrNull(target);
118 MOZ_ASSERT(ctx); // already checked the draw target above
119 ctx->Multiply(OrientationMatrix(size));
120 gfxUtils::DrawPixelSnapped(ctx, drawable, SizeDouble(size),
121 ImageRegion::Create(size), surfaceFormat,
122 SamplingFilter::LINEAR);
124 return target->Snapshot();
127 NS_IMETHODIMP_(already_AddRefed<SourceSurface>)
128 OrientedImage::GetFrameAtSize(const IntSize& aSize, uint32_t aWhichFrame,
129 uint32_t aFlags) {
130 // XXX(seth): It'd be nice to support downscale-during-decode for this case,
131 // but right now we just fall back to the intrinsic size.
132 return GetFrame(aWhichFrame, aFlags);
135 NS_IMETHODIMP_(bool)
136 OrientedImage::IsImageContainerAvailable(LayerManager* aManager,
137 uint32_t aFlags) {
138 if (mOrientation.IsIdentity()) {
139 return InnerImage()->IsImageContainerAvailable(aManager, aFlags);
141 return false;
144 NS_IMETHODIMP_(already_AddRefed<ImageContainer>)
145 OrientedImage::GetImageContainer(LayerManager* aManager, uint32_t aFlags) {
146 // XXX(seth): We currently don't have a way of orienting the result of
147 // GetImageContainer. We work around this by always returning null, but if it
148 // ever turns out that OrientedImage is widely used on codepaths that can
149 // actually benefit from GetImageContainer, it would be a good idea to fix
150 // that method for performance reasons.
152 if (mOrientation.IsIdentity()) {
153 return InnerImage()->GetImageContainer(aManager, aFlags);
156 return nullptr;
159 NS_IMETHODIMP_(bool)
160 OrientedImage::IsImageContainerAvailableAtSize(LayerManager* aManager,
161 const IntSize& aSize,
162 uint32_t aFlags) {
163 if (mOrientation.IsIdentity()) {
164 return InnerImage()->IsImageContainerAvailableAtSize(aManager, aSize,
165 aFlags);
167 return false;
170 NS_IMETHODIMP_(ImgDrawResult)
171 OrientedImage::GetImageContainerAtSize(
172 layers::LayerManager* aManager, const gfx::IntSize& aSize,
173 const Maybe<SVGImageContext>& aSVGContext, uint32_t aFlags,
174 layers::ImageContainer** aOutContainer) {
175 // XXX(seth): We currently don't have a way of orienting the result of
176 // GetImageContainer. We work around this by always returning null, but if it
177 // ever turns out that OrientedImage is widely used on codepaths that can
178 // actually benefit from GetImageContainer, it would be a good idea to fix
179 // that method for performance reasons.
181 if (mOrientation.IsIdentity()) {
182 return InnerImage()->GetImageContainerAtSize(aManager, aSize, aSVGContext,
183 aFlags, aOutContainer);
186 return ImgDrawResult::NOT_SUPPORTED;
189 struct MatrixBuilder {
190 explicit MatrixBuilder(bool aInvert) : mInvert(aInvert) {}
192 gfxMatrix Build() { return mMatrix; }
194 void Scale(gfxFloat aX, gfxFloat aY) {
195 if (mInvert) {
196 mMatrix *= gfxMatrix::Scaling(1.0 / aX, 1.0 / aY);
197 } else {
198 mMatrix.PreScale(aX, aY);
202 void Rotate(gfxFloat aPhi) {
203 if (mInvert) {
204 mMatrix *= gfxMatrix::Rotation(-aPhi);
205 } else {
206 mMatrix.PreRotate(aPhi);
210 void Translate(gfxPoint aDelta) {
211 if (mInvert) {
212 mMatrix *= gfxMatrix::Translation(-aDelta);
213 } else {
214 mMatrix.PreTranslate(aDelta);
218 private:
219 gfxMatrix mMatrix;
220 bool mInvert;
224 * OrientationMatrix() computes a matrix that applies the rotation and
225 * reflection specified by mOrientation, or that matrix's inverse if aInvert is
226 * true.
228 * @param aSize The scaled size of the inner image. (When outside code specifies
229 * the scaled size, as with imgIContainer::Draw and its aSize
230 * parameter, it's necessary to swap the width and height if
231 * mOrientation.SwapsWidthAndHeight() is true.)
232 * @param aInvert If true, compute the inverse of the orientation matrix. Prefer
233 * this approach to OrientationMatrix(..).Invert(), because it's
234 * more numerically accurate.
236 gfxMatrix OrientedImage::OrientationMatrix(const nsIntSize& aSize,
237 bool aInvert /* = false */) {
238 MatrixBuilder builder(aInvert);
240 // Apply reflection, if present. (This logically happens second, but we
241 // apply it first because these transformations are all premultiplied.) A
242 // translation is necessary to place the image back in the first quadrant.
243 switch (mOrientation.flip) {
244 case Flip::Unflipped:
245 break;
246 case Flip::Horizontal:
247 if (mOrientation.SwapsWidthAndHeight()) {
248 builder.Translate(gfxPoint(aSize.height, 0));
249 } else {
250 builder.Translate(gfxPoint(aSize.width, 0));
252 builder.Scale(-1.0, 1.0);
253 break;
254 default:
255 MOZ_ASSERT(false, "Invalid flip value");
258 // Apply rotation, if present. Again, a translation is used to place the
259 // image back in the first quadrant.
260 switch (mOrientation.rotation) {
261 case Angle::D0:
262 break;
263 case Angle::D90:
264 builder.Translate(gfxPoint(aSize.height, 0));
265 builder.Rotate(-1.5 * M_PI);
266 break;
267 case Angle::D180:
268 builder.Translate(gfxPoint(aSize.width, aSize.height));
269 builder.Rotate(-1.0 * M_PI);
270 break;
271 case Angle::D270:
272 builder.Translate(gfxPoint(0, aSize.width));
273 builder.Rotate(-0.5 * M_PI);
274 break;
275 default:
276 MOZ_ASSERT(false, "Invalid rotation value");
279 return builder.Build();
282 NS_IMETHODIMP_(ImgDrawResult)
283 OrientedImage::Draw(gfxContext* aContext, const nsIntSize& aSize,
284 const ImageRegion& aRegion, uint32_t aWhichFrame,
285 SamplingFilter aSamplingFilter,
286 const Maybe<SVGImageContext>& aSVGContext, uint32_t aFlags,
287 float aOpacity) {
288 if (mOrientation.IsIdentity()) {
289 return InnerImage()->Draw(aContext, aSize, aRegion, aWhichFrame,
290 aSamplingFilter, aSVGContext, aFlags, aOpacity);
293 // Update the image size to match the image's coordinate system. (This could
294 // be done using TransformBounds but since it's only a size a swap is enough.)
295 nsIntSize size(aSize);
296 if (mOrientation.SwapsWidthAndHeight()) {
297 swap(size.width, size.height);
300 // Update the matrix so that we transform the image into the orientation
301 // expected by the caller before drawing.
302 gfxMatrix matrix(OrientationMatrix(size));
303 gfxContextMatrixAutoSaveRestore saveMatrix(aContext);
304 aContext->Multiply(matrix);
306 // The region is already in the orientation expected by the caller, but we
307 // need it to be in the image's coordinate system, so we transform it using
308 // the inverse of the orientation matrix.
309 gfxMatrix inverseMatrix(OrientationMatrix(size, /* aInvert = */ true));
310 ImageRegion region(aRegion);
311 region.TransformBoundsBy(inverseMatrix);
313 auto orientViewport = [&](const SVGImageContext& aOldContext) {
314 SVGImageContext context(aOldContext);
315 auto oldViewport = aOldContext.GetViewportSize();
316 if (oldViewport && mOrientation.SwapsWidthAndHeight()) {
317 // Swap width and height:
318 CSSIntSize newViewport(oldViewport->height, oldViewport->width);
319 context.SetViewportSize(Some(newViewport));
321 return context;
324 return InnerImage()->Draw(aContext, size, region, aWhichFrame,
325 aSamplingFilter, aSVGContext.map(orientViewport),
326 aFlags, aOpacity);
329 nsIntSize OrientedImage::OptimalImageSizeForDest(const gfxSize& aDest,
330 uint32_t aWhichFrame,
331 SamplingFilter aSamplingFilter,
332 uint32_t aFlags) {
333 if (!mOrientation.SwapsWidthAndHeight()) {
334 return InnerImage()->OptimalImageSizeForDest(aDest, aWhichFrame,
335 aSamplingFilter, aFlags);
338 // Swap the size for the calculation, then swap it back for the caller.
339 gfxSize destSize(aDest.height, aDest.width);
340 nsIntSize innerImageSize(InnerImage()->OptimalImageSizeForDest(
341 destSize, aWhichFrame, aSamplingFilter, aFlags));
342 return nsIntSize(innerImageSize.height, innerImageSize.width);
345 NS_IMETHODIMP_(nsIntRect)
346 OrientedImage::GetImageSpaceInvalidationRect(const nsIntRect& aRect) {
347 nsIntRect rect(InnerImage()->GetImageSpaceInvalidationRect(aRect));
349 if (mOrientation.IsIdentity()) {
350 return rect;
353 nsIntSize innerSize;
354 nsresult rv = InnerImage()->GetWidth(&innerSize.width);
355 rv = NS_FAILED(rv) ? rv : InnerImage()->GetHeight(&innerSize.height);
356 if (NS_FAILED(rv)) {
357 // Fall back to identity if the width and height aren't available.
358 return rect;
361 // Transform the invalidation rect into the correct orientation.
362 gfxMatrix matrix(OrientationMatrix(innerSize));
363 gfxRect invalidRect(matrix.TransformBounds(
364 gfxRect(rect.X(), rect.Y(), rect.Width(), rect.Height())));
366 return IntRect::RoundOut(invalidRect.X(), invalidRect.Y(),
367 invalidRect.Width(), invalidRect.Height());
370 } // namespace image
371 } // namespace mozilla