Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
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/. */
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"
19 #ifdef XP_WIN
20 #include "gfxWindowsPlatform.h"
21 #endif
23 using namespace mozilla;
24 using namespace mozilla::layers;
25 using namespace mozilla::gfx;
27 #include "DeprecatedPremultiplyTables.h"
29 static const uint8_t PremultiplyValue(uint8_t a, uint8_t v) {
30 return gfxUtils::sPremultiplyTable[a*256+v];
31 }
33 static const uint8_t UnpremultiplyValue(uint8_t a, uint8_t v) {
34 return gfxUtils::sUnpremultiplyTable[a*256+v];
35 }
37 void
38 gfxUtils::PremultiplyImageSurface(gfxImageSurface *aSourceSurface,
39 gfxImageSurface *aDestSurface)
40 {
41 if (!aDestSurface)
42 aDestSurface = aSourceSurface;
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");
50 MOZ_ASSERT(aSourceSurface->Stride() == aSourceSurface->Width() * 4,
51 "Source surface stride isn't tightly packed");
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 }
62 uint8_t *src = aSourceSurface->Data();
63 uint8_t *dst = aDestSurface->Data();
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++;
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++;
83 *dst++ = a;
84 *dst++ = PremultiplyValue(a, r);
85 *dst++ = PremultiplyValue(a, g);
86 *dst++ = PremultiplyValue(a, b);
87 #endif
88 }
89 }
91 void
92 gfxUtils::UnpremultiplyImageSurface(gfxImageSurface *aSourceSurface,
93 gfxImageSurface *aDestSurface)
94 {
95 if (!aDestSurface)
96 aDestSurface = aSourceSurface;
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");
103 // Only premultiply ARGB32
104 if (aSourceSurface->Format() != gfxImageFormat::ARGB32) {
105 if (aDestSurface != aSourceSurface) {
106 aDestSurface->CopyFrom(aSourceSurface);
107 }
108 return;
109 }
111 uint8_t *src = aSourceSurface->Data();
112 uint8_t *dst = aDestSurface->Data();
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());
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++;
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++;
135 *dstRow++ = a;
136 *dstRow++ = UnpremultiplyValue(a, r);
137 *dstRow++ = UnpremultiplyValue(a, g);
138 *dstRow++ = UnpremultiplyValue(a, b);
139 #endif
140 }
141 }
142 }
144 TemporaryRef<DataSourceSurface>
145 gfxUtils::UnpremultiplyDataSurface(DataSourceSurface* aSurface)
146 {
147 // Only premultiply ARGB32
148 if (aSurface->GetFormat() != SurfaceFormat::B8G8R8A8) {
149 return aSurface;
150 }
152 DataSourceSurface::MappedSurface map;
153 if (!aSurface->Map(DataSourceSurface::MapType::READ, &map)) {
154 return nullptr;
155 }
157 RefPtr<DataSourceSurface> dest = Factory::CreateDataSourceSurfaceWithStride(aSurface->GetSize(),
158 aSurface->GetFormat(),
159 map.mStride);
161 DataSourceSurface::MappedSurface destMap;
162 if (!dest->Map(DataSourceSurface::MapType::WRITE, &destMap)) {
163 aSurface->Unmap();
164 return nullptr;
165 }
167 uint8_t *src = map.mData;
168 uint8_t *dst = destMap.mData;
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);
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++;
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++;
191 *dstRow++ = a;
192 *dstRow++ = UnpremultiplyValue(a, r);
193 *dstRow++ = UnpremultiplyValue(a, g);
194 *dstRow++ = UnpremultiplyValue(a, b);
195 #endif
196 }
197 }
199 aSurface->Unmap();
200 dest->Unmap();
201 return dest;
202 }
204 void
205 gfxUtils::ConvertBGRAtoRGBA(gfxImageSurface *aSourceSurface,
206 gfxImageSurface *aDestSurface) {
207 if (!aDestSurface)
208 aDestSurface = aSourceSurface;
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");
216 MOZ_ASSERT(aSourceSurface->Stride() == aSourceSurface->Width() * 4,
217 "Source surface stride isn't tightly packed");
219 MOZ_ASSERT(aSourceSurface->Format() == gfxImageFormat::ARGB32 || aSourceSurface->Format() == gfxImageFormat::RGB24,
220 "Surfaces must be ARGB32 or RGB24");
222 uint8_t *src = aSourceSurface->Data();
223 uint8_t *dst = aDestSurface->Data();
225 uint32_t dim = aSourceSurface->Width() * aSourceSurface->Height();
226 uint8_t *srcEnd = src + 4*dim;
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];
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 }
249 void
250 gfxUtils::ConvertBGRAtoRGBA(uint8_t* aData, uint32_t aLength)
251 {
252 uint8_t *src = aData;
253 uint8_t *srcEnd = src + aLength;
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];
261 src[0] = buffer[0];
262 src[1] = buffer[1];
263 src[2] = buffer[2];
264 }
265 }
267 static bool
268 IsSafeImageTransformComponent(gfxFloat aValue)
269 {
270 return aValue >= -32768 && aValue <= 32767;
271 }
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 }
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);
318 gfxRect needed = imageSpaceClipExtents.Intersect(aSourceRect);
319 needed = needed.Intersect(aSubimage);
320 needed.RoundOut();
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;
329 nsRefPtr<gfxDrawable> drawable;
330 gfxIntSize size(int32_t(needed.Width()), int32_t(needed.Height()));
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 }
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 }
351 return drawable.forget();
352 }
353 #endif // !MOZ_GFX_OPTIMIZE_MOBILE
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;
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 }
378 if (IsSafeImageTransformComponent(aDeviceSpaceToImageSpace.x0) &&
379 IsSafeImageTransformComponent(aDeviceSpaceToImageSpace.y0))
380 return;
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();
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);
398 mPushedGroup = true;
399 }
401 ~AutoCairoPixmanBugWorkaround()
402 {
403 if (mPushedGroup) {
404 mContext->PopGroupToSource();
405 mContext->Paint();
406 mContext->Restore();
407 }
408 }
410 bool PushedGroup() { return mPushedGroup; }
411 bool Succeeded() { return mSucceeded; }
413 private:
414 gfxContext* mContext;
415 bool mSucceeded;
416 bool mPushedGroup;
417 };
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 }
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;
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;
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 }
454 if (aImgHeight * kLargeStretch <= aSourceHeight || aImgWidth * kLargeStretch <= aSourceWidth) {
455 // Large image tiling detected.
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;
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 }
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.
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
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 */
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
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);
515 nsRefPtr<gfxASurface> currentTarget = aContext->CurrentSurface();
516 gfxMatrix deviceSpaceToImageSpace =
517 DeviceToImageTransform(aContext, aUserSpaceToImageSpace);
519 AutoCairoPixmanBugWorkaround workaround(aContext, deviceSpaceToImageSpace,
520 aFill, currentTarget);
521 if (!workaround.Succeeded())
522 return;
524 nsRefPtr<gfxDrawable> drawable = aDrawable;
526 aFilter = ReduceResamplingFilter(aFilter, aImageRect.Width(), aImageRect.Height(), aSourceRect.Width(), aSourceRect.Height());
528 gfxMatrix userSpaceToImageSpace = aUserSpaceToImageSpace;
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
567 drawable->Draw(aContext, aFill, doTile, aFilter, userSpaceToImageSpace);
568 }
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 }
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 }
598 static void
599 ClipToRegionInternal(gfxContext* aContext, const nsIntRegion& aRegion,
600 bool aSnap)
601 {
602 PathFromRegionInternal(aContext, aRegion, aSnap);
603 aContext->Clip();
604 }
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
620 RefPtr<PathBuilder> pb = aTarget->CreatePathBuilder();
621 nsIntRegionRectIterator iter(aRegion);
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);
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 }
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 }
656 /*static*/ void
657 gfxUtils::ClipToRegion(gfxContext* aContext, const nsIntRegion& aRegion)
658 {
659 ClipToRegionInternal(aContext, aRegion, false);
660 }
662 /*static*/ void
663 gfxUtils::ClipToRegion(DrawTarget* aTarget, const nsIntRegion& aRegion)
664 {
665 ClipToRegionInternal(aTarget, aRegion, false);
666 }
668 /*static*/ void
669 gfxUtils::ClipToRegionSnapped(gfxContext* aContext, const nsIntRegion& aRegion)
670 {
671 ClipToRegionInternal(aContext, aRegion, true);
672 }
674 /*static*/ void
675 gfxUtils::ClipToRegionSnapped(DrawTarget* aTarget, const nsIntRegion& aRegion)
676 {
677 ClipToRegionInternal(aTarget, aRegion, true);
678 }
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;
688 // Negative scaling is just a flip and irrelevant to
689 // our resolution calculation.
690 if (aVal < 0.0) {
691 aVal = -aVal;
692 }
694 bool inverse = false;
695 if (aVal < 1.0) {
696 inverse = true;
697 aVal = 1 / aVal;
698 }
700 gfxFloat power = log(aVal)/log(kScaleResolution);
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 }
713 gfxFloat scale = pow(kScaleResolution, power);
715 if (inverse) {
716 scale = 1 / scale;
717 }
719 return scale;
720 }
723 /*static*/ void
724 gfxUtils::PathFromRegion(gfxContext* aContext, const nsIntRegion& aRegion)
725 {
726 PathFromRegionInternal(aContext, aRegion, false);
727 }
729 /*static*/ void
730 gfxUtils::PathFromRegionSnapped(gfxContext* aContext, const nsIntRegion& aRegion)
731 {
732 PathFromRegionInternal(aContext, aRegion, true);
733 }
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 }
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 }
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 }
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);
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;
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 }
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);
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 }
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");
938 Rect bounds(0, 0, aSurface->GetSize().width, aSurface->GetSize().height);
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 }
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 }
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 }
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 }
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 }
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 }
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 }
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());
1075 gfxUtils::CopyAsDataURL(dt.get());
1076 }
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