OrientedImage.cpp (11554B)
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 24 namespace image { 25 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); 32 } 33 } 34 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); 41 } 42 } 43 44 nsresult OrientedImage::GetNativeSizes(nsTArray<IntSize>& aNativeSizes) { 45 nsresult rv = InnerImage()->GetNativeSizes(aNativeSizes); 46 47 if (mOrientation.SwapsWidthAndHeight()) { 48 auto i = aNativeSizes.Length(); 49 while (i > 0) { 50 --i; 51 swap(aNativeSizes[i].width, aNativeSizes[i].height); 52 } 53 } 54 55 return rv; 56 } 57 58 NS_IMETHODIMP 59 OrientedImage::GetIntrinsicSizeInAppUnits(nsSize* aSize) { 60 nsresult rv = InnerImage()->GetIntrinsicSizeInAppUnits(aSize); 61 62 if (mOrientation.SwapsWidthAndHeight()) { 63 swap(aSize->width, aSize->height); 64 } 65 66 return rv; 67 } 68 69 AspectRatio OrientedImage::GetIntrinsicRatio() { 70 AspectRatio ratio = InnerImage()->GetIntrinsicRatio(); 71 if (ratio && mOrientation.SwapsWidthAndHeight()) { 72 ratio = ratio.Inverted(); 73 } 74 return ratio; 75 } 76 77 already_AddRefed<SourceSurface> OrientedImage::OrientSurface( 78 Orientation aOrientation, SourceSurface* aSurface) { 79 MOZ_ASSERT(aSurface); 80 81 // If the image does not require any re-orientation, return aSurface itself. 82 if (aOrientation.IsIdentity()) { 83 return do_AddRef(aSurface); 84 } 85 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); 91 } 92 93 // Create our drawable. 94 RefPtr<gfxDrawable> drawable = new gfxSurfaceDrawable(aSurface, originalSize); 95 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; 100 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; 108 } 109 110 // Draw. 111 gfxContext ctx(target); 112 113 ctx.Multiply(OrientationMatrix(aOrientation, originalSize)); 114 gfxUtils::DrawPixelSnapped(&ctx, drawable, SizeDouble(originalSize), 115 ImageRegion::Create(originalSize), surfaceFormat, 116 SamplingFilter::LINEAR); 117 118 return target->Snapshot(); 119 } 120 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); 128 129 return OrientSurface(mOrientation, innerSurface); 130 } 131 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); 140 } 141 RefPtr<SourceSurface> innerSurface = 142 InnerImage()->GetFrameAtSize(innerSize, aWhichFrame, aFlags); 143 NS_ENSURE_TRUE(innerSurface, nullptr); 144 145 return OrientSurface(mOrientation, innerSurface); 146 } 147 148 NS_IMETHODIMP_(bool) 149 OrientedImage::IsImageContainerAvailable(WindowRenderer* aRenderer, 150 uint32_t aFlags) { 151 if (mOrientation.IsIdentity()) { 152 return InnerImage()->IsImageContainerAvailable(aRenderer, aFlags); 153 } 154 return false; 155 } 156 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. 169 170 if (mOrientation.IsIdentity()) { 171 return InnerImage()->GetImageProvider(aRenderer, aSize, aSVGContext, 172 aRegion, aFlags, aProvider); 173 } 174 175 return ImgDrawResult::NOT_SUPPORTED; 176 } 177 178 struct MatrixBuilder { 179 explicit MatrixBuilder(bool aInvert) : mInvert(aInvert) {} 180 181 gfxMatrix Build() { return mMatrix; } 182 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); 188 } 189 } 190 191 void Rotate(gfxFloat aPhi) { 192 if (mInvert) { 193 mMatrix *= gfxMatrix::Rotation(-aPhi); 194 } else { 195 mMatrix.PreRotate(aPhi); 196 } 197 } 198 199 void Translate(gfxPoint aDelta) { 200 if (mInvert) { 201 mMatrix *= gfxMatrix::Translation(-aDelta); 202 } else { 203 mMatrix.PreTranslate(aDelta); 204 } 205 } 206 207 private: 208 gfxMatrix mMatrix; 209 bool mInvert; 210 }; 211 212 gfxMatrix OrientedImage::OrientationMatrix(Orientation aOrientation, 213 const nsIntSize& aSize, 214 bool aInvert /* = false */) { 215 MatrixBuilder builder(aInvert); 216 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)); 226 } 227 builder.Scale(-1.0, 1.0); 228 } 229 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"); 249 } 250 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); 255 } 256 257 return builder.Build(); 258 } 259 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); 269 } 270 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); 276 } 277 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); 283 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); 290 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)); 298 } 299 return context; 300 }; 301 302 return InnerImage()->Draw(aContext, size, region, aWhichFrame, 303 aSamplingFilter, orientViewport(aSVGContext), 304 aFlags, aOpacity); 305 } 306 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); 314 } 315 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); 321 } 322 323 NS_IMETHODIMP_(nsIntRect) 324 OrientedImage::GetImageSpaceInvalidationRect(const nsIntRect& aRect) { 325 nsIntRect rect(InnerImage()->GetImageSpaceInvalidationRect(aRect)); 326 327 if (mOrientation.IsIdentity()) { 328 return rect; 329 } 330 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; 337 } 338 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()))); 343 344 return IntRect::RoundOut(invalidRect.X(), invalidRect.Y(), 345 invalidRect.Width(), invalidRect.Height()); 346 } 347 348 } // namespace image 349 } // namespace mozilla