Bug 1880273 [wpt PR 44583] - WebKit export of https://bugs.webkit.org/show_bug.cgi...
[gecko.git] / image / OrientedImage.cpp
blob8461405dcba6690d709b5fec86c7ec7c466f5065
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 "gfxContext.h"
12 #include "gfxDrawable.h"
13 #include "gfxPlatform.h"
14 #include "gfxUtils.h"
15 #include "ImageRegion.h"
16 #include "mozilla/SVGImageContext.h"
18 using std::swap;
20 namespace mozilla {
22 using namespace gfx;
24 namespace image {
26 NS_IMETHODIMP
27 OrientedImage::GetWidth(int32_t* aWidth) {
28 if (mOrientation.SwapsWidthAndHeight()) {
29 return InnerImage()->GetHeight(aWidth);
30 } else {
31 return InnerImage()->GetWidth(aWidth);
35 NS_IMETHODIMP
36 OrientedImage::GetHeight(int32_t* aHeight) {
37 if (mOrientation.SwapsWidthAndHeight()) {
38 return InnerImage()->GetWidth(aHeight);
39 } else {
40 return InnerImage()->GetHeight(aHeight);
44 nsresult OrientedImage::GetNativeSizes(nsTArray<IntSize>& aNativeSizes) {
45 nsresult rv = InnerImage()->GetNativeSizes(aNativeSizes);
47 if (mOrientation.SwapsWidthAndHeight()) {
48 auto i = aNativeSizes.Length();
49 while (i > 0) {
50 --i;
51 swap(aNativeSizes[i].width, aNativeSizes[i].height);
55 return rv;
58 NS_IMETHODIMP
59 OrientedImage::GetIntrinsicSize(nsSize* aSize) {
60 nsresult rv = InnerImage()->GetIntrinsicSize(aSize);
62 if (mOrientation.SwapsWidthAndHeight()) {
63 swap(aSize->width, aSize->height);
66 return rv;
69 Maybe<AspectRatio> OrientedImage::GetIntrinsicRatio() {
70 Maybe<AspectRatio> ratio = InnerImage()->GetIntrinsicRatio();
71 if (ratio && mOrientation.SwapsWidthAndHeight()) {
72 ratio = Some(ratio->Inverted());
74 return ratio;
77 already_AddRefed<SourceSurface> OrientedImage::OrientSurface(
78 Orientation aOrientation, SourceSurface* aSurface) {
79 MOZ_ASSERT(aSurface);
81 // If the image does not require any re-orientation, return aSurface itself.
82 if (aOrientation.IsIdentity()) {
83 return do_AddRef(aSurface);
86 // Determine the size of the new surface.
87 nsIntSize originalSize = aSurface->GetSize();
88 nsIntSize targetSize = originalSize;
89 if (aOrientation.SwapsWidthAndHeight()) {
90 swap(targetSize.width, targetSize.height);
93 // Create our drawable.
94 RefPtr<gfxDrawable> drawable = new gfxSurfaceDrawable(aSurface, originalSize);
96 // Determine an appropriate format for the surface.
97 gfx::SurfaceFormat surfaceFormat = IsOpaque(aSurface->GetFormat())
98 ? gfx::SurfaceFormat::OS_RGBX
99 : gfx::SurfaceFormat::OS_RGBA;
101 // Create the new surface to draw into.
102 RefPtr<DrawTarget> target =
103 gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
104 targetSize, surfaceFormat);
105 if (!target || !target->IsValid()) {
106 NS_ERROR("Could not create a DrawTarget");
107 return nullptr;
110 // Draw.
111 gfxContext ctx(target);
113 ctx.Multiply(OrientationMatrix(aOrientation, originalSize));
114 gfxUtils::DrawPixelSnapped(&ctx, drawable, SizeDouble(originalSize),
115 ImageRegion::Create(originalSize), surfaceFormat,
116 SamplingFilter::LINEAR);
118 return target->Snapshot();
121 NS_IMETHODIMP_(already_AddRefed<SourceSurface>)
122 OrientedImage::GetFrame(uint32_t aWhichFrame, uint32_t aFlags) {
123 // Get a SourceSurface for the inner image then orient it according to
124 // mOrientation.
125 RefPtr<SourceSurface> innerSurface =
126 InnerImage()->GetFrame(aWhichFrame, aFlags);
127 NS_ENSURE_TRUE(innerSurface, nullptr);
129 return OrientSurface(mOrientation, innerSurface);
132 NS_IMETHODIMP_(already_AddRefed<SourceSurface>)
133 OrientedImage::GetFrameAtSize(const IntSize& aSize, uint32_t aWhichFrame,
134 uint32_t aFlags) {
135 // Get a SourceSurface for the inner image then orient it according to
136 // mOrientation.
137 IntSize innerSize = aSize;
138 if (mOrientation.SwapsWidthAndHeight()) {
139 swap(innerSize.width, innerSize.height);
141 RefPtr<SourceSurface> innerSurface =
142 InnerImage()->GetFrameAtSize(innerSize, aWhichFrame, aFlags);
143 NS_ENSURE_TRUE(innerSurface, nullptr);
145 return OrientSurface(mOrientation, innerSurface);
148 NS_IMETHODIMP_(bool)
149 OrientedImage::IsImageContainerAvailable(WindowRenderer* aRenderer,
150 uint32_t aFlags) {
151 if (mOrientation.IsIdentity()) {
152 return InnerImage()->IsImageContainerAvailable(aRenderer, aFlags);
154 return false;
157 NS_IMETHODIMP_(ImgDrawResult)
158 OrientedImage::GetImageProvider(WindowRenderer* aRenderer,
159 const gfx::IntSize& aSize,
160 const SVGImageContext& aSVGContext,
161 const Maybe<ImageIntRegion>& aRegion,
162 uint32_t aFlags,
163 WebRenderImageProvider** aProvider) {
164 // XXX(seth): We currently don't have a way of orienting the result of
165 // GetImageContainer. We work around this by always returning null, but if it
166 // ever turns out that OrientedImage is widely used on codepaths that can
167 // actually benefit from GetImageContainer, it would be a good idea to fix
168 // that method for performance reasons.
170 if (mOrientation.IsIdentity()) {
171 return InnerImage()->GetImageProvider(aRenderer, aSize, aSVGContext,
172 aRegion, aFlags, aProvider);
175 return ImgDrawResult::NOT_SUPPORTED;
178 struct MatrixBuilder {
179 explicit MatrixBuilder(bool aInvert) : mInvert(aInvert) {}
181 gfxMatrix Build() { return mMatrix; }
183 void Scale(gfxFloat aX, gfxFloat aY) {
184 if (mInvert) {
185 mMatrix *= gfxMatrix::Scaling(1.0 / aX, 1.0 / aY);
186 } else {
187 mMatrix.PreScale(aX, aY);
191 void Rotate(gfxFloat aPhi) {
192 if (mInvert) {
193 mMatrix *= gfxMatrix::Rotation(-aPhi);
194 } else {
195 mMatrix.PreRotate(aPhi);
199 void Translate(gfxPoint aDelta) {
200 if (mInvert) {
201 mMatrix *= gfxMatrix::Translation(-aDelta);
202 } else {
203 mMatrix.PreTranslate(aDelta);
207 private:
208 gfxMatrix mMatrix;
209 bool mInvert;
212 gfxMatrix OrientedImage::OrientationMatrix(Orientation aOrientation,
213 const nsIntSize& aSize,
214 bool aInvert /* = false */) {
215 MatrixBuilder builder(aInvert);
217 // Apply reflection, if present. (For a regular, non-flipFirst reflection,
218 // this logically happens second, but we apply it first because these
219 // transformations are all premultiplied.) A translation is necessary to place
220 // the image back in the first quadrant.
221 if (aOrientation.flip == Flip::Horizontal && !aOrientation.flipFirst) {
222 if (aOrientation.SwapsWidthAndHeight()) {
223 builder.Translate(gfxPoint(aSize.height, 0));
224 } else {
225 builder.Translate(gfxPoint(aSize.width, 0));
227 builder.Scale(-1.0, 1.0);
230 // Apply rotation, if present. Again, a translation is used to place the
231 // image back in the first quadrant.
232 switch (aOrientation.rotation) {
233 case Angle::D0:
234 break;
235 case Angle::D90:
236 builder.Translate(gfxPoint(aSize.height, 0));
237 builder.Rotate(-1.5 * M_PI);
238 break;
239 case Angle::D180:
240 builder.Translate(gfxPoint(aSize.width, aSize.height));
241 builder.Rotate(-1.0 * M_PI);
242 break;
243 case Angle::D270:
244 builder.Translate(gfxPoint(0, aSize.width));
245 builder.Rotate(-0.5 * M_PI);
246 break;
247 default:
248 MOZ_ASSERT(false, "Invalid rotation value");
251 // Apply a flipFirst reflection.
252 if (aOrientation.flip == Flip::Horizontal && aOrientation.flipFirst) {
253 builder.Translate(gfxPoint(aSize.width, 0.0));
254 builder.Scale(-1.0, 1.0);
257 return builder.Build();
260 NS_IMETHODIMP_(ImgDrawResult)
261 OrientedImage::Draw(gfxContext* aContext, const nsIntSize& aSize,
262 const ImageRegion& aRegion, uint32_t aWhichFrame,
263 SamplingFilter aSamplingFilter,
264 const SVGImageContext& aSVGContext, uint32_t aFlags,
265 float aOpacity) {
266 if (mOrientation.IsIdentity()) {
267 return InnerImage()->Draw(aContext, aSize, aRegion, aWhichFrame,
268 aSamplingFilter, aSVGContext, aFlags, aOpacity);
271 // Update the image size to match the image's coordinate system. (This could
272 // be done using TransformBounds but since it's only a size a swap is enough.)
273 nsIntSize size(aSize);
274 if (mOrientation.SwapsWidthAndHeight()) {
275 swap(size.width, size.height);
278 // Update the matrix so that we transform the image into the orientation
279 // expected by the caller before drawing.
280 gfxMatrix matrix(OrientationMatrix(size));
281 gfxContextMatrixAutoSaveRestore saveMatrix(aContext);
282 aContext->Multiply(matrix);
284 // The region is already in the orientation expected by the caller, but we
285 // need it to be in the image's coordinate system, so we transform it using
286 // the inverse of the orientation matrix.
287 gfxMatrix inverseMatrix(OrientationMatrix(size, /* aInvert = */ true));
288 ImageRegion region(aRegion);
289 region.TransformBoundsBy(inverseMatrix);
291 auto orientViewport = [&](const SVGImageContext& aOldContext) {
292 SVGImageContext context(aOldContext);
293 auto oldViewport = aOldContext.GetViewportSize();
294 if (oldViewport && mOrientation.SwapsWidthAndHeight()) {
295 // Swap width and height:
296 CSSIntSize newViewport(oldViewport->height, oldViewport->width);
297 context.SetViewportSize(Some(newViewport));
299 return context;
302 return InnerImage()->Draw(aContext, size, region, aWhichFrame,
303 aSamplingFilter, orientViewport(aSVGContext),
304 aFlags, aOpacity);
307 nsIntSize OrientedImage::OptimalImageSizeForDest(const gfxSize& aDest,
308 uint32_t aWhichFrame,
309 SamplingFilter aSamplingFilter,
310 uint32_t aFlags) {
311 if (!mOrientation.SwapsWidthAndHeight()) {
312 return InnerImage()->OptimalImageSizeForDest(aDest, aWhichFrame,
313 aSamplingFilter, aFlags);
316 // Swap the size for the calculation, then swap it back for the caller.
317 gfxSize destSize(aDest.height, aDest.width);
318 nsIntSize innerImageSize(InnerImage()->OptimalImageSizeForDest(
319 destSize, aWhichFrame, aSamplingFilter, aFlags));
320 return nsIntSize(innerImageSize.height, innerImageSize.width);
323 NS_IMETHODIMP_(nsIntRect)
324 OrientedImage::GetImageSpaceInvalidationRect(const nsIntRect& aRect) {
325 nsIntRect rect(InnerImage()->GetImageSpaceInvalidationRect(aRect));
327 if (mOrientation.IsIdentity()) {
328 return rect;
331 nsIntSize innerSize;
332 nsresult rv = InnerImage()->GetWidth(&innerSize.width);
333 rv = NS_FAILED(rv) ? rv : InnerImage()->GetHeight(&innerSize.height);
334 if (NS_FAILED(rv)) {
335 // Fall back to identity if the width and height aren't available.
336 return rect;
339 // Transform the invalidation rect into the correct orientation.
340 gfxMatrix matrix(OrientationMatrix(innerSize));
341 gfxRect invalidRect(matrix.TransformBounds(
342 gfxRect(rect.X(), rect.Y(), rect.Width(), rect.Height())));
344 return IntRect::RoundOut(invalidRect.X(), invalidRect.Y(),
345 invalidRect.Width(), invalidRect.Height());
348 } // namespace image
349 } // namespace mozilla