|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* vim: set ts=2 et sw=2 tw=80: */ |
|
3 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
4 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
6 |
|
7 #include "imgFrame.h" |
|
8 #include "DiscardTracker.h" |
|
9 |
|
10 #include "prenv.h" |
|
11 |
|
12 #include "gfx2DGlue.h" |
|
13 #include "gfxPlatform.h" |
|
14 #include "gfxUtils.h" |
|
15 #include "gfxAlphaRecovery.h" |
|
16 |
|
17 static bool gDisableOptimize = false; |
|
18 |
|
19 #include "cairo.h" |
|
20 #include "GeckoProfiler.h" |
|
21 #include "mozilla/Likely.h" |
|
22 #include "mozilla/MemoryReporting.h" |
|
23 #include "nsMargin.h" |
|
24 #include "mozilla/CheckedInt.h" |
|
25 |
|
26 #if defined(XP_WIN) |
|
27 |
|
28 #include "gfxWindowsPlatform.h" |
|
29 |
|
30 /* Whether to use the windows surface; only for desktop win32 */ |
|
31 #define USE_WIN_SURFACE 1 |
|
32 |
|
33 #endif |
|
34 |
|
35 using namespace mozilla; |
|
36 using namespace mozilla::gfx; |
|
37 using namespace mozilla::image; |
|
38 |
|
39 static cairo_user_data_key_t kVolatileBuffer; |
|
40 |
|
41 static void |
|
42 VolatileBufferRelease(void *vbuf) |
|
43 { |
|
44 delete static_cast<VolatileBufferPtr<unsigned char>*>(vbuf); |
|
45 } |
|
46 |
|
47 gfxImageSurface * |
|
48 LockedImageSurface::CreateSurface(VolatileBuffer *vbuf, |
|
49 const gfxIntSize& size, |
|
50 gfxImageFormat format) |
|
51 { |
|
52 VolatileBufferPtr<unsigned char> *vbufptr = |
|
53 new VolatileBufferPtr<unsigned char>(vbuf); |
|
54 MOZ_ASSERT(!vbufptr->WasBufferPurged(), "Expected image data!"); |
|
55 |
|
56 long stride = gfxImageSurface::ComputeStride(size, format); |
|
57 gfxImageSurface *img = new gfxImageSurface(*vbufptr, size, stride, format); |
|
58 if (!img || img->CairoStatus()) { |
|
59 delete img; |
|
60 delete vbufptr; |
|
61 return nullptr; |
|
62 } |
|
63 |
|
64 img->SetData(&kVolatileBuffer, vbufptr, VolatileBufferRelease); |
|
65 return img; |
|
66 } |
|
67 |
|
68 TemporaryRef<VolatileBuffer> |
|
69 LockedImageSurface::AllocateBuffer(const gfxIntSize& size, |
|
70 gfxImageFormat format) |
|
71 { |
|
72 long stride = gfxImageSurface::ComputeStride(size, format); |
|
73 RefPtr<VolatileBuffer> buf = new VolatileBuffer(); |
|
74 if (buf->Init(stride * size.height, |
|
75 1 << gfxAlphaRecovery::GoodAlignmentLog2())) |
|
76 return buf; |
|
77 |
|
78 return nullptr; |
|
79 } |
|
80 |
|
81 // Returns true if an image of aWidth x aHeight is allowed and legal. |
|
82 static bool AllowedImageSize(int32_t aWidth, int32_t aHeight) |
|
83 { |
|
84 // reject over-wide or over-tall images |
|
85 const int32_t k64KLimit = 0x0000FFFF; |
|
86 if (MOZ_UNLIKELY(aWidth > k64KLimit || aHeight > k64KLimit )) { |
|
87 NS_WARNING("image too big"); |
|
88 return false; |
|
89 } |
|
90 |
|
91 // protect against invalid sizes |
|
92 if (MOZ_UNLIKELY(aHeight <= 0 || aWidth <= 0)) { |
|
93 return false; |
|
94 } |
|
95 |
|
96 // check to make sure we don't overflow a 32-bit |
|
97 CheckedInt32 requiredBytes = CheckedInt32(aWidth) * CheckedInt32(aHeight) * 4; |
|
98 if (MOZ_UNLIKELY(!requiredBytes.isValid())) { |
|
99 NS_WARNING("width or height too large"); |
|
100 return false; |
|
101 } |
|
102 #if defined(XP_MACOSX) |
|
103 // CoreGraphics is limited to images < 32K in *height*, so clamp all surfaces on the Mac to that height |
|
104 if (MOZ_UNLIKELY(aHeight > SHRT_MAX)) { |
|
105 NS_WARNING("image too big"); |
|
106 return false; |
|
107 } |
|
108 #endif |
|
109 return true; |
|
110 } |
|
111 |
|
112 // Returns whether we should, at this time, use image surfaces instead of |
|
113 // optimized platform-specific surfaces. |
|
114 static bool ShouldUseImageSurfaces() |
|
115 { |
|
116 #if defined(USE_WIN_SURFACE) |
|
117 static const DWORD kGDIObjectsHighWaterMark = 7000; |
|
118 |
|
119 if (gfxWindowsPlatform::GetPlatform()->GetRenderMode() == |
|
120 gfxWindowsPlatform::RENDER_DIRECT2D) { |
|
121 return true; |
|
122 } |
|
123 |
|
124 // at 7000 GDI objects, stop allocating normal images to make sure |
|
125 // we never hit the 10k hard limit. |
|
126 // GetCurrentProcess() just returns (HANDLE)-1, it's inlined afaik |
|
127 DWORD count = GetGuiResources(GetCurrentProcess(), GR_GDIOBJECTS); |
|
128 if (count == 0 || |
|
129 count > kGDIObjectsHighWaterMark) |
|
130 { |
|
131 // either something's broken (count == 0), |
|
132 // or we hit our high water mark; disable |
|
133 // image allocations for a bit. |
|
134 return true; |
|
135 } |
|
136 #endif |
|
137 |
|
138 return false; |
|
139 } |
|
140 |
|
141 imgFrame::imgFrame() : |
|
142 mDecoded(0, 0, 0, 0), |
|
143 mDirtyMutex("imgFrame::mDirty"), |
|
144 mPalettedImageData(nullptr), |
|
145 mSinglePixelColor(0), |
|
146 mTimeout(100), |
|
147 mDisposalMethod(0), /* imgIContainer::kDisposeNotSpecified */ |
|
148 mLockCount(0), |
|
149 mBlendMethod(1), /* imgIContainer::kBlendOver */ |
|
150 mSinglePixel(false), |
|
151 mFormatChanged(false), |
|
152 mCompositingFailed(false), |
|
153 mNonPremult(false), |
|
154 mDiscardable(false), |
|
155 mInformedDiscardTracker(false), |
|
156 mDirty(false) |
|
157 { |
|
158 static bool hasCheckedOptimize = false; |
|
159 if (!hasCheckedOptimize) { |
|
160 if (PR_GetEnv("MOZ_DISABLE_IMAGE_OPTIMIZE")) { |
|
161 gDisableOptimize = true; |
|
162 } |
|
163 hasCheckedOptimize = true; |
|
164 } |
|
165 } |
|
166 |
|
167 imgFrame::~imgFrame() |
|
168 { |
|
169 moz_free(mPalettedImageData); |
|
170 mPalettedImageData = nullptr; |
|
171 |
|
172 if (mInformedDiscardTracker) { |
|
173 DiscardTracker::InformDeallocation(4 * mSize.height * mSize.width); |
|
174 } |
|
175 } |
|
176 |
|
177 nsresult imgFrame::Init(int32_t aX, int32_t aY, int32_t aWidth, int32_t aHeight, |
|
178 gfxImageFormat aFormat, uint8_t aPaletteDepth /* = 0 */) |
|
179 { |
|
180 // assert for properties that should be verified by decoders, warn for properties related to bad content |
|
181 if (!AllowedImageSize(aWidth, aHeight)) { |
|
182 NS_WARNING("Should have legal image size"); |
|
183 return NS_ERROR_FAILURE; |
|
184 } |
|
185 |
|
186 mOffset.MoveTo(aX, aY); |
|
187 mSize.SizeTo(aWidth, aHeight); |
|
188 |
|
189 mFormat = aFormat; |
|
190 mPaletteDepth = aPaletteDepth; |
|
191 |
|
192 if (aPaletteDepth != 0) { |
|
193 // We're creating for a paletted image. |
|
194 if (aPaletteDepth > 8) { |
|
195 NS_WARNING("Should have legal palette depth"); |
|
196 NS_ERROR("This Depth is not supported"); |
|
197 return NS_ERROR_FAILURE; |
|
198 } |
|
199 |
|
200 // Use the fallible allocator here |
|
201 mPalettedImageData = (uint8_t*)moz_malloc(PaletteDataLength() + GetImageDataLength()); |
|
202 if (!mPalettedImageData) |
|
203 NS_WARNING("moz_malloc for paletted image data should succeed"); |
|
204 NS_ENSURE_TRUE(mPalettedImageData, NS_ERROR_OUT_OF_MEMORY); |
|
205 } else { |
|
206 // Inform the discard tracker that we are going to allocate some memory. |
|
207 if (!DiscardTracker::TryAllocation(4 * mSize.width * mSize.height)) { |
|
208 NS_WARNING("Exceed the hard limit of decode image size"); |
|
209 return NS_ERROR_OUT_OF_MEMORY; |
|
210 } |
|
211 // For Windows, we must create the device surface first (if we're |
|
212 // going to) so that the image surface can wrap it. Can't be done |
|
213 // the other way around. |
|
214 #ifdef USE_WIN_SURFACE |
|
215 if (!ShouldUseImageSurfaces()) { |
|
216 mWinSurface = new gfxWindowsSurface(gfxIntSize(mSize.width, mSize.height), mFormat); |
|
217 if (mWinSurface && mWinSurface->CairoStatus() == 0) { |
|
218 // no error |
|
219 mImageSurface = mWinSurface->GetAsImageSurface(); |
|
220 } else { |
|
221 mWinSurface = nullptr; |
|
222 } |
|
223 } |
|
224 #endif |
|
225 |
|
226 // For other platforms, space for the image surface is first allocated in |
|
227 // a volatile buffer and then wrapped by a LockedImageSurface. |
|
228 // This branch is also used on Windows if we're not using device surfaces |
|
229 // or if we couldn't create one. |
|
230 if (!mImageSurface) { |
|
231 mVBuf = LockedImageSurface::AllocateBuffer(mSize, mFormat); |
|
232 if (!mVBuf) { |
|
233 return NS_ERROR_OUT_OF_MEMORY; |
|
234 } |
|
235 if (mVBuf->OnHeap()) { |
|
236 long stride = gfxImageSurface::ComputeStride(mSize, mFormat); |
|
237 VolatileBufferPtr<uint8_t> ptr(mVBuf); |
|
238 memset(ptr, 0, stride * mSize.height); |
|
239 } |
|
240 mImageSurface = LockedImageSurface::CreateSurface(mVBuf, mSize, mFormat); |
|
241 } |
|
242 |
|
243 if (!mImageSurface || mImageSurface->CairoStatus()) { |
|
244 mImageSurface = nullptr; |
|
245 // guess |
|
246 if (!mImageSurface) { |
|
247 NS_WARNING("Allocation of gfxImageSurface should succeed"); |
|
248 } else if (!mImageSurface->CairoStatus()) { |
|
249 NS_WARNING("gfxImageSurface should have good CairoStatus"); |
|
250 } |
|
251 |
|
252 // Image surface allocation is failed, need to return |
|
253 // the booked buffer size. |
|
254 DiscardTracker::InformDeallocation(4 * mSize.width * mSize.height); |
|
255 return NS_ERROR_OUT_OF_MEMORY; |
|
256 } |
|
257 |
|
258 mInformedDiscardTracker = true; |
|
259 |
|
260 #ifdef XP_MACOSX |
|
261 if (!ShouldUseImageSurfaces()) { |
|
262 mQuartzSurface = new gfxQuartzImageSurface(mImageSurface); |
|
263 } |
|
264 #endif |
|
265 } |
|
266 |
|
267 return NS_OK; |
|
268 } |
|
269 |
|
270 nsresult imgFrame::Optimize() |
|
271 { |
|
272 MOZ_ASSERT(NS_IsMainThread()); |
|
273 |
|
274 if (gDisableOptimize) |
|
275 return NS_OK; |
|
276 |
|
277 if (mPalettedImageData || mOptSurface || mSinglePixel) |
|
278 return NS_OK; |
|
279 |
|
280 // Don't do single-color opts on non-premult data. |
|
281 // Cairo doesn't support non-premult single-colors. |
|
282 if (mNonPremult) |
|
283 return NS_OK; |
|
284 |
|
285 /* Figure out if the entire image is a constant color */ |
|
286 |
|
287 // this should always be true |
|
288 if (mImageSurface->Stride() == mSize.width * 4) { |
|
289 uint32_t *imgData = (uint32_t*) mImageSurface->Data(); |
|
290 uint32_t firstPixel = * (uint32_t*) imgData; |
|
291 uint32_t pixelCount = mSize.width * mSize.height + 1; |
|
292 |
|
293 while (--pixelCount && *imgData++ == firstPixel) |
|
294 ; |
|
295 |
|
296 if (pixelCount == 0) { |
|
297 // all pixels were the same |
|
298 if (mFormat == gfxImageFormat::ARGB32 || |
|
299 mFormat == gfxImageFormat::RGB24) |
|
300 { |
|
301 // Should already be premult if desired. |
|
302 gfxRGBA::PackedColorType inputType = gfxRGBA::PACKED_XRGB; |
|
303 if (mFormat == gfxImageFormat::ARGB32) |
|
304 inputType = gfxRGBA::PACKED_ARGB_PREMULTIPLIED; |
|
305 |
|
306 mSinglePixelColor = gfxRGBA(firstPixel, inputType); |
|
307 |
|
308 mSinglePixel = true; |
|
309 |
|
310 // blow away the older surfaces (if they exist), to release their memory |
|
311 mVBuf = nullptr; |
|
312 mImageSurface = nullptr; |
|
313 mOptSurface = nullptr; |
|
314 #ifdef USE_WIN_SURFACE |
|
315 mWinSurface = nullptr; |
|
316 #endif |
|
317 #ifdef XP_MACOSX |
|
318 mQuartzSurface = nullptr; |
|
319 #endif |
|
320 mDrawSurface = nullptr; |
|
321 |
|
322 // We just dumped most of our allocated memory, so tell the discard |
|
323 // tracker that we're not using any at all. |
|
324 if (mInformedDiscardTracker) { |
|
325 DiscardTracker::InformDeallocation(4 * mSize.width * mSize.height); |
|
326 mInformedDiscardTracker = false; |
|
327 } |
|
328 |
|
329 return NS_OK; |
|
330 } |
|
331 } |
|
332 |
|
333 // if it's not RGB24/ARGB32, don't optimize, but we never hit this at the moment |
|
334 } |
|
335 |
|
336 // if we're being forced to use image surfaces due to |
|
337 // resource constraints, don't try to optimize beyond same-pixel. |
|
338 if (ShouldUseImageSurfaces()) |
|
339 return NS_OK; |
|
340 |
|
341 mOptSurface = nullptr; |
|
342 |
|
343 #ifdef USE_WIN_SURFACE |
|
344 if (mWinSurface) { |
|
345 if (!mFormatChanged) { |
|
346 // just use the DIB if the format has not changed |
|
347 mOptSurface = mWinSurface; |
|
348 } |
|
349 } |
|
350 #endif |
|
351 |
|
352 #ifdef XP_MACOSX |
|
353 if (mQuartzSurface) { |
|
354 mQuartzSurface->Flush(); |
|
355 } |
|
356 #endif |
|
357 |
|
358 #ifdef ANDROID |
|
359 gfxImageFormat optFormat = |
|
360 gfxPlatform::GetPlatform()-> |
|
361 OptimalFormatForContent(gfxASurface::ContentFromFormat(mFormat)); |
|
362 |
|
363 if (optFormat == gfxImageFormat::RGB16_565) { |
|
364 RefPtr<VolatileBuffer> buf = |
|
365 LockedImageSurface::AllocateBuffer(mSize, optFormat); |
|
366 if (!buf) |
|
367 return NS_OK; |
|
368 |
|
369 nsRefPtr<gfxImageSurface> surf = |
|
370 LockedImageSurface::CreateSurface(buf, mSize, optFormat); |
|
371 |
|
372 gfxContext ctx(surf); |
|
373 ctx.SetOperator(gfxContext::OPERATOR_SOURCE); |
|
374 ctx.SetSource(mImageSurface); |
|
375 ctx.Paint(); |
|
376 |
|
377 mImageSurface = surf; |
|
378 mVBuf = buf; |
|
379 mFormat = optFormat; |
|
380 mDrawSurface = nullptr; |
|
381 } |
|
382 #else |
|
383 if (mOptSurface == nullptr) |
|
384 mOptSurface = gfxPlatform::GetPlatform()->OptimizeImage(mImageSurface, mFormat); |
|
385 #endif |
|
386 |
|
387 if (mOptSurface) { |
|
388 mVBuf = nullptr; |
|
389 mImageSurface = nullptr; |
|
390 #ifdef USE_WIN_SURFACE |
|
391 mWinSurface = nullptr; |
|
392 #endif |
|
393 #ifdef XP_MACOSX |
|
394 mQuartzSurface = nullptr; |
|
395 #endif |
|
396 mDrawSurface = nullptr; |
|
397 } |
|
398 |
|
399 return NS_OK; |
|
400 } |
|
401 |
|
402 static void |
|
403 DoSingleColorFastPath(gfxContext* aContext, |
|
404 const gfxRGBA& aSinglePixelColor, |
|
405 const gfxRect& aFill) |
|
406 { |
|
407 // if a == 0, it's a noop |
|
408 if (aSinglePixelColor.a == 0.0) |
|
409 return; |
|
410 |
|
411 gfxContext::GraphicsOperator op = aContext->CurrentOperator(); |
|
412 if (op == gfxContext::OPERATOR_OVER && aSinglePixelColor.a == 1.0) { |
|
413 aContext->SetOperator(gfxContext::OPERATOR_SOURCE); |
|
414 } |
|
415 |
|
416 aContext->SetDeviceColor(aSinglePixelColor); |
|
417 aContext->NewPath(); |
|
418 aContext->Rectangle(aFill); |
|
419 aContext->Fill(); |
|
420 aContext->SetOperator(op); |
|
421 aContext->SetDeviceColor(gfxRGBA(0,0,0,0)); |
|
422 } |
|
423 |
|
424 imgFrame::SurfaceWithFormat |
|
425 imgFrame::SurfaceForDrawing(bool aDoPadding, |
|
426 bool aDoPartialDecode, |
|
427 bool aDoTile, |
|
428 const nsIntMargin& aPadding, |
|
429 gfxMatrix& aUserSpaceToImageSpace, |
|
430 gfxRect& aFill, |
|
431 gfxRect& aSubimage, |
|
432 gfxRect& aSourceRect, |
|
433 gfxRect& aImageRect, |
|
434 gfxASurface* aSurface) |
|
435 { |
|
436 IntSize size(int32_t(aImageRect.Width()), int32_t(aImageRect.Height())); |
|
437 if (!aDoPadding && !aDoPartialDecode) { |
|
438 NS_ASSERTION(!mSinglePixel, "This should already have been handled"); |
|
439 return SurfaceWithFormat(new gfxSurfaceDrawable(aSurface, ThebesIntSize(size)), mFormat); |
|
440 } |
|
441 |
|
442 gfxRect available = gfxRect(mDecoded.x, mDecoded.y, mDecoded.width, mDecoded.height); |
|
443 |
|
444 if (aDoTile || mSinglePixel) { |
|
445 // Create a temporary surface. |
|
446 // Give this surface an alpha channel because there are |
|
447 // transparent pixels in the padding or undecoded area |
|
448 gfxImageFormat format = gfxImageFormat::ARGB32; |
|
449 nsRefPtr<gfxASurface> surface = |
|
450 gfxPlatform::GetPlatform()->CreateOffscreenSurface(size, gfxImageSurface::ContentFromFormat(format)); |
|
451 if (!surface || surface->CairoStatus()) |
|
452 return SurfaceWithFormat(); |
|
453 |
|
454 // Fill 'available' with whatever we've got |
|
455 gfxContext tmpCtx(surface); |
|
456 tmpCtx.SetOperator(gfxContext::OPERATOR_SOURCE); |
|
457 if (mSinglePixel) { |
|
458 tmpCtx.SetDeviceColor(mSinglePixelColor); |
|
459 } else { |
|
460 tmpCtx.SetSource(aSurface, gfxPoint(aPadding.left, aPadding.top)); |
|
461 } |
|
462 tmpCtx.Rectangle(available); |
|
463 tmpCtx.Fill(); |
|
464 |
|
465 return SurfaceWithFormat(new gfxSurfaceDrawable(surface, ThebesIntSize(size)), format); |
|
466 } |
|
467 |
|
468 // Not tiling, and we have a surface, so we can account for |
|
469 // padding and/or a partial decode just by twiddling parameters. |
|
470 // First, update our user-space fill rect. |
|
471 aSourceRect = aSourceRect.Intersect(available); |
|
472 gfxMatrix imageSpaceToUserSpace = aUserSpaceToImageSpace; |
|
473 imageSpaceToUserSpace.Invert(); |
|
474 aFill = imageSpaceToUserSpace.Transform(aSourceRect); |
|
475 |
|
476 aSubimage = aSubimage.Intersect(available) - gfxPoint(aPadding.left, aPadding.top); |
|
477 aUserSpaceToImageSpace.Multiply(gfxMatrix().Translate(-gfxPoint(aPadding.left, aPadding.top))); |
|
478 aSourceRect = aSourceRect - gfxPoint(aPadding.left, aPadding.top); |
|
479 aImageRect = gfxRect(0, 0, mSize.width, mSize.height); |
|
480 |
|
481 gfxIntSize availableSize(mDecoded.width, mDecoded.height); |
|
482 return SurfaceWithFormat(new gfxSurfaceDrawable(aSurface, availableSize), |
|
483 mFormat); |
|
484 } |
|
485 |
|
486 bool imgFrame::Draw(gfxContext *aContext, GraphicsFilter aFilter, |
|
487 const gfxMatrix &aUserSpaceToImageSpace, const gfxRect& aFill, |
|
488 const nsIntMargin &aPadding, const nsIntRect &aSubimage, |
|
489 uint32_t aImageFlags) |
|
490 { |
|
491 PROFILER_LABEL("image", "imgFrame::Draw"); |
|
492 NS_ASSERTION(!aFill.IsEmpty(), "zero dest size --- fix caller"); |
|
493 NS_ASSERTION(!aSubimage.IsEmpty(), "zero source size --- fix caller"); |
|
494 NS_ASSERTION(!mPalettedImageData, "Directly drawing a paletted image!"); |
|
495 |
|
496 bool doPadding = aPadding != nsIntMargin(0,0,0,0); |
|
497 bool doPartialDecode = !ImageComplete(); |
|
498 |
|
499 if (mSinglePixel && !doPadding && !doPartialDecode) { |
|
500 DoSingleColorFastPath(aContext, mSinglePixelColor, aFill); |
|
501 return true; |
|
502 } |
|
503 |
|
504 gfxMatrix userSpaceToImageSpace = aUserSpaceToImageSpace; |
|
505 gfxRect sourceRect = userSpaceToImageSpace.TransformBounds(aFill); |
|
506 gfxRect imageRect(0, 0, mSize.width + aPadding.LeftRight(), |
|
507 mSize.height + aPadding.TopBottom()); |
|
508 gfxRect subimage(aSubimage.x, aSubimage.y, aSubimage.width, aSubimage.height); |
|
509 gfxRect fill = aFill; |
|
510 |
|
511 NS_ASSERTION(!sourceRect.Intersect(subimage).IsEmpty(), |
|
512 "We must be allowed to sample *some* source pixels!"); |
|
513 |
|
514 nsRefPtr<gfxASurface> surf = CachedThebesSurface(); |
|
515 VolatileBufferPtr<unsigned char> ref(mVBuf); |
|
516 if (!mSinglePixel && !surf) { |
|
517 if (ref.WasBufferPurged()) { |
|
518 return false; |
|
519 } |
|
520 |
|
521 surf = mDrawSurface; |
|
522 if (!surf) { |
|
523 long stride = gfxImageSurface::ComputeStride(mSize, mFormat); |
|
524 nsRefPtr<gfxImageSurface> imgSurf = |
|
525 new gfxImageSurface(ref, mSize, stride, mFormat); |
|
526 #if defined(XP_MACOSX) |
|
527 surf = mDrawSurface = new gfxQuartzImageSurface(imgSurf); |
|
528 #else |
|
529 surf = mDrawSurface = imgSurf; |
|
530 #endif |
|
531 } |
|
532 if (!surf || surf->CairoStatus()) { |
|
533 mDrawSurface = nullptr; |
|
534 return true; |
|
535 } |
|
536 } |
|
537 |
|
538 bool doTile = !imageRect.Contains(sourceRect) && |
|
539 !(aImageFlags & imgIContainer::FLAG_CLAMP); |
|
540 SurfaceWithFormat surfaceResult = |
|
541 SurfaceForDrawing(doPadding, doPartialDecode, doTile, aPadding, |
|
542 userSpaceToImageSpace, fill, subimage, sourceRect, |
|
543 imageRect, surf); |
|
544 |
|
545 if (surfaceResult.IsValid()) { |
|
546 gfxUtils::DrawPixelSnapped(aContext, surfaceResult.mDrawable, |
|
547 userSpaceToImageSpace, |
|
548 subimage, sourceRect, imageRect, fill, |
|
549 surfaceResult.mFormat, aFilter, aImageFlags); |
|
550 } |
|
551 return true; |
|
552 } |
|
553 |
|
554 // This can be called from any thread, but not simultaneously. |
|
555 nsresult imgFrame::ImageUpdated(const nsIntRect &aUpdateRect) |
|
556 { |
|
557 MutexAutoLock lock(mDirtyMutex); |
|
558 |
|
559 mDecoded.UnionRect(mDecoded, aUpdateRect); |
|
560 |
|
561 // clamp to bounds, in case someone sends a bogus updateRect (I'm looking at |
|
562 // you, gif decoder) |
|
563 nsIntRect boundsRect(mOffset, mSize); |
|
564 mDecoded.IntersectRect(mDecoded, boundsRect); |
|
565 |
|
566 mDirty = true; |
|
567 |
|
568 return NS_OK; |
|
569 } |
|
570 |
|
571 bool imgFrame::GetIsDirty() const |
|
572 { |
|
573 MutexAutoLock lock(mDirtyMutex); |
|
574 return mDirty; |
|
575 } |
|
576 |
|
577 nsIntRect imgFrame::GetRect() const |
|
578 { |
|
579 return nsIntRect(mOffset, mSize); |
|
580 } |
|
581 |
|
582 gfxImageFormat imgFrame::GetFormat() const |
|
583 { |
|
584 return mFormat; |
|
585 } |
|
586 |
|
587 bool imgFrame::GetNeedsBackground() const |
|
588 { |
|
589 // We need a background painted if we have alpha or we're incomplete. |
|
590 return (mFormat == gfxImageFormat::ARGB32 || !ImageComplete()); |
|
591 } |
|
592 |
|
593 uint32_t imgFrame::GetImageBytesPerRow() const |
|
594 { |
|
595 if (mImageSurface) |
|
596 return mImageSurface->Stride(); |
|
597 |
|
598 if (mVBuf) |
|
599 return gfxImageSurface::ComputeStride(mSize, mFormat); |
|
600 |
|
601 if (mPaletteDepth) |
|
602 return mSize.width; |
|
603 |
|
604 NS_ERROR("GetImageBytesPerRow called with mImageSurface == null, mVBuf == null and mPaletteDepth == 0"); |
|
605 |
|
606 return 0; |
|
607 } |
|
608 |
|
609 uint32_t imgFrame::GetImageDataLength() const |
|
610 { |
|
611 return GetImageBytesPerRow() * mSize.height; |
|
612 } |
|
613 |
|
614 void imgFrame::GetImageData(uint8_t **aData, uint32_t *length) const |
|
615 { |
|
616 NS_ABORT_IF_FALSE(mLockCount != 0, "Can't GetImageData unless frame is locked"); |
|
617 |
|
618 if (mImageSurface) |
|
619 *aData = mImageSurface->Data(); |
|
620 else if (mPalettedImageData) |
|
621 *aData = mPalettedImageData + PaletteDataLength(); |
|
622 else |
|
623 *aData = nullptr; |
|
624 |
|
625 *length = GetImageDataLength(); |
|
626 } |
|
627 |
|
628 uint8_t* imgFrame::GetImageData() const |
|
629 { |
|
630 uint8_t *data; |
|
631 uint32_t length; |
|
632 GetImageData(&data, &length); |
|
633 return data; |
|
634 } |
|
635 |
|
636 bool imgFrame::GetIsPaletted() const |
|
637 { |
|
638 return mPalettedImageData != nullptr; |
|
639 } |
|
640 |
|
641 bool imgFrame::GetHasAlpha() const |
|
642 { |
|
643 return mFormat == gfxImageFormat::ARGB32; |
|
644 } |
|
645 |
|
646 void imgFrame::GetPaletteData(uint32_t **aPalette, uint32_t *length) const |
|
647 { |
|
648 NS_ABORT_IF_FALSE(mLockCount != 0, "Can't GetPaletteData unless frame is locked"); |
|
649 |
|
650 if (!mPalettedImageData) { |
|
651 *aPalette = nullptr; |
|
652 *length = 0; |
|
653 } else { |
|
654 *aPalette = (uint32_t *) mPalettedImageData; |
|
655 *length = PaletteDataLength(); |
|
656 } |
|
657 } |
|
658 |
|
659 uint32_t* imgFrame::GetPaletteData() const |
|
660 { |
|
661 uint32_t* data; |
|
662 uint32_t length; |
|
663 GetPaletteData(&data, &length); |
|
664 return data; |
|
665 } |
|
666 |
|
667 nsresult imgFrame::LockImageData() |
|
668 { |
|
669 MOZ_ASSERT(NS_IsMainThread()); |
|
670 |
|
671 NS_ABORT_IF_FALSE(mLockCount >= 0, "Unbalanced locks and unlocks"); |
|
672 if (mLockCount < 0) { |
|
673 return NS_ERROR_FAILURE; |
|
674 } |
|
675 |
|
676 mLockCount++; |
|
677 |
|
678 // If we are not the first lock, there's nothing to do. |
|
679 if (mLockCount != 1) { |
|
680 return NS_OK; |
|
681 } |
|
682 |
|
683 // Paletted images don't have surfaces, so there's nothing to do. |
|
684 if (mPalettedImageData) |
|
685 return NS_OK; |
|
686 |
|
687 if (!mImageSurface) { |
|
688 if (mVBuf) { |
|
689 VolatileBufferPtr<uint8_t> ref(mVBuf); |
|
690 if (ref.WasBufferPurged()) |
|
691 return NS_ERROR_FAILURE; |
|
692 |
|
693 mImageSurface = LockedImageSurface::CreateSurface(mVBuf, mSize, mFormat); |
|
694 if (!mImageSurface || mImageSurface->CairoStatus()) |
|
695 return NS_ERROR_OUT_OF_MEMORY; |
|
696 } |
|
697 if (mOptSurface || mSinglePixel || mFormat == gfxImageFormat::RGB16_565) { |
|
698 gfxImageFormat format = mFormat; |
|
699 if (mFormat == gfxImageFormat::RGB16_565) |
|
700 format = gfxImageFormat::ARGB32; |
|
701 |
|
702 // Recover the pixels |
|
703 RefPtr<VolatileBuffer> buf = |
|
704 LockedImageSurface::AllocateBuffer(mSize, format); |
|
705 if (!buf) { |
|
706 return NS_ERROR_OUT_OF_MEMORY; |
|
707 } |
|
708 |
|
709 RefPtr<gfxImageSurface> surf = |
|
710 LockedImageSurface::CreateSurface(buf, mSize, mFormat); |
|
711 if (!surf || surf->CairoStatus()) |
|
712 return NS_ERROR_OUT_OF_MEMORY; |
|
713 |
|
714 gfxContext context(surf); |
|
715 context.SetOperator(gfxContext::OPERATOR_SOURCE); |
|
716 if (mSinglePixel) |
|
717 context.SetDeviceColor(mSinglePixelColor); |
|
718 else if (mFormat == gfxImageFormat::RGB16_565) |
|
719 context.SetSource(mImageSurface); |
|
720 else |
|
721 context.SetSource(mOptSurface); |
|
722 context.Paint(); |
|
723 |
|
724 mFormat = format; |
|
725 mVBuf = buf; |
|
726 mImageSurface = surf; |
|
727 mOptSurface = nullptr; |
|
728 #ifdef USE_WIN_SURFACE |
|
729 mWinSurface = nullptr; |
|
730 #endif |
|
731 #ifdef XP_MACOSX |
|
732 mQuartzSurface = nullptr; |
|
733 #endif |
|
734 } |
|
735 } |
|
736 |
|
737 // We might write to the bits in this image surface, so we need to make the |
|
738 // surface ready for that. |
|
739 if (mImageSurface) |
|
740 mImageSurface->Flush(); |
|
741 |
|
742 #ifdef USE_WIN_SURFACE |
|
743 if (mWinSurface) |
|
744 mWinSurface->Flush(); |
|
745 #endif |
|
746 |
|
747 #ifdef XP_MACOSX |
|
748 if (!mQuartzSurface && !ShouldUseImageSurfaces()) { |
|
749 mQuartzSurface = new gfxQuartzImageSurface(mImageSurface); |
|
750 } |
|
751 #endif |
|
752 |
|
753 return NS_OK; |
|
754 } |
|
755 |
|
756 nsresult imgFrame::UnlockImageData() |
|
757 { |
|
758 MOZ_ASSERT(NS_IsMainThread()); |
|
759 |
|
760 NS_ABORT_IF_FALSE(mLockCount != 0, "Unlocking an unlocked image!"); |
|
761 if (mLockCount == 0) { |
|
762 return NS_ERROR_FAILURE; |
|
763 } |
|
764 |
|
765 mLockCount--; |
|
766 |
|
767 NS_ABORT_IF_FALSE(mLockCount >= 0, "Unbalanced locks and unlocks"); |
|
768 if (mLockCount < 0) { |
|
769 return NS_ERROR_FAILURE; |
|
770 } |
|
771 |
|
772 // If we are not the last lock, there's nothing to do. |
|
773 if (mLockCount != 0) { |
|
774 return NS_OK; |
|
775 } |
|
776 |
|
777 // Paletted images don't have surfaces, so there's nothing to do. |
|
778 if (mPalettedImageData) |
|
779 return NS_OK; |
|
780 |
|
781 // FIXME: Bug 795737 |
|
782 // If this image has been drawn since we were locked, it has had snapshots |
|
783 // added, and we need to remove them before calling MarkDirty. |
|
784 if (mImageSurface) |
|
785 mImageSurface->Flush(); |
|
786 |
|
787 #ifdef USE_WIN_SURFACE |
|
788 if (mWinSurface) |
|
789 mWinSurface->Flush(); |
|
790 #endif |
|
791 |
|
792 // Assume we've been written to. |
|
793 if (mImageSurface) |
|
794 mImageSurface->MarkDirty(); |
|
795 |
|
796 #ifdef USE_WIN_SURFACE |
|
797 if (mWinSurface) |
|
798 mWinSurface->MarkDirty(); |
|
799 #endif |
|
800 |
|
801 #ifdef XP_MACOSX |
|
802 // The quartz image surface (ab)uses the flush method to get the |
|
803 // cairo_image_surface data into a CGImage, so we have to call Flush() here. |
|
804 if (mQuartzSurface) |
|
805 mQuartzSurface->Flush(); |
|
806 #endif |
|
807 |
|
808 if (mVBuf && mDiscardable) { |
|
809 mImageSurface = nullptr; |
|
810 #ifdef XP_MACOSX |
|
811 mQuartzSurface = nullptr; |
|
812 #endif |
|
813 } |
|
814 |
|
815 return NS_OK; |
|
816 } |
|
817 |
|
818 void imgFrame::ApplyDirtToSurfaces() |
|
819 { |
|
820 MOZ_ASSERT(NS_IsMainThread()); |
|
821 |
|
822 MutexAutoLock lock(mDirtyMutex); |
|
823 if (mDirty) { |
|
824 // FIXME: Bug 795737 |
|
825 // If this image has been drawn since we were locked, it has had snapshots |
|
826 // added, and we need to remove them before calling MarkDirty. |
|
827 if (mImageSurface) |
|
828 mImageSurface->Flush(); |
|
829 |
|
830 #ifdef USE_WIN_SURFACE |
|
831 if (mWinSurface) |
|
832 mWinSurface->Flush(); |
|
833 #endif |
|
834 |
|
835 if (mImageSurface) |
|
836 mImageSurface->MarkDirty(); |
|
837 |
|
838 #ifdef USE_WIN_SURFACE |
|
839 if (mWinSurface) |
|
840 mWinSurface->MarkDirty(); |
|
841 #endif |
|
842 |
|
843 #ifdef XP_MACOSX |
|
844 // The quartz image surface (ab)uses the flush method to get the |
|
845 // cairo_image_surface data into a CGImage, so we have to call Flush() here. |
|
846 if (mQuartzSurface) |
|
847 mQuartzSurface->Flush(); |
|
848 #endif |
|
849 |
|
850 mDirty = false; |
|
851 } |
|
852 } |
|
853 |
|
854 void imgFrame::SetDiscardable() |
|
855 { |
|
856 MOZ_ASSERT(mLockCount, "Expected to be locked when SetDiscardable is called"); |
|
857 // Disabled elsewhere due to the cost of calling GetSourceSurfaceForSurface. |
|
858 #ifdef MOZ_WIDGET_ANDROID |
|
859 mDiscardable = true; |
|
860 #endif |
|
861 } |
|
862 |
|
863 int32_t imgFrame::GetRawTimeout() const |
|
864 { |
|
865 return mTimeout; |
|
866 } |
|
867 |
|
868 void imgFrame::SetRawTimeout(int32_t aTimeout) |
|
869 { |
|
870 mTimeout = aTimeout; |
|
871 } |
|
872 |
|
873 int32_t imgFrame::GetFrameDisposalMethod() const |
|
874 { |
|
875 return mDisposalMethod; |
|
876 } |
|
877 |
|
878 void imgFrame::SetFrameDisposalMethod(int32_t aFrameDisposalMethod) |
|
879 { |
|
880 mDisposalMethod = aFrameDisposalMethod; |
|
881 } |
|
882 |
|
883 int32_t imgFrame::GetBlendMethod() const |
|
884 { |
|
885 return mBlendMethod; |
|
886 } |
|
887 |
|
888 void imgFrame::SetBlendMethod(int32_t aBlendMethod) |
|
889 { |
|
890 mBlendMethod = (int8_t)aBlendMethod; |
|
891 } |
|
892 |
|
893 // This can be called from any thread. |
|
894 bool imgFrame::ImageComplete() const |
|
895 { |
|
896 MutexAutoLock lock(mDirtyMutex); |
|
897 |
|
898 return mDecoded.IsEqualInterior(nsIntRect(mOffset, mSize)); |
|
899 } |
|
900 |
|
901 // A hint from the image decoders that this image has no alpha, even |
|
902 // though we created is ARGB32. This changes our format to RGB24, |
|
903 // which in turn will cause us to Optimize() to RGB24. Has no effect |
|
904 // after Optimize() is called, though in all cases it will be just a |
|
905 // performance win -- the pixels are still correct and have the A byte |
|
906 // set to 0xff. |
|
907 void imgFrame::SetHasNoAlpha() |
|
908 { |
|
909 if (mFormat == gfxImageFormat::ARGB32) { |
|
910 mFormat = gfxImageFormat::RGB24; |
|
911 mFormatChanged = true; |
|
912 ThebesSurface()->SetOpaqueRect(gfxRect(0, 0, mSize.width, mSize.height)); |
|
913 } |
|
914 } |
|
915 |
|
916 void imgFrame::SetAsNonPremult(bool aIsNonPremult) |
|
917 { |
|
918 mNonPremult = aIsNonPremult; |
|
919 } |
|
920 |
|
921 bool imgFrame::GetCompositingFailed() const |
|
922 { |
|
923 return mCompositingFailed; |
|
924 } |
|
925 |
|
926 void imgFrame::SetCompositingFailed(bool val) |
|
927 { |
|
928 mCompositingFailed = val; |
|
929 } |
|
930 |
|
931 // If |aLocation| indicates this is heap memory, we try to measure things with |
|
932 // |aMallocSizeOf|. If that fails (because the platform doesn't support it) or |
|
933 // it's non-heap memory, we fall back to computing the size analytically. |
|
934 size_t |
|
935 imgFrame::SizeOfExcludingThisWithComputedFallbackIfHeap(gfxMemoryLocation aLocation, mozilla::MallocSizeOf aMallocSizeOf) const |
|
936 { |
|
937 // aMallocSizeOf is only used if aLocation==gfxMemoryLocation::IN_PROCESS_HEAP. It |
|
938 // should be nullptr otherwise. |
|
939 NS_ABORT_IF_FALSE( |
|
940 (aLocation == gfxMemoryLocation::IN_PROCESS_HEAP && aMallocSizeOf) || |
|
941 (aLocation != gfxMemoryLocation::IN_PROCESS_HEAP && !aMallocSizeOf), |
|
942 "mismatch between aLocation and aMallocSizeOf"); |
|
943 |
|
944 size_t n = 0; |
|
945 |
|
946 if (mPalettedImageData && aLocation == gfxMemoryLocation::IN_PROCESS_HEAP) { |
|
947 size_t n2 = aMallocSizeOf(mPalettedImageData); |
|
948 if (n2 == 0) { |
|
949 n2 = GetImageDataLength() + PaletteDataLength(); |
|
950 } |
|
951 n += n2; |
|
952 } |
|
953 |
|
954 #ifdef USE_WIN_SURFACE |
|
955 if (mWinSurface && aLocation == mWinSurface->GetMemoryLocation()) { |
|
956 n += mWinSurface->KnownMemoryUsed(); |
|
957 } else |
|
958 #endif |
|
959 #ifdef XP_MACOSX |
|
960 if (mQuartzSurface && aLocation == gfxMemoryLocation::IN_PROCESS_HEAP) { |
|
961 n += aMallocSizeOf(mQuartzSurface); |
|
962 } |
|
963 #endif |
|
964 if (mImageSurface && aLocation == mImageSurface->GetMemoryLocation()) { |
|
965 size_t n2 = 0; |
|
966 if (aLocation == gfxMemoryLocation::IN_PROCESS_HEAP) { // HEAP: measure |
|
967 n2 = mImageSurface->SizeOfIncludingThis(aMallocSizeOf); |
|
968 } |
|
969 if (n2 == 0) { // non-HEAP or computed fallback for HEAP |
|
970 n2 = mImageSurface->KnownMemoryUsed(); |
|
971 } |
|
972 n += n2; |
|
973 } |
|
974 |
|
975 if (mVBuf && aLocation == gfxMemoryLocation::IN_PROCESS_HEAP) { |
|
976 n += aMallocSizeOf(mVBuf); |
|
977 n += mVBuf->HeapSizeOfExcludingThis(aMallocSizeOf); |
|
978 } |
|
979 |
|
980 if (mVBuf && aLocation == gfxMemoryLocation::IN_PROCESS_NONHEAP) { |
|
981 n += mVBuf->NonHeapSizeOfExcludingThis(); |
|
982 } |
|
983 |
|
984 if (mOptSurface && aLocation == mOptSurface->GetMemoryLocation()) { |
|
985 size_t n2 = 0; |
|
986 if (aLocation == gfxMemoryLocation::IN_PROCESS_HEAP && |
|
987 mOptSurface->SizeOfIsMeasured()) { |
|
988 // HEAP: measure (but only if the sub-class is capable of measuring) |
|
989 n2 = mOptSurface->SizeOfIncludingThis(aMallocSizeOf); |
|
990 } |
|
991 if (n2 == 0) { // non-HEAP or computed fallback for HEAP |
|
992 n2 = mOptSurface->KnownMemoryUsed(); |
|
993 } |
|
994 n += n2; |
|
995 } |
|
996 |
|
997 return n; |
|
998 } |