|
1 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
|
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 "gfxUtils.h" |
|
7 #include "gfxContext.h" |
|
8 #include "gfxPlatform.h" |
|
9 #include "gfxDrawable.h" |
|
10 #include "mozilla/gfx/2D.h" |
|
11 #include "mozilla/RefPtr.h" |
|
12 #include "nsRegion.h" |
|
13 #include "yuv_convert.h" |
|
14 #include "ycbcr_to_rgb565.h" |
|
15 #include "GeckoProfiler.h" |
|
16 #include "ImageContainer.h" |
|
17 #include "gfx2DGlue.h" |
|
18 |
|
19 #ifdef XP_WIN |
|
20 #include "gfxWindowsPlatform.h" |
|
21 #endif |
|
22 |
|
23 using namespace mozilla; |
|
24 using namespace mozilla::layers; |
|
25 using namespace mozilla::gfx; |
|
26 |
|
27 #include "DeprecatedPremultiplyTables.h" |
|
28 |
|
29 static const uint8_t PremultiplyValue(uint8_t a, uint8_t v) { |
|
30 return gfxUtils::sPremultiplyTable[a*256+v]; |
|
31 } |
|
32 |
|
33 static const uint8_t UnpremultiplyValue(uint8_t a, uint8_t v) { |
|
34 return gfxUtils::sUnpremultiplyTable[a*256+v]; |
|
35 } |
|
36 |
|
37 void |
|
38 gfxUtils::PremultiplyImageSurface(gfxImageSurface *aSourceSurface, |
|
39 gfxImageSurface *aDestSurface) |
|
40 { |
|
41 if (!aDestSurface) |
|
42 aDestSurface = aSourceSurface; |
|
43 |
|
44 MOZ_ASSERT(aSourceSurface->Format() == aDestSurface->Format() && |
|
45 aSourceSurface->Width() == aDestSurface->Width() && |
|
46 aSourceSurface->Height() == aDestSurface->Height() && |
|
47 aSourceSurface->Stride() == aDestSurface->Stride(), |
|
48 "Source and destination surfaces don't have identical characteristics"); |
|
49 |
|
50 MOZ_ASSERT(aSourceSurface->Stride() == aSourceSurface->Width() * 4, |
|
51 "Source surface stride isn't tightly packed"); |
|
52 |
|
53 // Only premultiply ARGB32 |
|
54 if (aSourceSurface->Format() != gfxImageFormat::ARGB32) { |
|
55 if (aDestSurface != aSourceSurface) { |
|
56 memcpy(aDestSurface->Data(), aSourceSurface->Data(), |
|
57 aSourceSurface->Stride() * aSourceSurface->Height()); |
|
58 } |
|
59 return; |
|
60 } |
|
61 |
|
62 uint8_t *src = aSourceSurface->Data(); |
|
63 uint8_t *dst = aDestSurface->Data(); |
|
64 |
|
65 uint32_t dim = aSourceSurface->Width() * aSourceSurface->Height(); |
|
66 for (uint32_t i = 0; i < dim; ++i) { |
|
67 #ifdef IS_LITTLE_ENDIAN |
|
68 uint8_t b = *src++; |
|
69 uint8_t g = *src++; |
|
70 uint8_t r = *src++; |
|
71 uint8_t a = *src++; |
|
72 |
|
73 *dst++ = PremultiplyValue(a, b); |
|
74 *dst++ = PremultiplyValue(a, g); |
|
75 *dst++ = PremultiplyValue(a, r); |
|
76 *dst++ = a; |
|
77 #else |
|
78 uint8_t a = *src++; |
|
79 uint8_t r = *src++; |
|
80 uint8_t g = *src++; |
|
81 uint8_t b = *src++; |
|
82 |
|
83 *dst++ = a; |
|
84 *dst++ = PremultiplyValue(a, r); |
|
85 *dst++ = PremultiplyValue(a, g); |
|
86 *dst++ = PremultiplyValue(a, b); |
|
87 #endif |
|
88 } |
|
89 } |
|
90 |
|
91 void |
|
92 gfxUtils::UnpremultiplyImageSurface(gfxImageSurface *aSourceSurface, |
|
93 gfxImageSurface *aDestSurface) |
|
94 { |
|
95 if (!aDestSurface) |
|
96 aDestSurface = aSourceSurface; |
|
97 |
|
98 MOZ_ASSERT(aSourceSurface->Format() == aDestSurface->Format() && |
|
99 aSourceSurface->Width() == aDestSurface->Width() && |
|
100 aSourceSurface->Height() == aDestSurface->Height(), |
|
101 "Source and destination surfaces don't have identical characteristics"); |
|
102 |
|
103 // Only premultiply ARGB32 |
|
104 if (aSourceSurface->Format() != gfxImageFormat::ARGB32) { |
|
105 if (aDestSurface != aSourceSurface) { |
|
106 aDestSurface->CopyFrom(aSourceSurface); |
|
107 } |
|
108 return; |
|
109 } |
|
110 |
|
111 uint8_t *src = aSourceSurface->Data(); |
|
112 uint8_t *dst = aDestSurface->Data(); |
|
113 |
|
114 for (int32_t i = 0; i < aSourceSurface->Height(); ++i) { |
|
115 uint8_t *srcRow = src + (i * aSourceSurface->Stride()); |
|
116 uint8_t *dstRow = dst + (i * aDestSurface->Stride()); |
|
117 |
|
118 for (int32_t j = 0; j < aSourceSurface->Width(); ++j) { |
|
119 #ifdef IS_LITTLE_ENDIAN |
|
120 uint8_t b = *srcRow++; |
|
121 uint8_t g = *srcRow++; |
|
122 uint8_t r = *srcRow++; |
|
123 uint8_t a = *srcRow++; |
|
124 |
|
125 *dstRow++ = UnpremultiplyValue(a, b); |
|
126 *dstRow++ = UnpremultiplyValue(a, g); |
|
127 *dstRow++ = UnpremultiplyValue(a, r); |
|
128 *dstRow++ = a; |
|
129 #else |
|
130 uint8_t a = *srcRow++; |
|
131 uint8_t r = *srcRow++; |
|
132 uint8_t g = *srcRow++; |
|
133 uint8_t b = *srcRow++; |
|
134 |
|
135 *dstRow++ = a; |
|
136 *dstRow++ = UnpremultiplyValue(a, r); |
|
137 *dstRow++ = UnpremultiplyValue(a, g); |
|
138 *dstRow++ = UnpremultiplyValue(a, b); |
|
139 #endif |
|
140 } |
|
141 } |
|
142 } |
|
143 |
|
144 TemporaryRef<DataSourceSurface> |
|
145 gfxUtils::UnpremultiplyDataSurface(DataSourceSurface* aSurface) |
|
146 { |
|
147 // Only premultiply ARGB32 |
|
148 if (aSurface->GetFormat() != SurfaceFormat::B8G8R8A8) { |
|
149 return aSurface; |
|
150 } |
|
151 |
|
152 DataSourceSurface::MappedSurface map; |
|
153 if (!aSurface->Map(DataSourceSurface::MapType::READ, &map)) { |
|
154 return nullptr; |
|
155 } |
|
156 |
|
157 RefPtr<DataSourceSurface> dest = Factory::CreateDataSourceSurfaceWithStride(aSurface->GetSize(), |
|
158 aSurface->GetFormat(), |
|
159 map.mStride); |
|
160 |
|
161 DataSourceSurface::MappedSurface destMap; |
|
162 if (!dest->Map(DataSourceSurface::MapType::WRITE, &destMap)) { |
|
163 aSurface->Unmap(); |
|
164 return nullptr; |
|
165 } |
|
166 |
|
167 uint8_t *src = map.mData; |
|
168 uint8_t *dst = destMap.mData; |
|
169 |
|
170 for (int32_t i = 0; i < aSurface->GetSize().height; ++i) { |
|
171 uint8_t *srcRow = src + (i * map.mStride); |
|
172 uint8_t *dstRow = dst + (i * destMap.mStride); |
|
173 |
|
174 for (int32_t j = 0; j < aSurface->GetSize().width; ++j) { |
|
175 #ifdef IS_LITTLE_ENDIAN |
|
176 uint8_t b = *srcRow++; |
|
177 uint8_t g = *srcRow++; |
|
178 uint8_t r = *srcRow++; |
|
179 uint8_t a = *srcRow++; |
|
180 |
|
181 *dstRow++ = UnpremultiplyValue(a, b); |
|
182 *dstRow++ = UnpremultiplyValue(a, g); |
|
183 *dstRow++ = UnpremultiplyValue(a, r); |
|
184 *dstRow++ = a; |
|
185 #else |
|
186 uint8_t a = *srcRow++; |
|
187 uint8_t r = *srcRow++; |
|
188 uint8_t g = *srcRow++; |
|
189 uint8_t b = *srcRow++; |
|
190 |
|
191 *dstRow++ = a; |
|
192 *dstRow++ = UnpremultiplyValue(a, r); |
|
193 *dstRow++ = UnpremultiplyValue(a, g); |
|
194 *dstRow++ = UnpremultiplyValue(a, b); |
|
195 #endif |
|
196 } |
|
197 } |
|
198 |
|
199 aSurface->Unmap(); |
|
200 dest->Unmap(); |
|
201 return dest; |
|
202 } |
|
203 |
|
204 void |
|
205 gfxUtils::ConvertBGRAtoRGBA(gfxImageSurface *aSourceSurface, |
|
206 gfxImageSurface *aDestSurface) { |
|
207 if (!aDestSurface) |
|
208 aDestSurface = aSourceSurface; |
|
209 |
|
210 MOZ_ASSERT(aSourceSurface->Format() == aDestSurface->Format() && |
|
211 aSourceSurface->Width() == aDestSurface->Width() && |
|
212 aSourceSurface->Height() == aDestSurface->Height() && |
|
213 aSourceSurface->Stride() == aDestSurface->Stride(), |
|
214 "Source and destination surfaces don't have identical characteristics"); |
|
215 |
|
216 MOZ_ASSERT(aSourceSurface->Stride() == aSourceSurface->Width() * 4, |
|
217 "Source surface stride isn't tightly packed"); |
|
218 |
|
219 MOZ_ASSERT(aSourceSurface->Format() == gfxImageFormat::ARGB32 || aSourceSurface->Format() == gfxImageFormat::RGB24, |
|
220 "Surfaces must be ARGB32 or RGB24"); |
|
221 |
|
222 uint8_t *src = aSourceSurface->Data(); |
|
223 uint8_t *dst = aDestSurface->Data(); |
|
224 |
|
225 uint32_t dim = aSourceSurface->Width() * aSourceSurface->Height(); |
|
226 uint8_t *srcEnd = src + 4*dim; |
|
227 |
|
228 if (src == dst) { |
|
229 uint8_t buffer[4]; |
|
230 for (; src != srcEnd; src += 4) { |
|
231 buffer[0] = src[2]; |
|
232 buffer[1] = src[1]; |
|
233 buffer[2] = src[0]; |
|
234 |
|
235 src[0] = buffer[0]; |
|
236 src[1] = buffer[1]; |
|
237 src[2] = buffer[2]; |
|
238 } |
|
239 } else { |
|
240 for (; src != srcEnd; src += 4, dst += 4) { |
|
241 dst[0] = src[2]; |
|
242 dst[1] = src[1]; |
|
243 dst[2] = src[0]; |
|
244 dst[3] = src[3]; |
|
245 } |
|
246 } |
|
247 } |
|
248 |
|
249 void |
|
250 gfxUtils::ConvertBGRAtoRGBA(uint8_t* aData, uint32_t aLength) |
|
251 { |
|
252 uint8_t *src = aData; |
|
253 uint8_t *srcEnd = src + aLength; |
|
254 |
|
255 uint8_t buffer[4]; |
|
256 for (; src != srcEnd; src += 4) { |
|
257 buffer[0] = src[2]; |
|
258 buffer[1] = src[1]; |
|
259 buffer[2] = src[0]; |
|
260 |
|
261 src[0] = buffer[0]; |
|
262 src[1] = buffer[1]; |
|
263 src[2] = buffer[2]; |
|
264 } |
|
265 } |
|
266 |
|
267 static bool |
|
268 IsSafeImageTransformComponent(gfxFloat aValue) |
|
269 { |
|
270 return aValue >= -32768 && aValue <= 32767; |
|
271 } |
|
272 |
|
273 #ifndef MOZ_GFX_OPTIMIZE_MOBILE |
|
274 /** |
|
275 * This returns the fastest operator to use for solid surfaces which have no |
|
276 * alpha channel or their alpha channel is uniformly opaque. |
|
277 * This differs per render mode. |
|
278 */ |
|
279 static gfxContext::GraphicsOperator |
|
280 OptimalFillOperator() |
|
281 { |
|
282 #ifdef XP_WIN |
|
283 if (gfxWindowsPlatform::GetPlatform()->GetRenderMode() == |
|
284 gfxWindowsPlatform::RENDER_DIRECT2D) { |
|
285 // D2D -really- hates operator source. |
|
286 return gfxContext::OPERATOR_OVER; |
|
287 } else { |
|
288 #endif |
|
289 return gfxContext::OPERATOR_SOURCE; |
|
290 #ifdef XP_WIN |
|
291 } |
|
292 #endif |
|
293 } |
|
294 |
|
295 // EXTEND_PAD won't help us here; we have to create a temporary surface to hold |
|
296 // the subimage of pixels we're allowed to sample. |
|
297 static already_AddRefed<gfxDrawable> |
|
298 CreateSamplingRestrictedDrawable(gfxDrawable* aDrawable, |
|
299 gfxContext* aContext, |
|
300 const gfxMatrix& aUserSpaceToImageSpace, |
|
301 const gfxRect& aSourceRect, |
|
302 const gfxRect& aSubimage, |
|
303 const gfxImageFormat aFormat) |
|
304 { |
|
305 PROFILER_LABEL("gfxUtils", "CreateSamplingRestricedDrawable"); |
|
306 gfxRect userSpaceClipExtents = aContext->GetClipExtents(); |
|
307 // This isn't optimal --- if aContext has a rotation then GetClipExtents |
|
308 // will have to do a bounding-box computation, and TransformBounds might |
|
309 // too, so we could get a better result if we computed image space clip |
|
310 // extents in one go --- but it doesn't really matter and this is easier |
|
311 // to understand. |
|
312 gfxRect imageSpaceClipExtents = |
|
313 aUserSpaceToImageSpace.TransformBounds(userSpaceClipExtents); |
|
314 // Inflate by one pixel because bilinear filtering will sample at most |
|
315 // one pixel beyond the computed image pixel coordinate. |
|
316 imageSpaceClipExtents.Inflate(1.0); |
|
317 |
|
318 gfxRect needed = imageSpaceClipExtents.Intersect(aSourceRect); |
|
319 needed = needed.Intersect(aSubimage); |
|
320 needed.RoundOut(); |
|
321 |
|
322 // if 'needed' is empty, nothing will be drawn since aFill |
|
323 // must be entirely outside the clip region, so it doesn't |
|
324 // matter what we do here, but we should avoid trying to |
|
325 // create a zero-size surface. |
|
326 if (needed.IsEmpty()) |
|
327 return nullptr; |
|
328 |
|
329 nsRefPtr<gfxDrawable> drawable; |
|
330 gfxIntSize size(int32_t(needed.Width()), int32_t(needed.Height())); |
|
331 |
|
332 nsRefPtr<gfxImageSurface> image = aDrawable->GetAsImageSurface(); |
|
333 if (image && gfxRect(0, 0, image->GetSize().width, image->GetSize().height).Contains(needed)) { |
|
334 nsRefPtr<gfxASurface> temp = image->GetSubimage(needed); |
|
335 drawable = new gfxSurfaceDrawable(temp, size, gfxMatrix().Translate(-needed.TopLeft())); |
|
336 } else { |
|
337 mozilla::RefPtr<mozilla::gfx::DrawTarget> target = |
|
338 gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(ToIntSize(size), |
|
339 ImageFormatToSurfaceFormat(aFormat)); |
|
340 if (!target) { |
|
341 return nullptr; |
|
342 } |
|
343 |
|
344 nsRefPtr<gfxContext> tmpCtx = new gfxContext(target); |
|
345 tmpCtx->SetOperator(OptimalFillOperator()); |
|
346 aDrawable->Draw(tmpCtx, needed - needed.TopLeft(), true, |
|
347 GraphicsFilter::FILTER_FAST, gfxMatrix().Translate(needed.TopLeft())); |
|
348 drawable = new gfxSurfaceDrawable(target, size, gfxMatrix().Translate(-needed.TopLeft())); |
|
349 } |
|
350 |
|
351 return drawable.forget(); |
|
352 } |
|
353 #endif // !MOZ_GFX_OPTIMIZE_MOBILE |
|
354 |
|
355 // working around cairo/pixman bug (bug 364968) |
|
356 // Our device-space-to-image-space transform may not be acceptable to pixman. |
|
357 struct MOZ_STACK_CLASS AutoCairoPixmanBugWorkaround |
|
358 { |
|
359 AutoCairoPixmanBugWorkaround(gfxContext* aContext, |
|
360 const gfxMatrix& aDeviceSpaceToImageSpace, |
|
361 const gfxRect& aFill, |
|
362 const gfxASurface* aSurface) |
|
363 : mContext(aContext), mSucceeded(true), mPushedGroup(false) |
|
364 { |
|
365 // Quartz's limits for matrix are much larger than pixman |
|
366 if (!aSurface || aSurface->GetType() == gfxSurfaceType::Quartz) |
|
367 return; |
|
368 |
|
369 if (!IsSafeImageTransformComponent(aDeviceSpaceToImageSpace.xx) || |
|
370 !IsSafeImageTransformComponent(aDeviceSpaceToImageSpace.xy) || |
|
371 !IsSafeImageTransformComponent(aDeviceSpaceToImageSpace.yx) || |
|
372 !IsSafeImageTransformComponent(aDeviceSpaceToImageSpace.yy)) { |
|
373 NS_WARNING("Scaling up too much, bailing out"); |
|
374 mSucceeded = false; |
|
375 return; |
|
376 } |
|
377 |
|
378 if (IsSafeImageTransformComponent(aDeviceSpaceToImageSpace.x0) && |
|
379 IsSafeImageTransformComponent(aDeviceSpaceToImageSpace.y0)) |
|
380 return; |
|
381 |
|
382 // We'll push a group, which will hopefully reduce our transform's |
|
383 // translation so it's in bounds. |
|
384 gfxMatrix currentMatrix = mContext->CurrentMatrix(); |
|
385 mContext->Save(); |
|
386 |
|
387 // Clip the rounded-out-to-device-pixels bounds of the |
|
388 // transformed fill area. This is the area for the group we |
|
389 // want to push. |
|
390 mContext->IdentityMatrix(); |
|
391 gfxRect bounds = currentMatrix.TransformBounds(aFill); |
|
392 bounds.RoundOut(); |
|
393 mContext->Clip(bounds); |
|
394 mContext->SetMatrix(currentMatrix); |
|
395 mContext->PushGroup(gfxContentType::COLOR_ALPHA); |
|
396 mContext->SetOperator(gfxContext::OPERATOR_OVER); |
|
397 |
|
398 mPushedGroup = true; |
|
399 } |
|
400 |
|
401 ~AutoCairoPixmanBugWorkaround() |
|
402 { |
|
403 if (mPushedGroup) { |
|
404 mContext->PopGroupToSource(); |
|
405 mContext->Paint(); |
|
406 mContext->Restore(); |
|
407 } |
|
408 } |
|
409 |
|
410 bool PushedGroup() { return mPushedGroup; } |
|
411 bool Succeeded() { return mSucceeded; } |
|
412 |
|
413 private: |
|
414 gfxContext* mContext; |
|
415 bool mSucceeded; |
|
416 bool mPushedGroup; |
|
417 }; |
|
418 |
|
419 static gfxMatrix |
|
420 DeviceToImageTransform(gfxContext* aContext, |
|
421 const gfxMatrix& aUserSpaceToImageSpace) |
|
422 { |
|
423 gfxFloat deviceX, deviceY; |
|
424 nsRefPtr<gfxASurface> currentTarget = |
|
425 aContext->CurrentSurface(&deviceX, &deviceY); |
|
426 gfxMatrix currentMatrix = aContext->CurrentMatrix(); |
|
427 gfxMatrix deviceToUser = gfxMatrix(currentMatrix).Invert(); |
|
428 deviceToUser.Translate(-gfxPoint(-deviceX, -deviceY)); |
|
429 return gfxMatrix(deviceToUser).Multiply(aUserSpaceToImageSpace); |
|
430 } |
|
431 |
|
432 /* These heuristics are based on Source/WebCore/platform/graphics/skia/ImageSkia.cpp:computeResamplingMode() */ |
|
433 #ifdef MOZ_GFX_OPTIMIZE_MOBILE |
|
434 static GraphicsFilter ReduceResamplingFilter(GraphicsFilter aFilter, |
|
435 int aImgWidth, int aImgHeight, |
|
436 float aSourceWidth, float aSourceHeight) |
|
437 { |
|
438 // Images smaller than this in either direction are considered "small" and |
|
439 // are not resampled ever (see below). |
|
440 const int kSmallImageSizeThreshold = 8; |
|
441 |
|
442 // The amount an image can be stretched in a single direction before we |
|
443 // say that it is being stretched so much that it must be a line or |
|
444 // background that doesn't need resampling. |
|
445 const float kLargeStretch = 3.0f; |
|
446 |
|
447 if (aImgWidth <= kSmallImageSizeThreshold |
|
448 || aImgHeight <= kSmallImageSizeThreshold) { |
|
449 // Never resample small images. These are often used for borders and |
|
450 // rules (think 1x1 images used to make lines). |
|
451 return GraphicsFilter::FILTER_NEAREST; |
|
452 } |
|
453 |
|
454 if (aImgHeight * kLargeStretch <= aSourceHeight || aImgWidth * kLargeStretch <= aSourceWidth) { |
|
455 // Large image tiling detected. |
|
456 |
|
457 // Don't resample if it is being tiled a lot in only one direction. |
|
458 // This is trying to catch cases where somebody has created a border |
|
459 // (which might be large) and then is stretching it to fill some part |
|
460 // of the page. |
|
461 if (fabs(aSourceWidth - aImgWidth)/aImgWidth < 0.5 || fabs(aSourceHeight - aImgHeight)/aImgHeight < 0.5) |
|
462 return GraphicsFilter::FILTER_NEAREST; |
|
463 |
|
464 // The image is growing a lot and in more than one direction. Resampling |
|
465 // is slow and doesn't give us very much when growing a lot. |
|
466 return aFilter; |
|
467 } |
|
468 |
|
469 /* Some notes on other heuristics: |
|
470 The Skia backend also uses nearest for backgrounds that are stretched by |
|
471 a large amount. I'm not sure this is common enough for us to worry about |
|
472 now. It also uses nearest for backgrounds/avoids high quality for images |
|
473 that are very slightly scaled. I'm also not sure that very slightly |
|
474 scaled backgrounds are common enough us to worry about. |
|
475 |
|
476 We don't currently have much support for doing high quality interpolation. |
|
477 The only place this currently happens is on Quartz and we don't have as |
|
478 much control over it as would be needed. Webkit avoids using high quality |
|
479 resampling during load. It also avoids high quality if the transformation |
|
480 is not just a scale and translation |
|
481 |
|
482 WebKit bug #40045 added code to avoid resampling different parts |
|
483 of an image with different methods by using a resampling hint size. |
|
484 It currently looks unused in WebKit but it's something to watch out for. |
|
485 */ |
|
486 |
|
487 return aFilter; |
|
488 } |
|
489 #else |
|
490 static GraphicsFilter ReduceResamplingFilter(GraphicsFilter aFilter, |
|
491 int aImgWidth, int aImgHeight, |
|
492 int aSourceWidth, int aSourceHeight) |
|
493 { |
|
494 // Just pass the filter through unchanged |
|
495 return aFilter; |
|
496 } |
|
497 #endif |
|
498 |
|
499 /* static */ void |
|
500 gfxUtils::DrawPixelSnapped(gfxContext* aContext, |
|
501 gfxDrawable* aDrawable, |
|
502 const gfxMatrix& aUserSpaceToImageSpace, |
|
503 const gfxRect& aSubimage, |
|
504 const gfxRect& aSourceRect, |
|
505 const gfxRect& aImageRect, |
|
506 const gfxRect& aFill, |
|
507 const gfxImageFormat aFormat, |
|
508 GraphicsFilter aFilter, |
|
509 uint32_t aImageFlags) |
|
510 { |
|
511 PROFILER_LABEL("gfxUtils", "DrawPixelSnapped"); |
|
512 bool doTile = !aImageRect.Contains(aSourceRect) && |
|
513 !(aImageFlags & imgIContainer::FLAG_CLAMP); |
|
514 |
|
515 nsRefPtr<gfxASurface> currentTarget = aContext->CurrentSurface(); |
|
516 gfxMatrix deviceSpaceToImageSpace = |
|
517 DeviceToImageTransform(aContext, aUserSpaceToImageSpace); |
|
518 |
|
519 AutoCairoPixmanBugWorkaround workaround(aContext, deviceSpaceToImageSpace, |
|
520 aFill, currentTarget); |
|
521 if (!workaround.Succeeded()) |
|
522 return; |
|
523 |
|
524 nsRefPtr<gfxDrawable> drawable = aDrawable; |
|
525 |
|
526 aFilter = ReduceResamplingFilter(aFilter, aImageRect.Width(), aImageRect.Height(), aSourceRect.Width(), aSourceRect.Height()); |
|
527 |
|
528 gfxMatrix userSpaceToImageSpace = aUserSpaceToImageSpace; |
|
529 |
|
530 // On Mobile, we don't ever want to do this; it has the potential for |
|
531 // allocating very large temporary surfaces, especially since we'll |
|
532 // do full-page snapshots often (see bug 749426). |
|
533 #ifdef MOZ_GFX_OPTIMIZE_MOBILE |
|
534 // If the pattern translation is large we can get into trouble with pixman's |
|
535 // 16 bit coordinate limits. For now, we only do this on platforms where |
|
536 // we know we have the pixman limits. 16384.0 is a somewhat arbitrary |
|
537 // large number to make sure we avoid the expensive fmod when we can, but |
|
538 // still maintain a safe margin from the actual limit |
|
539 if (doTile && (userSpaceToImageSpace.y0 > 16384.0 || userSpaceToImageSpace.x0 > 16384.0)) { |
|
540 userSpaceToImageSpace.x0 = fmod(userSpaceToImageSpace.x0, aImageRect.width); |
|
541 userSpaceToImageSpace.y0 = fmod(userSpaceToImageSpace.y0, aImageRect.height); |
|
542 } |
|
543 #else |
|
544 // OK now, the hard part left is to account for the subimage sampling |
|
545 // restriction. If all the transforms involved are just integer |
|
546 // translations, then we assume no resampling will occur so there's |
|
547 // nothing to do. |
|
548 // XXX if only we had source-clipping in cairo! |
|
549 if (aContext->CurrentMatrix().HasNonIntegerTranslation() || |
|
550 aUserSpaceToImageSpace.HasNonIntegerTranslation()) { |
|
551 if (doTile || !aSubimage.Contains(aImageRect)) { |
|
552 nsRefPtr<gfxDrawable> restrictedDrawable = |
|
553 CreateSamplingRestrictedDrawable(aDrawable, aContext, |
|
554 aUserSpaceToImageSpace, aSourceRect, |
|
555 aSubimage, aFormat); |
|
556 if (restrictedDrawable) { |
|
557 drawable.swap(restrictedDrawable); |
|
558 } |
|
559 } |
|
560 // We no longer need to tile: Either we never needed to, or we already |
|
561 // filled a surface with the tiled pattern; this surface can now be |
|
562 // drawn without tiling. |
|
563 doTile = false; |
|
564 } |
|
565 #endif |
|
566 |
|
567 drawable->Draw(aContext, aFill, doTile, aFilter, userSpaceToImageSpace); |
|
568 } |
|
569 |
|
570 /* static */ int |
|
571 gfxUtils::ImageFormatToDepth(gfxImageFormat aFormat) |
|
572 { |
|
573 switch (aFormat) { |
|
574 case gfxImageFormat::ARGB32: |
|
575 return 32; |
|
576 case gfxImageFormat::RGB24: |
|
577 return 24; |
|
578 case gfxImageFormat::RGB16_565: |
|
579 return 16; |
|
580 default: |
|
581 break; |
|
582 } |
|
583 return 0; |
|
584 } |
|
585 |
|
586 static void |
|
587 PathFromRegionInternal(gfxContext* aContext, const nsIntRegion& aRegion, |
|
588 bool aSnap) |
|
589 { |
|
590 aContext->NewPath(); |
|
591 nsIntRegionRectIterator iter(aRegion); |
|
592 const nsIntRect* r; |
|
593 while ((r = iter.Next()) != nullptr) { |
|
594 aContext->Rectangle(gfxRect(r->x, r->y, r->width, r->height), aSnap); |
|
595 } |
|
596 } |
|
597 |
|
598 static void |
|
599 ClipToRegionInternal(gfxContext* aContext, const nsIntRegion& aRegion, |
|
600 bool aSnap) |
|
601 { |
|
602 PathFromRegionInternal(aContext, aRegion, aSnap); |
|
603 aContext->Clip(); |
|
604 } |
|
605 |
|
606 static TemporaryRef<Path> |
|
607 PathFromRegionInternal(DrawTarget* aTarget, const nsIntRegion& aRegion, |
|
608 bool aSnap) |
|
609 { |
|
610 Matrix mat = aTarget->GetTransform(); |
|
611 const gfxFloat epsilon = 0.000001; |
|
612 #define WITHIN_E(a,b) (fabs((a)-(b)) < epsilon) |
|
613 // We're essentially duplicating the logic in UserToDevicePixelSnapped here. |
|
614 bool shouldNotSnap = !aSnap || (WITHIN_E(mat._11,1.0) && |
|
615 WITHIN_E(mat._22,1.0) && |
|
616 WITHIN_E(mat._12,0.0) && |
|
617 WITHIN_E(mat._21,0.0)); |
|
618 #undef WITHIN_E |
|
619 |
|
620 RefPtr<PathBuilder> pb = aTarget->CreatePathBuilder(); |
|
621 nsIntRegionRectIterator iter(aRegion); |
|
622 |
|
623 const nsIntRect* r; |
|
624 if (shouldNotSnap) { |
|
625 while ((r = iter.Next()) != nullptr) { |
|
626 pb->MoveTo(Point(r->x, r->y)); |
|
627 pb->LineTo(Point(r->XMost(), r->y)); |
|
628 pb->LineTo(Point(r->XMost(), r->YMost())); |
|
629 pb->LineTo(Point(r->x, r->YMost())); |
|
630 pb->Close(); |
|
631 } |
|
632 } else { |
|
633 while ((r = iter.Next()) != nullptr) { |
|
634 Rect rect(r->x, r->y, r->width, r->height); |
|
635 |
|
636 rect.Round(); |
|
637 pb->MoveTo(rect.TopLeft()); |
|
638 pb->LineTo(rect.TopRight()); |
|
639 pb->LineTo(rect.BottomRight()); |
|
640 pb->LineTo(rect.BottomLeft()); |
|
641 pb->Close(); |
|
642 } |
|
643 } |
|
644 RefPtr<Path> path = pb->Finish(); |
|
645 return path; |
|
646 } |
|
647 |
|
648 static void |
|
649 ClipToRegionInternal(DrawTarget* aTarget, const nsIntRegion& aRegion, |
|
650 bool aSnap) |
|
651 { |
|
652 RefPtr<Path> path = PathFromRegionInternal(aTarget, aRegion, aSnap); |
|
653 aTarget->PushClip(path); |
|
654 } |
|
655 |
|
656 /*static*/ void |
|
657 gfxUtils::ClipToRegion(gfxContext* aContext, const nsIntRegion& aRegion) |
|
658 { |
|
659 ClipToRegionInternal(aContext, aRegion, false); |
|
660 } |
|
661 |
|
662 /*static*/ void |
|
663 gfxUtils::ClipToRegion(DrawTarget* aTarget, const nsIntRegion& aRegion) |
|
664 { |
|
665 ClipToRegionInternal(aTarget, aRegion, false); |
|
666 } |
|
667 |
|
668 /*static*/ void |
|
669 gfxUtils::ClipToRegionSnapped(gfxContext* aContext, const nsIntRegion& aRegion) |
|
670 { |
|
671 ClipToRegionInternal(aContext, aRegion, true); |
|
672 } |
|
673 |
|
674 /*static*/ void |
|
675 gfxUtils::ClipToRegionSnapped(DrawTarget* aTarget, const nsIntRegion& aRegion) |
|
676 { |
|
677 ClipToRegionInternal(aTarget, aRegion, true); |
|
678 } |
|
679 |
|
680 /*static*/ gfxFloat |
|
681 gfxUtils::ClampToScaleFactor(gfxFloat aVal) |
|
682 { |
|
683 // Arbitary scale factor limitation. We can increase this |
|
684 // for better scaling performance at the cost of worse |
|
685 // quality. |
|
686 static const gfxFloat kScaleResolution = 2; |
|
687 |
|
688 // Negative scaling is just a flip and irrelevant to |
|
689 // our resolution calculation. |
|
690 if (aVal < 0.0) { |
|
691 aVal = -aVal; |
|
692 } |
|
693 |
|
694 bool inverse = false; |
|
695 if (aVal < 1.0) { |
|
696 inverse = true; |
|
697 aVal = 1 / aVal; |
|
698 } |
|
699 |
|
700 gfxFloat power = log(aVal)/log(kScaleResolution); |
|
701 |
|
702 // If power is within 1e-6 of an integer, round to nearest to |
|
703 // prevent floating point errors, otherwise round up to the |
|
704 // next integer value. |
|
705 if (fabs(power - NS_round(power)) < 1e-6) { |
|
706 power = NS_round(power); |
|
707 } else if (inverse) { |
|
708 power = floor(power); |
|
709 } else { |
|
710 power = ceil(power); |
|
711 } |
|
712 |
|
713 gfxFloat scale = pow(kScaleResolution, power); |
|
714 |
|
715 if (inverse) { |
|
716 scale = 1 / scale; |
|
717 } |
|
718 |
|
719 return scale; |
|
720 } |
|
721 |
|
722 |
|
723 /*static*/ void |
|
724 gfxUtils::PathFromRegion(gfxContext* aContext, const nsIntRegion& aRegion) |
|
725 { |
|
726 PathFromRegionInternal(aContext, aRegion, false); |
|
727 } |
|
728 |
|
729 /*static*/ void |
|
730 gfxUtils::PathFromRegionSnapped(gfxContext* aContext, const nsIntRegion& aRegion) |
|
731 { |
|
732 PathFromRegionInternal(aContext, aRegion, true); |
|
733 } |
|
734 |
|
735 gfxMatrix |
|
736 gfxUtils::TransformRectToRect(const gfxRect& aFrom, const gfxPoint& aToTopLeft, |
|
737 const gfxPoint& aToTopRight, const gfxPoint& aToBottomRight) |
|
738 { |
|
739 gfxMatrix m; |
|
740 if (aToTopRight.y == aToTopLeft.y && aToTopRight.x == aToBottomRight.x) { |
|
741 // Not a rotation, so xy and yx are zero |
|
742 m.xy = m.yx = 0.0; |
|
743 m.xx = (aToBottomRight.x - aToTopLeft.x)/aFrom.width; |
|
744 m.yy = (aToBottomRight.y - aToTopLeft.y)/aFrom.height; |
|
745 m.x0 = aToTopLeft.x - m.xx*aFrom.x; |
|
746 m.y0 = aToTopLeft.y - m.yy*aFrom.y; |
|
747 } else { |
|
748 NS_ASSERTION(aToTopRight.y == aToBottomRight.y && aToTopRight.x == aToTopLeft.x, |
|
749 "Destination rectangle not axis-aligned"); |
|
750 m.xx = m.yy = 0.0; |
|
751 m.xy = (aToBottomRight.x - aToTopLeft.x)/aFrom.height; |
|
752 m.yx = (aToBottomRight.y - aToTopLeft.y)/aFrom.width; |
|
753 m.x0 = aToTopLeft.x - m.xy*aFrom.y; |
|
754 m.y0 = aToTopLeft.y - m.yx*aFrom.x; |
|
755 } |
|
756 return m; |
|
757 } |
|
758 |
|
759 Matrix |
|
760 gfxUtils::TransformRectToRect(const gfxRect& aFrom, const IntPoint& aToTopLeft, |
|
761 const IntPoint& aToTopRight, const IntPoint& aToBottomRight) |
|
762 { |
|
763 Matrix m; |
|
764 if (aToTopRight.y == aToTopLeft.y && aToTopRight.x == aToBottomRight.x) { |
|
765 // Not a rotation, so xy and yx are zero |
|
766 m._12 = m._21 = 0.0; |
|
767 m._11 = (aToBottomRight.x - aToTopLeft.x)/aFrom.width; |
|
768 m._22 = (aToBottomRight.y - aToTopLeft.y)/aFrom.height; |
|
769 m._31 = aToTopLeft.x - m._11*aFrom.x; |
|
770 m._32 = aToTopLeft.y - m._22*aFrom.y; |
|
771 } else { |
|
772 NS_ASSERTION(aToTopRight.y == aToBottomRight.y && aToTopRight.x == aToTopLeft.x, |
|
773 "Destination rectangle not axis-aligned"); |
|
774 m._11 = m._22 = 0.0; |
|
775 m._21 = (aToBottomRight.x - aToTopLeft.x)/aFrom.height; |
|
776 m._12 = (aToBottomRight.y - aToTopLeft.y)/aFrom.width; |
|
777 m._31 = aToTopLeft.x - m._21*aFrom.y; |
|
778 m._32 = aToTopLeft.y - m._12*aFrom.x; |
|
779 } |
|
780 return m; |
|
781 } |
|
782 |
|
783 bool |
|
784 gfxUtils::GfxRectToIntRect(const gfxRect& aIn, nsIntRect* aOut) |
|
785 { |
|
786 *aOut = nsIntRect(int32_t(aIn.X()), int32_t(aIn.Y()), |
|
787 int32_t(aIn.Width()), int32_t(aIn.Height())); |
|
788 return gfxRect(aOut->x, aOut->y, aOut->width, aOut->height).IsEqualEdges(aIn); |
|
789 } |
|
790 |
|
791 void |
|
792 gfxUtils::GetYCbCrToRGBDestFormatAndSize(const PlanarYCbCrData& aData, |
|
793 gfxImageFormat& aSuggestedFormat, |
|
794 gfxIntSize& aSuggestedSize) |
|
795 { |
|
796 YUVType yuvtype = |
|
797 TypeFromSize(aData.mYSize.width, |
|
798 aData.mYSize.height, |
|
799 aData.mCbCrSize.width, |
|
800 aData.mCbCrSize.height); |
|
801 |
|
802 // 'prescale' is true if the scaling is to be done as part of the |
|
803 // YCbCr to RGB conversion rather than on the RGB data when rendered. |
|
804 bool prescale = aSuggestedSize.width > 0 && aSuggestedSize.height > 0 && |
|
805 ToIntSize(aSuggestedSize) != aData.mPicSize; |
|
806 |
|
807 if (aSuggestedFormat == gfxImageFormat::RGB16_565) { |
|
808 #if defined(HAVE_YCBCR_TO_RGB565) |
|
809 if (prescale && |
|
810 !IsScaleYCbCrToRGB565Fast(aData.mPicX, |
|
811 aData.mPicY, |
|
812 aData.mPicSize.width, |
|
813 aData.mPicSize.height, |
|
814 aSuggestedSize.width, |
|
815 aSuggestedSize.height, |
|
816 yuvtype, |
|
817 FILTER_BILINEAR) && |
|
818 IsConvertYCbCrToRGB565Fast(aData.mPicX, |
|
819 aData.mPicY, |
|
820 aData.mPicSize.width, |
|
821 aData.mPicSize.height, |
|
822 yuvtype)) { |
|
823 prescale = false; |
|
824 } |
|
825 #else |
|
826 // yuv2rgb16 function not available |
|
827 aSuggestedFormat = gfxImageFormat::RGB24; |
|
828 #endif |
|
829 } |
|
830 else if (aSuggestedFormat != gfxImageFormat::RGB24) { |
|
831 // No other formats are currently supported. |
|
832 aSuggestedFormat = gfxImageFormat::RGB24; |
|
833 } |
|
834 if (aSuggestedFormat == gfxImageFormat::RGB24) { |
|
835 /* ScaleYCbCrToRGB32 does not support a picture offset, nor 4:4:4 data. |
|
836 See bugs 639415 and 640073. */ |
|
837 if (aData.mPicX != 0 || aData.mPicY != 0 || yuvtype == YV24) |
|
838 prescale = false; |
|
839 } |
|
840 if (!prescale) { |
|
841 ToIntSize(aSuggestedSize) = aData.mPicSize; |
|
842 } |
|
843 } |
|
844 |
|
845 void |
|
846 gfxUtils::ConvertYCbCrToRGB(const PlanarYCbCrData& aData, |
|
847 const gfxImageFormat& aDestFormat, |
|
848 const gfxIntSize& aDestSize, |
|
849 unsigned char* aDestBuffer, |
|
850 int32_t aStride) |
|
851 { |
|
852 // ConvertYCbCrToRGB et al. assume the chroma planes are rounded up if the |
|
853 // luma plane is odd sized. |
|
854 MOZ_ASSERT((aData.mCbCrSize.width == aData.mYSize.width || |
|
855 aData.mCbCrSize.width == (aData.mYSize.width + 1) >> 1) && |
|
856 (aData.mCbCrSize.height == aData.mYSize.height || |
|
857 aData.mCbCrSize.height == (aData.mYSize.height + 1) >> 1)); |
|
858 YUVType yuvtype = |
|
859 TypeFromSize(aData.mYSize.width, |
|
860 aData.mYSize.height, |
|
861 aData.mCbCrSize.width, |
|
862 aData.mCbCrSize.height); |
|
863 |
|
864 // Convert from YCbCr to RGB now, scaling the image if needed. |
|
865 if (ToIntSize(aDestSize) != aData.mPicSize) { |
|
866 #if defined(HAVE_YCBCR_TO_RGB565) |
|
867 if (aDestFormat == gfxImageFormat::RGB16_565) { |
|
868 ScaleYCbCrToRGB565(aData.mYChannel, |
|
869 aData.mCbChannel, |
|
870 aData.mCrChannel, |
|
871 aDestBuffer, |
|
872 aData.mPicX, |
|
873 aData.mPicY, |
|
874 aData.mPicSize.width, |
|
875 aData.mPicSize.height, |
|
876 aDestSize.width, |
|
877 aDestSize.height, |
|
878 aData.mYStride, |
|
879 aData.mCbCrStride, |
|
880 aStride, |
|
881 yuvtype, |
|
882 FILTER_BILINEAR); |
|
883 } else |
|
884 #endif |
|
885 ScaleYCbCrToRGB32(aData.mYChannel, |
|
886 aData.mCbChannel, |
|
887 aData.mCrChannel, |
|
888 aDestBuffer, |
|
889 aData.mPicSize.width, |
|
890 aData.mPicSize.height, |
|
891 aDestSize.width, |
|
892 aDestSize.height, |
|
893 aData.mYStride, |
|
894 aData.mCbCrStride, |
|
895 aStride, |
|
896 yuvtype, |
|
897 ROTATE_0, |
|
898 FILTER_BILINEAR); |
|
899 } else { // no prescale |
|
900 #if defined(HAVE_YCBCR_TO_RGB565) |
|
901 if (aDestFormat == gfxImageFormat::RGB16_565) { |
|
902 ConvertYCbCrToRGB565(aData.mYChannel, |
|
903 aData.mCbChannel, |
|
904 aData.mCrChannel, |
|
905 aDestBuffer, |
|
906 aData.mPicX, |
|
907 aData.mPicY, |
|
908 aData.mPicSize.width, |
|
909 aData.mPicSize.height, |
|
910 aData.mYStride, |
|
911 aData.mCbCrStride, |
|
912 aStride, |
|
913 yuvtype); |
|
914 } else // aDestFormat != gfxImageFormat::RGB16_565 |
|
915 #endif |
|
916 ConvertYCbCrToRGB32(aData.mYChannel, |
|
917 aData.mCbChannel, |
|
918 aData.mCrChannel, |
|
919 aDestBuffer, |
|
920 aData.mPicX, |
|
921 aData.mPicY, |
|
922 aData.mPicSize.width, |
|
923 aData.mPicSize.height, |
|
924 aData.mYStride, |
|
925 aData.mCbCrStride, |
|
926 aStride, |
|
927 yuvtype); |
|
928 } |
|
929 } |
|
930 |
|
931 /* static */ TemporaryRef<DataSourceSurface> |
|
932 gfxUtils::CopySurfaceToDataSourceSurfaceWithFormat(SourceSurface* aSurface, |
|
933 SurfaceFormat aFormat) |
|
934 { |
|
935 MOZ_ASSERT(aFormat != aSurface->GetFormat(), |
|
936 "Unnecessary - and very expersive - surface format conversion"); |
|
937 |
|
938 Rect bounds(0, 0, aSurface->GetSize().width, aSurface->GetSize().height); |
|
939 |
|
940 if (aSurface->GetType() != SurfaceType::DATA) { |
|
941 // If the surface is NOT of type DATA then its data is not mapped into main |
|
942 // memory. Format conversion is probably faster on the GPU, and by doing it |
|
943 // there we can avoid any expensive uploads/readbacks except for (possibly) |
|
944 // a single readback due to the unavoidable GetDataSurface() call. Using |
|
945 // CreateOffscreenContentDrawTarget ensures the conversion happens on the |
|
946 // GPU. |
|
947 RefPtr<DrawTarget> dt = gfxPlatform::GetPlatform()-> |
|
948 CreateOffscreenContentDrawTarget(aSurface->GetSize(), aFormat); |
|
949 // Using DrawSurface() here rather than CopySurface() because CopySurface |
|
950 // is optimized for memcpy and therefore isn't good for format conversion. |
|
951 // Using OP_OVER since in our case it's equivalent to OP_SOURCE and |
|
952 // generally more optimized. |
|
953 dt->DrawSurface(aSurface, bounds, bounds, DrawSurfaceOptions(), |
|
954 DrawOptions(1.0f, CompositionOp::OP_OVER)); |
|
955 RefPtr<SourceSurface> surface = dt->Snapshot(); |
|
956 return surface->GetDataSurface(); |
|
957 } |
|
958 |
|
959 // If the surface IS of type DATA then it may or may not be in main memory |
|
960 // depending on whether or not it has been mapped yet. We have no way of |
|
961 // knowing, so we can't be sure if it's best to create a data wrapping |
|
962 // DrawTarget for the conversion or an offscreen content DrawTarget. We could |
|
963 // guess it's not mapped and create an offscreen content DrawTarget, but if |
|
964 // it is then we'll end up uploading the surface data, and most likely the |
|
965 // caller is going to be accessing the resulting surface data, resulting in a |
|
966 // readback (both very expensive operations). Alternatively we could guess |
|
967 // the data is mapped and create a data wrapping DrawTarget and, if the |
|
968 // surface is not in main memory, then we will incure a readback. The latter |
|
969 // of these two "wrong choices" is the least costly (a readback, vs an |
|
970 // upload and a readback), and more than likely the DATA surface that we've |
|
971 // been passed actually IS in main memory anyway. For these reasons it's most |
|
972 // likely best to create a data wrapping DrawTarget here to do the format |
|
973 // conversion. |
|
974 RefPtr<DataSourceSurface> dataSurface = |
|
975 Factory::CreateDataSourceSurface(aSurface->GetSize(), aFormat); |
|
976 DataSourceSurface::MappedSurface map; |
|
977 if (!dataSurface || |
|
978 !dataSurface->Map(DataSourceSurface::MapType::READ_WRITE, &map)) { |
|
979 return nullptr; |
|
980 } |
|
981 RefPtr<DrawTarget> dt = |
|
982 Factory::CreateDrawTargetForData(BackendType::CAIRO, |
|
983 map.mData, |
|
984 dataSurface->GetSize(), |
|
985 map.mStride, |
|
986 aFormat); |
|
987 if (!dt) { |
|
988 dataSurface->Unmap(); |
|
989 return nullptr; |
|
990 } |
|
991 // Using DrawSurface() here rather than CopySurface() because CopySurface |
|
992 // is optimized for memcpy and therefore isn't good for format conversion. |
|
993 // Using OP_OVER since in our case it's equivalent to OP_SOURCE and |
|
994 // generally more optimized. |
|
995 dt->DrawSurface(aSurface, bounds, bounds, DrawSurfaceOptions(), |
|
996 DrawOptions(1.0f, CompositionOp::OP_OVER)); |
|
997 dataSurface->Unmap(); |
|
998 return dataSurface.forget(); |
|
999 } |
|
1000 |
|
1001 #ifdef MOZ_DUMP_PAINTING |
|
1002 /* static */ void |
|
1003 gfxUtils::WriteAsPNG(DrawTarget* aDT, const char* aFile) |
|
1004 { |
|
1005 aDT->Flush(); |
|
1006 nsRefPtr<gfxASurface> surf = gfxPlatform::GetPlatform()->GetThebesSurfaceForDrawTarget(aDT); |
|
1007 if (surf) { |
|
1008 surf->WriteAsPNG(aFile); |
|
1009 } else { |
|
1010 NS_WARNING("Failed to get Thebes surface!"); |
|
1011 } |
|
1012 } |
|
1013 |
|
1014 /* static */ void |
|
1015 gfxUtils::DumpAsDataURL(DrawTarget* aDT) |
|
1016 { |
|
1017 aDT->Flush(); |
|
1018 nsRefPtr<gfxASurface> surf = gfxPlatform::GetPlatform()->GetThebesSurfaceForDrawTarget(aDT); |
|
1019 if (surf) { |
|
1020 surf->DumpAsDataURL(); |
|
1021 } else { |
|
1022 NS_WARNING("Failed to get Thebes surface!"); |
|
1023 } |
|
1024 } |
|
1025 |
|
1026 /* static */ void |
|
1027 gfxUtils::CopyAsDataURL(DrawTarget* aDT) |
|
1028 { |
|
1029 aDT->Flush(); |
|
1030 nsRefPtr<gfxASurface> surf = gfxPlatform::GetPlatform()->GetThebesSurfaceForDrawTarget(aDT); |
|
1031 if (surf) { |
|
1032 surf->CopyAsDataURL(); |
|
1033 } else { |
|
1034 NS_WARNING("Failed to get Thebes surface!"); |
|
1035 } |
|
1036 } |
|
1037 |
|
1038 /* static */ void |
|
1039 gfxUtils::WriteAsPNG(RefPtr<gfx::SourceSurface> aSourceSurface, const char* aFile) |
|
1040 { |
|
1041 RefPtr<gfx::DataSourceSurface> dataSurface = aSourceSurface->GetDataSurface(); |
|
1042 RefPtr<gfx::DrawTarget> dt |
|
1043 = gfxPlatform::GetPlatform() |
|
1044 ->CreateDrawTargetForData(dataSurface->GetData(), |
|
1045 dataSurface->GetSize(), |
|
1046 dataSurface->Stride(), |
|
1047 aSourceSurface->GetFormat()); |
|
1048 gfxUtils::WriteAsPNG(dt.get(), aFile); |
|
1049 } |
|
1050 |
|
1051 /* static */ void |
|
1052 gfxUtils::DumpAsDataURL(RefPtr<gfx::SourceSurface> aSourceSurface) |
|
1053 { |
|
1054 RefPtr<gfx::DataSourceSurface> dataSurface = aSourceSurface->GetDataSurface(); |
|
1055 RefPtr<gfx::DrawTarget> dt |
|
1056 = gfxPlatform::GetPlatform() |
|
1057 ->CreateDrawTargetForData(dataSurface->GetData(), |
|
1058 dataSurface->GetSize(), |
|
1059 dataSurface->Stride(), |
|
1060 aSourceSurface->GetFormat()); |
|
1061 gfxUtils::DumpAsDataURL(dt.get()); |
|
1062 } |
|
1063 |
|
1064 /* static */ void |
|
1065 gfxUtils::CopyAsDataURL(RefPtr<gfx::SourceSurface> aSourceSurface) |
|
1066 { |
|
1067 RefPtr<gfx::DataSourceSurface> dataSurface = aSourceSurface->GetDataSurface(); |
|
1068 RefPtr<gfx::DrawTarget> dt |
|
1069 = gfxPlatform::GetPlatform() |
|
1070 ->CreateDrawTargetForData(dataSurface->GetData(), |
|
1071 dataSurface->GetSize(), |
|
1072 dataSurface->Stride(), |
|
1073 aSourceSurface->GetFormat()); |
|
1074 |
|
1075 gfxUtils::CopyAsDataURL(dt.get()); |
|
1076 } |
|
1077 |
|
1078 bool gfxUtils::sDumpPaintList = getenv("MOZ_DUMP_PAINT_LIST") != 0; |
|
1079 bool gfxUtils::sDumpPainting = getenv("MOZ_DUMP_PAINT") != 0; |
|
1080 bool gfxUtils::sDumpPaintingToFile = getenv("MOZ_DUMP_PAINT_TO_FILE") != 0; |
|
1081 FILE *gfxUtils::sDumpPaintFile = nullptr; |
|
1082 #endif |