Bug 1527719 [wpt PR 15359] - KV storage: make backingStore return the same frozen...
[gecko.git] / image / OrientedImage.cpp
blob6aa1a6a2a6097c01c7f5df2579c584b391026e8d
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 NS_IMETHODIMP
71 OrientedImage::GetIntrinsicRatio(nsSize* aRatio) {
72 nsresult rv = InnerImage()->GetIntrinsicRatio(aRatio);
74 if (mOrientation.SwapsWidthAndHeight()) {
75 swap(aRatio->width, aRatio->height);
78 return rv;
81 NS_IMETHODIMP_(already_AddRefed<SourceSurface>)
82 OrientedImage::GetFrame(uint32_t aWhichFrame, uint32_t aFlags) {
83 nsresult rv;
85 if (mOrientation.IsIdentity()) {
86 return InnerImage()->GetFrame(aWhichFrame, aFlags);
89 // Get the underlying dimensions.
90 IntSize size;
91 rv = InnerImage()->GetWidth(&size.width);
92 NS_ENSURE_SUCCESS(rv, nullptr);
93 rv = InnerImage()->GetHeight(&size.height);
94 NS_ENSURE_SUCCESS(rv, nullptr);
96 // Determine an appropriate format for the surface.
97 gfx::SurfaceFormat surfaceFormat;
98 if (InnerImage()->WillDrawOpaqueNow()) {
99 surfaceFormat = gfx::SurfaceFormat::B8G8R8X8;
100 } else {
101 surfaceFormat = gfx::SurfaceFormat::B8G8R8A8;
104 // Create a surface to draw into.
105 RefPtr<DrawTarget> target =
106 gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
107 size, surfaceFormat);
108 if (!target || !target->IsValid()) {
109 NS_ERROR("Could not create a DrawTarget");
110 return nullptr;
113 // Create our drawable.
114 RefPtr<SourceSurface> innerSurface =
115 InnerImage()->GetFrame(aWhichFrame, aFlags);
116 NS_ENSURE_TRUE(innerSurface, nullptr);
117 RefPtr<gfxDrawable> drawable = new gfxSurfaceDrawable(innerSurface, size);
119 // Draw.
120 RefPtr<gfxContext> ctx = gfxContext::CreateOrNull(target);
121 MOZ_ASSERT(ctx); // already checked the draw target above
122 ctx->Multiply(OrientationMatrix(size));
123 gfxUtils::DrawPixelSnapped(ctx, drawable, SizeDouble(size),
124 ImageRegion::Create(size), surfaceFormat,
125 SamplingFilter::LINEAR);
127 return target->Snapshot();
130 NS_IMETHODIMP_(already_AddRefed<SourceSurface>)
131 OrientedImage::GetFrameAtSize(const IntSize& aSize, uint32_t aWhichFrame,
132 uint32_t aFlags) {
133 // XXX(seth): It'd be nice to support downscale-during-decode for this case,
134 // but right now we just fall back to the intrinsic size.
135 return GetFrame(aWhichFrame, aFlags);
138 NS_IMETHODIMP_(bool)
139 OrientedImage::IsImageContainerAvailable(LayerManager* aManager,
140 uint32_t aFlags) {
141 if (mOrientation.IsIdentity()) {
142 return InnerImage()->IsImageContainerAvailable(aManager, aFlags);
144 return false;
147 NS_IMETHODIMP_(already_AddRefed<ImageContainer>)
148 OrientedImage::GetImageContainer(LayerManager* aManager, uint32_t aFlags) {
149 // XXX(seth): We currently don't have a way of orienting the result of
150 // GetImageContainer. We work around this by always returning null, but if it
151 // ever turns out that OrientedImage is widely used on codepaths that can
152 // actually benefit from GetImageContainer, it would be a good idea to fix
153 // that method for performance reasons.
155 if (mOrientation.IsIdentity()) {
156 return InnerImage()->GetImageContainer(aManager, aFlags);
159 return nullptr;
162 NS_IMETHODIMP_(bool)
163 OrientedImage::IsImageContainerAvailableAtSize(LayerManager* aManager,
164 const IntSize& aSize,
165 uint32_t aFlags) {
166 if (mOrientation.IsIdentity()) {
167 return InnerImage()->IsImageContainerAvailableAtSize(aManager, aSize,
168 aFlags);
170 return false;
173 NS_IMETHODIMP_(ImgDrawResult)
174 OrientedImage::GetImageContainerAtSize(
175 layers::LayerManager* aManager, const gfx::IntSize& aSize,
176 const Maybe<SVGImageContext>& aSVGContext, uint32_t aFlags,
177 layers::ImageContainer** aOutContainer) {
178 // XXX(seth): We currently don't have a way of orienting the result of
179 // GetImageContainer. We work around this by always returning null, but if it
180 // ever turns out that OrientedImage is widely used on codepaths that can
181 // actually benefit from GetImageContainer, it would be a good idea to fix
182 // that method for performance reasons.
184 if (mOrientation.IsIdentity()) {
185 return InnerImage()->GetImageContainerAtSize(aManager, aSize, aSVGContext,
186 aFlags, aOutContainer);
189 return ImgDrawResult::NOT_SUPPORTED;
192 struct MatrixBuilder {
193 explicit MatrixBuilder(bool aInvert) : mInvert(aInvert) {}
195 gfxMatrix Build() { return mMatrix; }
197 void Scale(gfxFloat aX, gfxFloat aY) {
198 if (mInvert) {
199 mMatrix *= gfxMatrix::Scaling(1.0 / aX, 1.0 / aY);
200 } else {
201 mMatrix.PreScale(aX, aY);
205 void Rotate(gfxFloat aPhi) {
206 if (mInvert) {
207 mMatrix *= gfxMatrix::Rotation(-aPhi);
208 } else {
209 mMatrix.PreRotate(aPhi);
213 void Translate(gfxPoint aDelta) {
214 if (mInvert) {
215 mMatrix *= gfxMatrix::Translation(-aDelta);
216 } else {
217 mMatrix.PreTranslate(aDelta);
221 private:
222 gfxMatrix mMatrix;
223 bool mInvert;
227 * OrientationMatrix() computes a matrix that applies the rotation and
228 * reflection specified by mOrientation, or that matrix's inverse if aInvert is
229 * true.
231 * @param aSize The scaled size of the inner image. (When outside code specifies
232 * the scaled size, as with imgIContainer::Draw and its aSize
233 * parameter, it's necessary to swap the width and height if
234 * mOrientation.SwapsWidthAndHeight() is true.)
235 * @param aInvert If true, compute the inverse of the orientation matrix. Prefer
236 * this approach to OrientationMatrix(..).Invert(), because it's
237 * more numerically accurate.
239 gfxMatrix OrientedImage::OrientationMatrix(const nsIntSize& aSize,
240 bool aInvert /* = false */) {
241 MatrixBuilder builder(aInvert);
243 // Apply reflection, if present. (This logically happens second, but we
244 // apply it first because these transformations are all premultiplied.) A
245 // translation is necessary to place the image back in the first quadrant.
246 switch (mOrientation.flip) {
247 case Flip::Unflipped:
248 break;
249 case Flip::Horizontal:
250 if (mOrientation.SwapsWidthAndHeight()) {
251 builder.Translate(gfxPoint(aSize.height, 0));
252 } else {
253 builder.Translate(gfxPoint(aSize.width, 0));
255 builder.Scale(-1.0, 1.0);
256 break;
257 default:
258 MOZ_ASSERT(false, "Invalid flip value");
261 // Apply rotation, if present. Again, a translation is used to place the
262 // image back in the first quadrant.
263 switch (mOrientation.rotation) {
264 case Angle::D0:
265 break;
266 case Angle::D90:
267 builder.Translate(gfxPoint(aSize.height, 0));
268 builder.Rotate(-1.5 * M_PI);
269 break;
270 case Angle::D180:
271 builder.Translate(gfxPoint(aSize.width, aSize.height));
272 builder.Rotate(-1.0 * M_PI);
273 break;
274 case Angle::D270:
275 builder.Translate(gfxPoint(0, aSize.width));
276 builder.Rotate(-0.5 * M_PI);
277 break;
278 default:
279 MOZ_ASSERT(false, "Invalid rotation value");
282 return builder.Build();
285 NS_IMETHODIMP_(ImgDrawResult)
286 OrientedImage::Draw(gfxContext* aContext, const nsIntSize& aSize,
287 const ImageRegion& aRegion, uint32_t aWhichFrame,
288 SamplingFilter aSamplingFilter,
289 const Maybe<SVGImageContext>& aSVGContext, uint32_t aFlags,
290 float aOpacity) {
291 if (mOrientation.IsIdentity()) {
292 return InnerImage()->Draw(aContext, aSize, aRegion, aWhichFrame,
293 aSamplingFilter, aSVGContext, aFlags, aOpacity);
296 // Update the image size to match the image's coordinate system. (This could
297 // be done using TransformBounds but since it's only a size a swap is enough.)
298 nsIntSize size(aSize);
299 if (mOrientation.SwapsWidthAndHeight()) {
300 swap(size.width, size.height);
303 // Update the matrix so that we transform the image into the orientation
304 // expected by the caller before drawing.
305 gfxMatrix matrix(OrientationMatrix(size));
306 gfxContextMatrixAutoSaveRestore saveMatrix(aContext);
307 aContext->Multiply(matrix);
309 // The region is already in the orientation expected by the caller, but we
310 // need it to be in the image's coordinate system, so we transform it using
311 // the inverse of the orientation matrix.
312 gfxMatrix inverseMatrix(OrientationMatrix(size, /* aInvert = */ true));
313 ImageRegion region(aRegion);
314 region.TransformBoundsBy(inverseMatrix);
316 auto orientViewport = [&](const SVGImageContext& aOldContext) {
317 SVGImageContext context(aOldContext);
318 auto oldViewport = aOldContext.GetViewportSize();
319 if (oldViewport && mOrientation.SwapsWidthAndHeight()) {
320 // Swap width and height:
321 CSSIntSize newViewport(oldViewport->height, oldViewport->width);
322 context.SetViewportSize(Some(newViewport));
324 return context;
327 return InnerImage()->Draw(aContext, size, region, aWhichFrame,
328 aSamplingFilter, aSVGContext.map(orientViewport),
329 aFlags, aOpacity);
332 nsIntSize OrientedImage::OptimalImageSizeForDest(const gfxSize& aDest,
333 uint32_t aWhichFrame,
334 SamplingFilter aSamplingFilter,
335 uint32_t aFlags) {
336 if (!mOrientation.SwapsWidthAndHeight()) {
337 return InnerImage()->OptimalImageSizeForDest(aDest, aWhichFrame,
338 aSamplingFilter, aFlags);
341 // Swap the size for the calculation, then swap it back for the caller.
342 gfxSize destSize(aDest.height, aDest.width);
343 nsIntSize innerImageSize(InnerImage()->OptimalImageSizeForDest(
344 destSize, aWhichFrame, aSamplingFilter, aFlags));
345 return nsIntSize(innerImageSize.height, innerImageSize.width);
348 NS_IMETHODIMP_(nsIntRect)
349 OrientedImage::GetImageSpaceInvalidationRect(const nsIntRect& aRect) {
350 nsIntRect rect(InnerImage()->GetImageSpaceInvalidationRect(aRect));
352 if (mOrientation.IsIdentity()) {
353 return rect;
356 nsIntSize innerSize;
357 nsresult rv = InnerImage()->GetWidth(&innerSize.width);
358 rv = NS_FAILED(rv) ? rv : InnerImage()->GetHeight(&innerSize.height);
359 if (NS_FAILED(rv)) {
360 // Fall back to identity if the width and height aren't available.
361 return rect;
364 // Transform the invalidation rect into the correct orientation.
365 gfxMatrix matrix(OrientationMatrix(innerSize));
366 gfxRect invalidRect(matrix.TransformBounds(
367 gfxRect(rect.X(), rect.Y(), rect.Width(), rect.Height())));
369 return IntRect::RoundOut(invalidRect.X(), invalidRect.Y(),
370 invalidRect.Width(), invalidRect.Height());
373 } // namespace image
374 } // namespace mozilla