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