1 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
9 #include "gfxContext.h"
11 #include "gfxImageSurface.h"
12 #include "gfxPlatform.h"
13 #include "gfxDrawable.h"
15 #include "imgIEncoder.h"
16 #include "mozilla/Base64.h"
17 #include "mozilla/StyleColorInlines.h"
18 #include "mozilla/Components.h"
19 #include "mozilla/dom/Document.h"
20 #include "mozilla/dom/ImageEncoder.h"
21 #include "mozilla/dom/WorkerPrivate.h"
22 #include "mozilla/dom/WorkerRunnable.h"
23 #include "mozilla/ipc/CrossProcessSemaphore.h"
24 #include "mozilla/gfx/2D.h"
25 #include "mozilla/gfx/DataSurfaceHelpers.h"
26 #include "mozilla/gfx/Logging.h"
27 #include "mozilla/gfx/PathHelpers.h"
28 #include "mozilla/gfx/Swizzle.h"
29 #include "mozilla/gfx/gfxVars.h"
30 #include "mozilla/image/nsBMPEncoder.h"
31 #include "mozilla/image/nsICOEncoder.h"
32 #include "mozilla/image/nsJPEGEncoder.h"
33 #include "mozilla/image/nsPNGEncoder.h"
34 #include "mozilla/layers/SynchronousTask.h"
35 #include "mozilla/Maybe.h"
36 #include "mozilla/Preferences.h"
37 #include "mozilla/ProfilerLabels.h"
38 #include "mozilla/RefPtr.h"
39 #include "mozilla/ServoStyleConsts.h"
40 #include "mozilla/StaticPrefs_gfx.h"
41 #include "mozilla/StaticPrefs_layout.h"
42 #include "mozilla/UniquePtrExtensions.h"
43 #include "mozilla/Unused.h"
44 #include "mozilla/webrender/webrender_ffi.h"
45 #include "nsAppRunner.h"
46 #include "nsComponentManagerUtils.h"
47 #include "nsIClipboardHelper.h"
49 #include "nsIGfxInfo.h"
50 #include "nsMimeTypes.h"
51 #include "nsPresContext.h"
53 #include "nsServiceManagerUtils.h"
54 #include "nsRFPService.h"
55 #include "ImageContainer.h"
56 #include "ImageRegion.h"
57 #include "gfx2DGlue.h"
60 # include "gfxWindowsPlatform.h"
63 using namespace mozilla
;
64 using namespace mozilla::image
;
65 using namespace mozilla::layers
;
66 using namespace mozilla::gfx
;
69 #include "mozilla/Compression.h"
71 using namespace mozilla::Compression
;
75 * Dump a raw image to the default log. This function is exported
76 * from libxul, so it can be called from any library in addition to
77 * (of course) from a debugger.
79 * Note: this helper currently assumes that all 2-bytepp images are
80 * r5g6b5, and that all 4-bytepp images are r8g8b8a8.
83 void mozilla_dump_image(void* bytes
, int width
, int height
, int bytepp
,
85 if (0 == strideBytes
) {
86 strideBytes
= width
* bytepp
;
89 // TODO more flexible; parse string?
92 format
= SurfaceFormat::R5G6B5_UINT16
;
96 format
= SurfaceFormat::R8G8B8A8
;
100 RefPtr
<DataSourceSurface
> surf
= Factory::CreateWrappingDataSourceSurface(
101 (uint8_t*)bytes
, strideBytes
, IntSize(width
, height
), format
);
102 gfxUtils::DumpAsDataURI(surf
);
106 static bool MapSrcDest(DataSourceSurface
* srcSurf
, DataSourceSurface
* destSurf
,
107 DataSourceSurface::MappedSurface
* out_srcMap
,
108 DataSourceSurface::MappedSurface
* out_destMap
) {
109 MOZ_ASSERT(srcSurf
&& destSurf
);
110 MOZ_ASSERT(out_srcMap
&& out_destMap
);
112 if (srcSurf
->GetSize() != destSurf
->GetSize()) {
113 MOZ_ASSERT(false, "Width and height must match.");
117 if (srcSurf
== destSurf
) {
118 DataSourceSurface::MappedSurface map
;
119 if (!srcSurf
->Map(DataSourceSurface::MapType::READ_WRITE
, &map
)) {
120 NS_WARNING("Couldn't Map srcSurf/destSurf.");
129 // Map src for reading.
130 DataSourceSurface::MappedSurface srcMap
;
131 if (!srcSurf
->Map(DataSourceSurface::MapType::READ
, &srcMap
)) {
132 NS_WARNING("Couldn't Map srcSurf.");
136 // Map dest for writing.
137 DataSourceSurface::MappedSurface destMap
;
138 if (!destSurf
->Map(DataSourceSurface::MapType::WRITE
, &destMap
)) {
139 NS_WARNING("Couldn't Map aDest.");
144 *out_srcMap
= srcMap
;
145 *out_destMap
= destMap
;
149 static void UnmapSrcDest(DataSourceSurface
* srcSurf
,
150 DataSourceSurface
* destSurf
) {
151 if (srcSurf
== destSurf
) {
159 bool gfxUtils::PremultiplyDataSurface(DataSourceSurface
* srcSurf
,
160 DataSourceSurface
* destSurf
) {
161 MOZ_ASSERT(srcSurf
&& destSurf
);
163 DataSourceSurface::MappedSurface srcMap
;
164 DataSourceSurface::MappedSurface destMap
;
165 if (!MapSrcDest(srcSurf
, destSurf
, &srcMap
, &destMap
)) return false;
167 PremultiplyData(srcMap
.mData
, srcMap
.mStride
, srcSurf
->GetFormat(),
168 destMap
.mData
, destMap
.mStride
, destSurf
->GetFormat(),
171 UnmapSrcDest(srcSurf
, destSurf
);
175 bool gfxUtils::UnpremultiplyDataSurface(DataSourceSurface
* srcSurf
,
176 DataSourceSurface
* destSurf
) {
177 MOZ_ASSERT(srcSurf
&& destSurf
);
179 DataSourceSurface::MappedSurface srcMap
;
180 DataSourceSurface::MappedSurface destMap
;
181 if (!MapSrcDest(srcSurf
, destSurf
, &srcMap
, &destMap
)) return false;
183 UnpremultiplyData(srcMap
.mData
, srcMap
.mStride
, srcSurf
->GetFormat(),
184 destMap
.mData
, destMap
.mStride
, destSurf
->GetFormat(),
187 UnmapSrcDest(srcSurf
, destSurf
);
191 static bool MapSrcAndCreateMappedDest(
192 DataSourceSurface
* srcSurf
, RefPtr
<DataSourceSurface
>* out_destSurf
,
193 DataSourceSurface::MappedSurface
* out_srcMap
,
194 DataSourceSurface::MappedSurface
* out_destMap
) {
196 MOZ_ASSERT(out_destSurf
&& out_srcMap
&& out_destMap
);
198 // Ok, map source for reading.
199 DataSourceSurface::MappedSurface srcMap
;
200 if (!srcSurf
->Map(DataSourceSurface::MapType::READ
, &srcMap
)) {
201 MOZ_ASSERT(false, "Couldn't Map srcSurf.");
205 // Make our dest surface based on the src.
206 RefPtr
<DataSourceSurface
> destSurf
=
207 Factory::CreateDataSourceSurfaceWithStride(
208 srcSurf
->GetSize(), srcSurf
->GetFormat(), srcMap
.mStride
);
209 if (NS_WARN_IF(!destSurf
)) {
213 DataSourceSurface::MappedSurface destMap
;
214 if (!destSurf
->Map(DataSourceSurface::MapType::WRITE
, &destMap
)) {
215 MOZ_ASSERT(false, "Couldn't Map destSurf.");
220 *out_destSurf
= destSurf
;
221 *out_srcMap
= srcMap
;
222 *out_destMap
= destMap
;
226 already_AddRefed
<DataSourceSurface
> gfxUtils::CreatePremultipliedDataSurface(
227 DataSourceSurface
* srcSurf
) {
228 RefPtr
<DataSourceSurface
> destSurf
;
229 DataSourceSurface::MappedSurface srcMap
;
230 DataSourceSurface::MappedSurface destMap
;
231 if (!MapSrcAndCreateMappedDest(srcSurf
, &destSurf
, &srcMap
, &destMap
)) {
232 MOZ_ASSERT(false, "MapSrcAndCreateMappedDest failed.");
233 RefPtr
<DataSourceSurface
> surface(srcSurf
);
234 return surface
.forget();
237 PremultiplyData(srcMap
.mData
, srcMap
.mStride
, srcSurf
->GetFormat(),
238 destMap
.mData
, destMap
.mStride
, destSurf
->GetFormat(),
241 UnmapSrcDest(srcSurf
, destSurf
);
242 return destSurf
.forget();
245 already_AddRefed
<DataSourceSurface
> gfxUtils::CreateUnpremultipliedDataSurface(
246 DataSourceSurface
* srcSurf
) {
247 RefPtr
<DataSourceSurface
> destSurf
;
248 DataSourceSurface::MappedSurface srcMap
;
249 DataSourceSurface::MappedSurface destMap
;
250 if (!MapSrcAndCreateMappedDest(srcSurf
, &destSurf
, &srcMap
, &destMap
)) {
251 MOZ_ASSERT(false, "MapSrcAndCreateMappedDest failed.");
252 RefPtr
<DataSourceSurface
> surface(srcSurf
);
253 return surface
.forget();
256 UnpremultiplyData(srcMap
.mData
, srcMap
.mStride
, srcSurf
->GetFormat(),
257 destMap
.mData
, destMap
.mStride
, destSurf
->GetFormat(),
260 UnmapSrcDest(srcSurf
, destSurf
);
261 return destSurf
.forget();
264 void gfxUtils::ConvertBGRAtoRGBA(uint8_t* aData
, uint32_t aLength
) {
265 MOZ_ASSERT((aLength
% 4) == 0, "Loop below will pass srcEnd!");
266 SwizzleData(aData
, aLength
, SurfaceFormat::B8G8R8A8
, aData
, aLength
,
267 SurfaceFormat::R8G8B8A8
, IntSize(aLength
/ 4, 1));
270 #if !defined(MOZ_GFX_OPTIMIZE_MOBILE)
272 * This returns the fastest operator to use for solid surfaces which have no
273 * alpha channel or their alpha channel is uniformly opaque.
274 * This differs per render mode.
276 static CompositionOp
OptimalFillOp() {
278 if (gfxWindowsPlatform::GetPlatform()->IsDirect2DBackend()) {
279 // D2D -really- hates operator source.
280 return CompositionOp::OP_OVER
;
283 return CompositionOp::OP_SOURCE
;
286 // EXTEND_PAD won't help us here; we have to create a temporary surface to hold
287 // the subimage of pixels we're allowed to sample.
288 static already_AddRefed
<gfxDrawable
> CreateSamplingRestrictedDrawable(
289 gfxDrawable
* aDrawable
, gfxContext
* aContext
, const ImageRegion
& aRegion
,
290 const SurfaceFormat aFormat
, bool aUseOptimalFillOp
) {
291 AUTO_PROFILER_LABEL("CreateSamplingRestrictedDrawable", GRAPHICS
);
293 DrawTarget
* destDrawTarget
= aContext
->GetDrawTarget();
294 // We've been not using CreateSamplingRestrictedDrawable in a bunch of places
295 // for a while. Let's disable it everywhere and confirm that it's ok to get
297 if (destDrawTarget
->GetBackendType() == BackendType::DIRECT2D1_1
|| (true)) {
301 gfxRect clipExtents
= aContext
->GetClipExtents();
303 // Inflate by one pixel because bilinear filtering will sample at most
304 // one pixel beyond the computed image pixel coordinate.
305 clipExtents
.Inflate(1.0);
307 gfxRect needed
= aRegion
.IntersectAndRestrict(clipExtents
);
310 // if 'needed' is empty, nothing will be drawn since aFill
311 // must be entirely outside the clip region, so it doesn't
312 // matter what we do here, but we should avoid trying to
313 // create a zero-size surface.
314 if (needed
.IsEmpty()) return nullptr;
316 IntSize
size(int32_t(needed
.Width()), int32_t(needed
.Height()));
318 RefPtr
<DrawTarget
> target
=
319 gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(size
,
321 if (!target
|| !target
->IsValid()) {
325 gfxContext
tmpCtx(target
);
327 if (aUseOptimalFillOp
) {
328 tmpCtx
.SetOp(OptimalFillOp());
330 aDrawable
->Draw(&tmpCtx
, needed
- needed
.TopLeft(), ExtendMode::REPEAT
,
331 SamplingFilter::LINEAR
, 1.0,
332 gfxMatrix::Translation(needed
.TopLeft()));
333 RefPtr
<SourceSurface
> surface
= target
->Snapshot();
335 RefPtr
<gfxDrawable
> drawable
= new gfxSurfaceDrawable(
336 surface
, size
, gfxMatrix::Translation(-needed
.TopLeft()));
337 return drawable
.forget();
339 #endif // !MOZ_GFX_OPTIMIZE_MOBILE
341 /* These heuristics are based on
342 * Source/WebCore/platform/graphics/skia/ImageSkia.cpp:computeResamplingMode()
344 #ifdef MOZ_GFX_OPTIMIZE_MOBILE
345 static SamplingFilter
ReduceResamplingFilter(SamplingFilter aSamplingFilter
,
346 int aImgWidth
, int aImgHeight
,
348 float aSourceHeight
) {
349 // Images smaller than this in either direction are considered "small" and
350 // are not resampled ever (see below).
351 const int kSmallImageSizeThreshold
= 8;
353 // The amount an image can be stretched in a single direction before we
354 // say that it is being stretched so much that it must be a line or
355 // background that doesn't need resampling.
356 const float kLargeStretch
= 3.0f
;
358 if (aImgWidth
<= kSmallImageSizeThreshold
||
359 aImgHeight
<= kSmallImageSizeThreshold
) {
360 // Never resample small images. These are often used for borders and
361 // rules (think 1x1 images used to make lines).
362 return SamplingFilter::POINT
;
365 if (aImgHeight
* kLargeStretch
<= aSourceHeight
||
366 aImgWidth
* kLargeStretch
<= aSourceWidth
) {
367 // Large image tiling detected.
369 // Don't resample if it is being tiled a lot in only one direction.
370 // This is trying to catch cases where somebody has created a border
371 // (which might be large) and then is stretching it to fill some part
373 if (fabs(aSourceWidth
- aImgWidth
) / aImgWidth
< 0.5 ||
374 fabs(aSourceHeight
- aImgHeight
) / aImgHeight
< 0.5)
375 return SamplingFilter::POINT
;
377 // The image is growing a lot and in more than one direction. Resampling
378 // is slow and doesn't give us very much when growing a lot.
379 return aSamplingFilter
;
382 /* Some notes on other heuristics:
383 The Skia backend also uses nearest for backgrounds that are stretched by
384 a large amount. I'm not sure this is common enough for us to worry about
385 now. It also uses nearest for backgrounds/avoids high quality for images
386 that are very slightly scaled. I'm also not sure that very slightly
387 scaled backgrounds are common enough us to worry about.
389 We don't currently have much support for doing high quality interpolation.
390 The only place this currently happens is on Quartz and we don't have as
391 much control over it as would be needed. Webkit avoids using high quality
392 resampling during load. It also avoids high quality if the transformation
393 is not just a scale and translation
395 WebKit bug #40045 added code to avoid resampling different parts
396 of an image with different methods by using a resampling hint size.
397 It currently looks unused in WebKit but it's something to watch out for.
400 return aSamplingFilter
;
403 static SamplingFilter
ReduceResamplingFilter(SamplingFilter aSamplingFilter
,
404 int aImgWidth
, int aImgHeight
,
407 // Just pass the filter through unchanged
408 return aSamplingFilter
;
412 #ifdef MOZ_WIDGET_COCOA
413 // Only prescale a temporary surface if we're going to repeat it often.
414 // Scaling is expensive on OS X and without prescaling, we'd scale
415 // every tile of the repeated rect. However, using a temp surface also
416 // potentially uses more memory if the scaled image is large. So only prescale
417 // on a temp surface if we know we're going to repeat the image in either the X
418 // or Y axis multiple times.
419 static bool ShouldUseTempSurface(Rect aImageRect
, Rect aNeededRect
) {
420 int repeatX
= aNeededRect
.width
/ aImageRect
.width
;
421 int repeatY
= aNeededRect
.height
/ aImageRect
.height
;
422 return (repeatX
>= 5) || (repeatY
>= 5);
425 static bool PrescaleAndTileDrawable(gfxDrawable
* aDrawable
,
426 gfxContext
* aContext
,
427 const ImageRegion
& aRegion
, Rect aImageRect
,
428 const SamplingFilter aSamplingFilter
,
429 const SurfaceFormat aFormat
,
430 gfxFloat aOpacity
, ExtendMode aExtendMode
) {
431 MatrixScales scaleFactor
=
432 aContext
->CurrentMatrix().ScaleFactors().ConvertTo
<float>();
433 Matrix scaleMatrix
= Matrix::Scaling(scaleFactor
.xScale
, scaleFactor
.yScale
);
434 const float fuzzFactor
= 0.01;
436 // If we aren't scaling or translating, don't go down this path
437 if ((FuzzyEqual(scaleFactor
.xScale
, 1.0f
, fuzzFactor
) &&
438 FuzzyEqual(scaleFactor
.yScale
, 1.0f
, fuzzFactor
)) ||
439 aContext
->CurrentMatrix().HasNonAxisAlignedTransform()) {
443 gfxRect clipExtents
= aContext
->GetClipExtents();
445 // Inflate by one pixel because bilinear filtering will sample at most
446 // one pixel beyond the computed image pixel coordinate.
447 clipExtents
.Inflate(1.0);
449 gfxRect needed
= aRegion
.IntersectAndRestrict(clipExtents
);
450 Rect scaledNeededRect
= scaleMatrix
.TransformBounds(ToRect(needed
));
451 scaledNeededRect
.RoundOut();
452 if (scaledNeededRect
.IsEmpty()) {
456 Rect scaledImageRect
= scaleMatrix
.TransformBounds(aImageRect
);
457 if (!ShouldUseTempSurface(scaledImageRect
, scaledNeededRect
)) {
461 IntSize
scaledImageSize((int32_t)scaledImageRect
.width
,
462 (int32_t)scaledImageRect
.height
);
463 if (scaledImageSize
.width
!= scaledImageRect
.width
||
464 scaledImageSize
.height
!= scaledImageRect
.height
) {
465 // If the scaled image isn't pixel aligned, we'll get artifacts
466 // so we have to take the slow path.
470 RefPtr
<DrawTarget
> scaledDT
=
471 gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
472 scaledImageSize
, aFormat
);
473 if (!scaledDT
|| !scaledDT
->IsValid()) {
477 gfxContext
tmpCtx(scaledDT
);
479 scaledDT
->SetTransform(scaleMatrix
);
480 gfxRect
gfxImageRect(aImageRect
.x
, aImageRect
.y
, aImageRect
.width
,
483 // Since this is just the scaled image, we don't want to repeat anything yet.
484 aDrawable
->Draw(&tmpCtx
, gfxImageRect
, ExtendMode::CLAMP
, aSamplingFilter
,
487 RefPtr
<SourceSurface
> scaledImage
= scaledDT
->Snapshot();
490 gfxContextMatrixAutoSaveRestore
autoSR(aContext
);
491 Matrix withoutScale
= aContext
->CurrentMatrix();
492 DrawTarget
* destDrawTarget
= aContext
->GetDrawTarget();
494 // The translation still is in scaled units
495 withoutScale
.PreScale(1.0f
/ scaleFactor
.xScale
, 1.0f
/ scaleFactor
.yScale
);
496 aContext
->SetMatrix(withoutScale
);
498 DrawOptions
drawOptions(aOpacity
, aContext
->CurrentOp(),
499 aContext
->CurrentAntialiasMode());
501 SurfacePattern
scaledImagePattern(scaledImage
, aExtendMode
, Matrix(),
503 destDrawTarget
->FillRect(scaledNeededRect
, scaledImagePattern
, drawOptions
);
507 #endif // MOZ_WIDGET_COCOA
510 void gfxUtils::DrawPixelSnapped(gfxContext
* aContext
, gfxDrawable
* aDrawable
,
511 const gfxSize
& aImageSize
,
512 const ImageRegion
& aRegion
,
513 const SurfaceFormat aFormat
,
514 SamplingFilter aSamplingFilter
,
515 uint32_t aImageFlags
, gfxFloat aOpacity
,
516 bool aUseOptimalFillOp
) {
517 AUTO_PROFILER_LABEL("gfxUtils::DrawPixelSnapped", GRAPHICS
);
519 gfxRect
imageRect(gfxPoint(0, 0), aImageSize
);
520 gfxRect
region(aRegion
.Rect());
521 ExtendMode extendMode
= aRegion
.GetExtendMode();
523 RefPtr
<gfxDrawable
> drawable
= aDrawable
;
525 aSamplingFilter
= ReduceResamplingFilter(aSamplingFilter
, imageRect
.Width(),
526 imageRect
.Height(), region
.Width(),
529 // OK now, the hard part left is to account for the subimage sampling
530 // restriction. If all the transforms involved are just integer
531 // translations, then we assume no resampling will occur so there's
533 // XXX if only we had source-clipping in cairo!
535 if (aContext
->CurrentMatrix().HasNonIntegerTranslation()) {
536 if ((extendMode
!= ExtendMode::CLAMP
) ||
537 !aRegion
.RestrictionContains(imageRect
)) {
538 if (drawable
->DrawWithSamplingRect(
539 aContext
->GetDrawTarget(), aContext
->CurrentOp(),
540 aContext
->CurrentAntialiasMode(), aRegion
.Rect(),
541 aRegion
.Restriction(), extendMode
, aSamplingFilter
, aOpacity
)) {
545 #ifdef MOZ_WIDGET_COCOA
546 if (PrescaleAndTileDrawable(aDrawable
, aContext
, aRegion
,
547 ToRect(imageRect
), aSamplingFilter
, aFormat
,
548 aOpacity
, extendMode
)) {
553 // On Mobile, we don't ever want to do this; it has the potential for
554 // allocating very large temporary surfaces, especially since we'll
555 // do full-page snapshots often (see bug 749426).
556 #if !defined(MOZ_GFX_OPTIMIZE_MOBILE)
557 RefPtr
<gfxDrawable
> restrictedDrawable
= CreateSamplingRestrictedDrawable(
558 aDrawable
, aContext
, aRegion
, aFormat
, aUseOptimalFillOp
);
559 if (restrictedDrawable
) {
560 drawable
.swap(restrictedDrawable
);
562 // We no longer need to tile: Either we never needed to, or we already
563 // filled a surface with the tiled pattern; this surface can now be
564 // drawn without tiling.
565 extendMode
= ExtendMode::CLAMP
;
571 drawable
->Draw(aContext
, aRegion
.Rect(), extendMode
, aSamplingFilter
,
572 aOpacity
, gfxMatrix());
576 int gfxUtils::ImageFormatToDepth(gfxImageFormat aFormat
) {
578 case SurfaceFormat::A8R8G8B8_UINT32
:
580 case SurfaceFormat::X8R8G8B8_UINT32
:
582 case SurfaceFormat::R5G6B5_UINT16
:
591 void gfxUtils::ClipToRegion(gfxContext
* aContext
, const nsIntRegion
& aRegion
) {
593 for (auto iter
= aRegion
.RectIter(); !iter
.Done(); iter
.Next()) {
594 const IntRect
& r
= iter
.Get();
595 aContext
->Rectangle(gfxRect(r
.X(), r
.Y(), r
.Width(), r
.Height()));
601 void gfxUtils::ClipToRegion(DrawTarget
* aTarget
, const nsIntRegion
& aRegion
) {
602 uint32_t numRects
= aRegion
.GetNumRects();
603 // If there is only one rect, then the region bounds are equivalent to the
604 // contents. So just use push a single clip rect with the bounds.
606 aTarget
->PushClipRect(Rect(aRegion
.GetBounds()));
610 // Check if the target's transform will preserve axis-alignment and
611 // pixel-alignment for each rect. For now, just handle the common case
612 // of integer translations.
613 Matrix transform
= aTarget
->GetTransform();
614 if (transform
.IsIntegerTranslation()) {
615 IntPoint translation
= RoundedToInt(transform
.GetTranslation());
616 AutoTArray
<IntRect
, 16> rects
;
617 rects
.SetLength(numRects
);
619 // Build the list of transformed rects by adding in the translation.
620 for (auto iter
= aRegion
.RectIter(); !iter
.Done(); iter
.Next()) {
621 IntRect rect
= iter
.Get();
622 rect
.MoveBy(translation
);
625 aTarget
->PushDeviceSpaceClipRects(rects
.Elements(), rects
.Length());
627 // The transform does not produce axis-aligned rects or a rect was not
628 // pixel-aligned. So just build a path with all the rects and clip to it
630 RefPtr
<PathBuilder
> pathBuilder
= aTarget
->CreatePathBuilder();
631 for (auto iter
= aRegion
.RectIter(); !iter
.Done(); iter
.Next()) {
632 AppendRectToPath(pathBuilder
, Rect(iter
.Get()));
634 RefPtr
<Path
> path
= pathBuilder
->Finish();
635 aTarget
->PushClip(path
);
640 float gfxUtils::ClampToScaleFactor(float aVal
, bool aRoundDown
) {
641 // Arbitary scale factor limitation. We can increase this
642 // for better scaling performance at the cost of worse
644 static const float kScaleResolution
= 2;
646 // Negative scaling is just a flip and irrelevant to
647 // our resolution calculation.
652 bool inverse
= false;
658 float power
= logf(aVal
) / logf(kScaleResolution
);
660 // If power is within 1e-5 of an integer, round to nearest to
661 // prevent floating point errors, otherwise round up to the
662 // next integer value.
663 if (fabs(power
- NS_round(power
)) < 1e-5) {
664 power
= NS_round(power
);
665 // Use floor when we are either inverted or rounding down, but
667 } else if (inverse
!= aRoundDown
) {
668 power
= floor(power
);
669 // Otherwise, ceil when we are not inverted and not rounding
670 // down, or we are inverted and rounding down.
675 float scale
= powf(kScaleResolution
, power
);
684 gfxMatrix
gfxUtils::TransformRectToRect(const gfxRect
& aFrom
,
685 const gfxPoint
& aToTopLeft
,
686 const gfxPoint
& aToTopRight
,
687 const gfxPoint
& aToBottomRight
) {
689 if (aToTopRight
.y
== aToTopLeft
.y
&& aToTopRight
.x
== aToBottomRight
.x
) {
690 // Not a rotation, so xy and yx are zero
692 m
._11
= (aToBottomRight
.x
- aToTopLeft
.x
) / aFrom
.Width();
693 m
._22
= (aToBottomRight
.y
- aToTopLeft
.y
) / aFrom
.Height();
694 m
._31
= aToTopLeft
.x
- m
._11
* aFrom
.X();
695 m
._32
= aToTopLeft
.y
- m
._22
* aFrom
.Y();
698 aToTopRight
.y
== aToBottomRight
.y
&& aToTopRight
.x
== aToTopLeft
.x
,
699 "Destination rectangle not axis-aligned");
701 m
._21
= (aToBottomRight
.x
- aToTopLeft
.x
) / aFrom
.Height();
702 m
._12
= (aToBottomRight
.y
- aToTopLeft
.y
) / aFrom
.Width();
703 m
._31
= aToTopLeft
.x
- m
._21
* aFrom
.Y();
704 m
._32
= aToTopLeft
.y
- m
._12
* aFrom
.X();
709 Matrix
gfxUtils::TransformRectToRect(const gfxRect
& aFrom
,
710 const IntPoint
& aToTopLeft
,
711 const IntPoint
& aToTopRight
,
712 const IntPoint
& aToBottomRight
) {
714 if (aToTopRight
.y
== aToTopLeft
.y
&& aToTopRight
.x
== aToBottomRight
.x
) {
715 // Not a rotation, so xy and yx are zero
717 m
._11
= (aToBottomRight
.x
- aToTopLeft
.x
) / aFrom
.Width();
718 m
._22
= (aToBottomRight
.y
- aToTopLeft
.y
) / aFrom
.Height();
719 m
._31
= aToTopLeft
.x
- m
._11
* aFrom
.X();
720 m
._32
= aToTopLeft
.y
- m
._22
* aFrom
.Y();
723 aToTopRight
.y
== aToBottomRight
.y
&& aToTopRight
.x
== aToTopLeft
.x
,
724 "Destination rectangle not axis-aligned");
726 m
._21
= (aToBottomRight
.x
- aToTopLeft
.x
) / aFrom
.Height();
727 m
._12
= (aToBottomRight
.y
- aToTopLeft
.y
) / aFrom
.Width();
728 m
._31
= aToTopLeft
.x
- m
._21
* aFrom
.Y();
729 m
._32
= aToTopLeft
.y
- m
._12
* aFrom
.X();
734 /* This function is sort of shitty. We truncate doubles
735 * to ints then convert those ints back to doubles to make sure that
736 * they equal the doubles that we got in. */
737 bool gfxUtils::GfxRectToIntRect(const gfxRect
& aIn
, IntRect
* aOut
) {
738 *aOut
= IntRect(int32_t(aIn
.X()), int32_t(aIn
.Y()), int32_t(aIn
.Width()),
739 int32_t(aIn
.Height()));
740 return gfxRect(aOut
->X(), aOut
->Y(), aOut
->Width(), aOut
->Height())
744 /* Clamp r to CAIRO_COORD_MIN .. CAIRO_COORD_MAX
745 * these are to be device coordinates.
747 * Cairo is currently using 24.8 fixed point,
748 * so -2^24 .. 2^24-1 is our valid
751 void gfxUtils::ConditionRect(gfxRect
& aRect
) {
752 #define CAIRO_COORD_MAX (16777215.0)
753 #define CAIRO_COORD_MIN (-16777216.0)
754 // if either x or y is way out of bounds;
755 // note that we don't handle negative w/h here
756 if (aRect
.X() > CAIRO_COORD_MAX
) {
757 aRect
.SetRectX(CAIRO_COORD_MAX
, 0.0);
760 if (aRect
.Y() > CAIRO_COORD_MAX
) {
761 aRect
.SetRectY(CAIRO_COORD_MAX
, 0.0);
764 if (aRect
.X() < CAIRO_COORD_MIN
) {
765 aRect
.SetWidth(aRect
.XMost() - CAIRO_COORD_MIN
);
766 if (aRect
.Width() < 0.0) {
769 aRect
.MoveToX(CAIRO_COORD_MIN
);
772 if (aRect
.Y() < CAIRO_COORD_MIN
) {
773 aRect
.SetHeight(aRect
.YMost() - CAIRO_COORD_MIN
);
774 if (aRect
.Height() < 0.0) {
775 aRect
.SetHeight(0.0);
777 aRect
.MoveToY(CAIRO_COORD_MIN
);
780 if (aRect
.XMost() > CAIRO_COORD_MAX
) {
781 aRect
.SetRightEdge(CAIRO_COORD_MAX
);
784 if (aRect
.YMost() > CAIRO_COORD_MAX
) {
785 aRect
.SetBottomEdge(CAIRO_COORD_MAX
);
787 #undef CAIRO_COORD_MAX
788 #undef CAIRO_COORD_MIN
792 gfxQuad
gfxUtils::TransformToQuad(const gfxRect
& aRect
,
793 const mozilla::gfx::Matrix4x4
& aMatrix
) {
796 points
[0] = aMatrix
.TransformPoint(aRect
.TopLeft());
797 points
[1] = aMatrix
.TransformPoint(aRect
.TopRight());
798 points
[2] = aMatrix
.TransformPoint(aRect
.BottomRight());
799 points
[3] = aMatrix
.TransformPoint(aRect
.BottomLeft());
801 // Could this ever result in lines that intersect? I don't think so.
802 return gfxQuad(points
[0], points
[1], points
[2], points
[3]);
805 Matrix4x4
gfxUtils::SnapTransformTranslation(const Matrix4x4
& aTransform
,
806 Matrix
* aResidualTransform
) {
807 if (aResidualTransform
) {
808 *aResidualTransform
= Matrix();
812 if (aTransform
.CanDraw2D(&matrix2D
) && !matrix2D
.HasNonTranslation() &&
813 matrix2D
.HasNonIntegerTranslation()) {
814 return Matrix4x4::From2D(
815 SnapTransformTranslation(matrix2D
, aResidualTransform
));
818 return SnapTransformTranslation3D(aTransform
, aResidualTransform
);
821 Matrix
gfxUtils::SnapTransformTranslation(const Matrix
& aTransform
,
822 Matrix
* aResidualTransform
) {
823 if (aResidualTransform
) {
824 *aResidualTransform
= Matrix();
827 if (!aTransform
.HasNonTranslation() &&
828 aTransform
.HasNonIntegerTranslation()) {
829 auto snappedTranslation
= IntPoint::Round(aTransform
.GetTranslation());
830 Matrix snappedMatrix
=
831 Matrix::Translation(snappedTranslation
.x
, snappedTranslation
.y
);
832 if (aResidualTransform
) {
833 // set aResidualTransform so that aResidual * snappedMatrix == matrix2D.
834 // (I.e., appying snappedMatrix after aResidualTransform gives the
836 *aResidualTransform
=
837 Matrix::Translation(aTransform
._31
- snappedTranslation
.x
,
838 aTransform
._32
- snappedTranslation
.y
);
840 return snappedMatrix
;
846 Matrix4x4
gfxUtils::SnapTransformTranslation3D(const Matrix4x4
& aTransform
,
847 Matrix
* aResidualTransform
) {
848 if (aTransform
.IsSingular() || aTransform
.HasPerspectiveComponent() ||
849 aTransform
.HasNonTranslation() ||
850 !aTransform
.HasNonIntegerTranslation()) {
851 // For a singular transform, there is no reversed matrix, so we
853 // For a perspective transform, the content is transformed in
854 // non-linear, so we don't snap it too.
858 // Snap for 3D Transforms
860 Point3D transformedOrigin
= aTransform
.TransformPoint(Point3D());
862 // Compute the transformed snap by rounding the values of
863 // transformed origin.
864 auto transformedSnapXY
=
865 IntPoint::Round(transformedOrigin
.x
, transformedOrigin
.y
);
866 Matrix4x4 inverse
= aTransform
;
868 // see Matrix4x4::ProjectPoint()
869 Float transformedSnapZ
=
871 : (-(transformedSnapXY
.x
* inverse
._13
+
872 transformedSnapXY
.y
* inverse
._23
+ inverse
._43
) /
874 Point3D transformedSnap
=
875 Point3D(transformedSnapXY
.x
, transformedSnapXY
.y
, transformedSnapZ
);
876 if (transformedOrigin
== transformedSnap
) {
880 // Compute the snap from the transformed snap.
881 Point3D snap
= inverse
.TransformPoint(transformedSnap
);
882 if (snap
.z
> 0.001 || snap
.z
< -0.001) {
883 // Allow some level of accumulated computation error.
884 MOZ_ASSERT(inverse
._33
== 0.0);
888 // The difference between the origin and snap is the residual transform.
889 if (aResidualTransform
) {
890 // The residual transform is to translate the snap to the origin
891 // of the content buffer.
892 *aResidualTransform
= Matrix::Translation(-snap
.x
, -snap
.y
);
895 // Translate transformed origin to transformed snap since the
896 // residual transform would trnslate the snap to the origin.
897 Point3D transformedShift
= transformedSnap
- transformedOrigin
;
898 Matrix4x4 result
= aTransform
;
899 result
.PostTranslate(transformedShift
.x
, transformedShift
.y
,
902 // For non-2d transform, residual translation could be more than
903 // 0.5 pixels for every axis.
908 Matrix4x4
gfxUtils::SnapTransform(const Matrix4x4
& aTransform
,
909 const gfxRect
& aSnapRect
,
910 Matrix
* aResidualTransform
) {
911 if (aResidualTransform
) {
912 *aResidualTransform
= Matrix();
916 if (aTransform
.Is2D(&matrix2D
)) {
917 return Matrix4x4::From2D(
918 SnapTransform(matrix2D
, aSnapRect
, aResidualTransform
));
923 Matrix
gfxUtils::SnapTransform(const Matrix
& aTransform
,
924 const gfxRect
& aSnapRect
,
925 Matrix
* aResidualTransform
) {
926 if (aResidualTransform
) {
927 *aResidualTransform
= Matrix();
930 if (gfxSize(1.0, 1.0) <= aSnapRect
.Size() &&
931 aTransform
.PreservesAxisAlignedRectangles()) {
932 auto transformedTopLeft
= IntPoint::Round(
933 aTransform
.TransformPoint(ToPoint(aSnapRect
.TopLeft())));
934 auto transformedTopRight
= IntPoint::Round(
935 aTransform
.TransformPoint(ToPoint(aSnapRect
.TopRight())));
936 auto transformedBottomRight
= IntPoint::Round(
937 aTransform
.TransformPoint(ToPoint(aSnapRect
.BottomRight())));
939 Matrix snappedMatrix
= gfxUtils::TransformRectToRect(
940 aSnapRect
, transformedTopLeft
, transformedTopRight
,
941 transformedBottomRight
);
943 if (aResidualTransform
&& !snappedMatrix
.IsSingular()) {
944 // set aResidualTransform so that aResidual * snappedMatrix == matrix2D.
945 // (i.e., appying snappedMatrix after aResidualTransform gives the
947 Matrix snappedMatrixInverse
= snappedMatrix
;
948 snappedMatrixInverse
.Invert();
949 *aResidualTransform
= aTransform
* snappedMatrixInverse
;
951 return snappedMatrix
;
957 void gfxUtils::ClearThebesSurface(gfxASurface
* aSurface
) {
958 if (aSurface
->CairoStatus()) {
961 cairo_surface_t
* surf
= aSurface
->CairoSurface();
962 if (cairo_surface_status(surf
)) {
965 cairo_t
* ctx
= cairo_create(surf
);
966 cairo_set_source_rgba(ctx
, 0.0, 0.0, 0.0, 0.0);
967 cairo_set_operator(ctx
, CAIRO_OPERATOR_SOURCE
);
968 IntRect
bounds(nsIntPoint(0, 0), aSurface
->GetSize());
969 cairo_rectangle(ctx
, bounds
.X(), bounds
.Y(), bounds
.Width(), bounds
.Height());
975 already_AddRefed
<DataSourceSurface
>
976 gfxUtils::CopySurfaceToDataSourceSurfaceWithFormat(SourceSurface
* aSurface
,
977 SurfaceFormat aFormat
) {
978 MOZ_ASSERT(aFormat
!= aSurface
->GetFormat(),
979 "Unnecessary - and very expersive - surface format conversion");
981 Rect
bounds(0, 0, aSurface
->GetSize().width
, aSurface
->GetSize().height
);
983 if (!aSurface
->IsDataSourceSurface()) {
984 // If the surface is NOT of type DATA then its data is not mapped into main
985 // memory. Format conversion is probably faster on the GPU, and by doing it
986 // there we can avoid any expensive uploads/readbacks except for (possibly)
987 // a single readback due to the unavoidable GetDataSurface() call. Using
988 // CreateOffscreenContentDrawTarget ensures the conversion happens on the
990 RefPtr
<DrawTarget
> dt
=
991 gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
992 aSurface
->GetSize(), aFormat
);
994 gfxWarning() << "gfxUtils::CopySurfaceToDataSourceSurfaceWithFormat "
995 "failed in CreateOffscreenContentDrawTarget";
999 // Using DrawSurface() here rather than CopySurface() because CopySurface
1000 // is optimized for memcpy and therefore isn't good for format conversion.
1001 // Using OP_OVER since in our case it's equivalent to OP_SOURCE and
1002 // generally more optimized.
1003 dt
->DrawSurface(aSurface
, bounds
, bounds
, DrawSurfaceOptions(),
1004 DrawOptions(1.0f
, CompositionOp::OP_OVER
));
1005 RefPtr
<SourceSurface
> surface
= dt
->Snapshot();
1006 return surface
->GetDataSurface();
1009 // If the surface IS of type DATA then it may or may not be in main memory
1010 // depending on whether or not it has been mapped yet. We have no way of
1011 // knowing, so we can't be sure if it's best to create a data wrapping
1012 // DrawTarget for the conversion or an offscreen content DrawTarget. We could
1013 // guess it's not mapped and create an offscreen content DrawTarget, but if
1014 // it is then we'll end up uploading the surface data, and most likely the
1015 // caller is going to be accessing the resulting surface data, resulting in a
1016 // readback (both very expensive operations). Alternatively we could guess
1017 // the data is mapped and create a data wrapping DrawTarget and, if the
1018 // surface is not in main memory, then we will incure a readback. The latter
1019 // of these two "wrong choices" is the least costly (a readback, vs an
1020 // upload and a readback), and more than likely the DATA surface that we've
1021 // been passed actually IS in main memory anyway. For these reasons it's most
1022 // likely best to create a data wrapping DrawTarget here to do the format
1024 RefPtr
<DataSourceSurface
> dataSurface
=
1025 Factory::CreateDataSourceSurface(aSurface
->GetSize(), aFormat
);
1026 DataSourceSurface::MappedSurface map
;
1028 !dataSurface
->Map(DataSourceSurface::MapType::READ_WRITE
, &map
)) {
1031 RefPtr
<DrawTarget
> dt
= Factory::CreateDrawTargetForData(
1032 BackendType::CAIRO
, map
.mData
, dataSurface
->GetSize(), map
.mStride
,
1035 dataSurface
->Unmap();
1038 // Using DrawSurface() here rather than CopySurface() because CopySurface
1039 // is optimized for memcpy and therefore isn't good for format conversion.
1040 // Using OP_OVER since in our case it's equivalent to OP_SOURCE and
1041 // generally more optimized.
1042 dt
->DrawSurface(aSurface
, bounds
, bounds
, DrawSurfaceOptions(),
1043 DrawOptions(1.0f
, CompositionOp::OP_OVER
));
1044 dataSurface
->Unmap();
1045 return dataSurface
.forget();
1048 const uint32_t gfxUtils::sNumFrameColors
= 8;
1051 const gfx::DeviceColor
& gfxUtils::GetColorForFrameNumber(
1052 uint64_t aFrameNumber
) {
1053 static bool initialized
= false;
1054 static gfx::DeviceColor colors
[sNumFrameColors
];
1057 // This isn't truly device color, but it is just for debug.
1059 colors
[i
++] = gfx::DeviceColor::FromABGR(0xffff0000);
1060 colors
[i
++] = gfx::DeviceColor::FromABGR(0xffcc00ff);
1061 colors
[i
++] = gfx::DeviceColor::FromABGR(0xff0066cc);
1062 colors
[i
++] = gfx::DeviceColor::FromABGR(0xff00ff00);
1063 colors
[i
++] = gfx::DeviceColor::FromABGR(0xff33ffff);
1064 colors
[i
++] = gfx::DeviceColor::FromABGR(0xffff0099);
1065 colors
[i
++] = gfx::DeviceColor::FromABGR(0xff0000ff);
1066 colors
[i
++] = gfx::DeviceColor::FromABGR(0xff999999);
1067 MOZ_ASSERT(i
== sNumFrameColors
);
1071 return colors
[aFrameNumber
% sNumFrameColors
];
1075 nsresult
gfxUtils::EncodeSourceSurfaceAsStream(SourceSurface
* aSurface
,
1076 const ImageType aImageType
,
1077 const nsAString
& aOutputOptions
,
1078 nsIInputStream
** aOutStream
) {
1079 const IntSize size
= aSurface
->GetSize();
1080 if (size
.IsEmpty()) {
1081 return NS_ERROR_FAILURE
;
1084 RefPtr
<DataSourceSurface
> dataSurface
;
1085 if (aSurface
->GetFormat() != SurfaceFormat::B8G8R8A8
) {
1086 // FIXME bug 995807 (B8G8R8X8), bug 831898 (R5G6B5)
1087 dataSurface
= gfxUtils::CopySurfaceToDataSourceSurfaceWithFormat(
1088 aSurface
, SurfaceFormat::B8G8R8A8
);
1090 dataSurface
= aSurface
->GetDataSurface();
1093 return NS_ERROR_FAILURE
;
1096 DataSourceSurface::MappedSurface map
;
1097 if (!dataSurface
->Map(DataSourceSurface::MapType::READ
, &map
)) {
1098 return NS_ERROR_FAILURE
;
1101 RefPtr
<imgIEncoder
> encoder
= nullptr;
1103 switch (aImageType
) {
1104 case ImageType::BMP
:
1105 encoder
= MakeRefPtr
<nsBMPEncoder
>();
1108 case ImageType::ICO
:
1109 encoder
= MakeRefPtr
<nsICOEncoder
>();
1112 case ImageType::JPEG
:
1113 encoder
= MakeRefPtr
<nsJPEGEncoder
>();
1116 case ImageType::PNG
:
1117 encoder
= MakeRefPtr
<nsPNGEncoder
>();
1124 MOZ_RELEASE_ASSERT(encoder
!= nullptr);
1126 nsresult rv
= encoder
->InitFromData(
1127 map
.mData
, BufferSizeFromStrideAndHeight(map
.mStride
, size
.height
),
1128 size
.width
, size
.height
, map
.mStride
, imgIEncoder::INPUT_FORMAT_HOSTARGB
,
1130 dataSurface
->Unmap();
1131 if (NS_FAILED(rv
)) {
1132 return NS_ERROR_FAILURE
;
1135 nsCOMPtr
<nsIInputStream
> imgStream(encoder
);
1137 return NS_ERROR_FAILURE
;
1140 imgStream
.forget(aOutStream
);
1146 Maybe
<nsTArray
<uint8_t>> gfxUtils::EncodeSourceSurfaceAsBytes(
1147 SourceSurface
* aSurface
, const ImageType aImageType
,
1148 const nsAString
& aOutputOptions
) {
1149 nsCOMPtr
<nsIInputStream
> imgStream
;
1150 nsresult rv
= EncodeSourceSurfaceAsStream(
1151 aSurface
, aImageType
, aOutputOptions
, getter_AddRefs(imgStream
));
1152 if (NS_FAILED(rv
)) {
1157 rv
= imgStream
->Available(&bufSize64
);
1158 if (NS_FAILED(rv
) || bufSize64
> UINT32_MAX
) {
1162 uint32_t bytesLeft
= static_cast<uint32_t>(bufSize64
);
1164 nsTArray
<uint8_t> imgData
;
1165 imgData
.SetLength(bytesLeft
);
1166 uint8_t* bytePtr
= imgData
.Elements();
1168 while (bytesLeft
> 0) {
1169 uint32_t bytesRead
= 0;
1170 rv
= imgStream
->Read(reinterpret_cast<char*>(bytePtr
), bytesLeft
,
1172 if (NS_FAILED(rv
) || bytesRead
== 0) {
1176 bytePtr
+= bytesRead
;
1177 bytesLeft
-= bytesRead
;
1182 // Currently, all implementers of imgIEncoder report their exact size through
1183 // nsIInputStream::Available(), but let's explicitly state that we rely on
1184 // that behavior for the algorithm above.
1187 uint32_t bytesRead
= 0;
1188 rv
= imgStream
->Read(&dummy
, 1, &bytesRead
);
1189 MOZ_ASSERT(NS_SUCCEEDED(rv
) && bytesRead
== 0);
1193 return Some(std::move(imgData
));
1197 nsresult
gfxUtils::EncodeSourceSurface(SourceSurface
* aSurface
,
1198 const ImageType aImageType
,
1199 const nsAString
& aOutputOptions
,
1200 BinaryOrData aBinaryOrData
, FILE* aFile
,
1201 nsACString
* aStrOut
) {
1202 MOZ_ASSERT(aBinaryOrData
== gfxUtils::eDataURIEncode
|| aFile
|| aStrOut
,
1203 "Copying binary encoding to clipboard not currently supported");
1206 EncodeSourceSurfaceAsBytes(aSurface
, aImageType
, aOutputOptions
);
1207 if (!maybeImgData
) {
1208 return NS_ERROR_FAILURE
;
1211 nsTArray
<uint8_t>& imgData
= *maybeImgData
;
1213 if (aBinaryOrData
== gfxUtils::eBinaryEncode
) {
1215 Unused
<< fwrite(imgData
.Elements(), 1, imgData
.Length(), aFile
);
1220 nsCString stringBuf
;
1221 nsACString
& dataURI
= aStrOut
? *aStrOut
: stringBuf
;
1222 dataURI
.AppendLiteral("data:");
1224 switch (aImageType
) {
1225 case ImageType::BMP
:
1226 dataURI
.AppendLiteral(IMAGE_BMP
);
1229 case ImageType::ICO
:
1230 dataURI
.AppendLiteral(IMAGE_ICO_MS
);
1232 case ImageType::JPEG
:
1233 dataURI
.AppendLiteral(IMAGE_JPEG
);
1236 case ImageType::PNG
:
1237 dataURI
.AppendLiteral(IMAGE_PNG
);
1244 dataURI
.AppendLiteral(";base64,");
1245 nsresult rv
= Base64EncodeAppend(reinterpret_cast<char*>(imgData
.Elements()),
1246 imgData
.Length(), dataURI
);
1247 NS_ENSURE_SUCCESS(rv
, rv
);
1251 if (aFile
== stdout
|| aFile
== stderr
) {
1252 // ADB logcat cuts off long strings so we will break it down
1253 const char* cStr
= dataURI
.BeginReading();
1254 size_t len
= strlen(cStr
);
1256 printf_stderr("IMG: %.140s\n", cStr
);
1257 if (len
<= 140) break;
1263 fprintf(aFile
, "%s", dataURI
.BeginReading());
1264 } else if (!aStrOut
) {
1265 nsCOMPtr
<nsIClipboardHelper
> clipboard(
1266 do_GetService("@mozilla.org/widget/clipboardhelper;1", &rv
));
1268 clipboard
->CopyString(NS_ConvertASCIItoUTF16(dataURI
));
1274 static nsCString
EncodeSourceSurfaceAsPNGURI(SourceSurface
* aSurface
) {
1276 gfxUtils::EncodeSourceSurface(aSurface
, ImageType::PNG
, u
""_ns
,
1277 gfxUtils::eDataURIEncode
, nullptr, &string
);
1281 // https://jdashg.github.io/misc/colors/from-coeffs.html
1282 const float kBT601NarrowYCbCrToRGB_RowMajor
[16] = {
1283 1.16438f
, 0.00000f
, 1.59603f
, -0.87420f
, 1.16438f
, -0.39176f
,
1284 -0.81297f
, 0.53167f
, 1.16438f
, 2.01723f
, 0.00000f
, -1.08563f
,
1285 0.00000f
, 0.00000f
, 0.00000f
, 1.00000f
};
1286 const float kBT709NarrowYCbCrToRGB_RowMajor
[16] = {
1287 1.16438f
, 0.00000f
, 1.79274f
, -0.97295f
, 1.16438f
, -0.21325f
,
1288 -0.53291f
, 0.30148f
, 1.16438f
, 2.11240f
, 0.00000f
, -1.13340f
,
1289 0.00000f
, 0.00000f
, 0.00000f
, 1.00000f
};
1290 const float kBT2020NarrowYCbCrToRGB_RowMajor
[16] = {
1291 1.16438f
, 0.00000f
, 1.67867f
, -0.91569f
, 1.16438f
, -0.18733f
,
1292 -0.65042f
, 0.34746f
, 1.16438f
, 2.14177f
, 0.00000f
, -1.14815f
,
1293 0.00000f
, 0.00000f
, 0.00000f
, 1.00000f
};
1294 const float kIdentityNarrowYCbCrToRGB_RowMajor
[16] = {
1295 0.00000f
, 0.00000f
, 1.00000f
, 0.00000f
, 1.00000f
, 0.00000f
,
1296 0.00000f
, 0.00000f
, 0.00000f
, 1.00000f
, 0.00000f
, 0.00000f
,
1297 0.00000f
, 0.00000f
, 0.00000f
, 1.00000f
};
1299 /* static */ const float* gfxUtils::YuvToRgbMatrix4x3RowMajor(
1300 gfx::YUVColorSpace aYUVColorSpace
) {
1302 { x[0], x[1], x[2], 0.0f, x[4], x[5], x[6], 0.0f, x[8], x[9], x[10], 0.0f }
1304 static const float rec601
[12] = X(kBT601NarrowYCbCrToRGB_RowMajor
);
1305 static const float rec709
[12] = X(kBT709NarrowYCbCrToRGB_RowMajor
);
1306 static const float rec2020
[12] = X(kBT2020NarrowYCbCrToRGB_RowMajor
);
1307 static const float identity
[12] = X(kIdentityNarrowYCbCrToRGB_RowMajor
);
1311 switch (aYUVColorSpace
) {
1312 case gfx::YUVColorSpace::BT601
:
1314 case gfx::YUVColorSpace::BT709
:
1316 case gfx::YUVColorSpace::BT2020
:
1318 case gfx::YUVColorSpace::Identity
:
1321 MOZ_CRASH("Bad YUVColorSpace");
1325 /* static */ const float* gfxUtils::YuvToRgbMatrix3x3ColumnMajor(
1326 gfx::YUVColorSpace aYUVColorSpace
) {
1328 { x[0], x[4], x[8], x[1], x[5], x[9], x[2], x[6], x[10] }
1330 static const float rec601
[9] = X(kBT601NarrowYCbCrToRGB_RowMajor
);
1331 static const float rec709
[9] = X(kBT709NarrowYCbCrToRGB_RowMajor
);
1332 static const float rec2020
[9] = X(kBT2020NarrowYCbCrToRGB_RowMajor
);
1333 static const float identity
[9] = X(kIdentityNarrowYCbCrToRGB_RowMajor
);
1337 switch (aYUVColorSpace
) {
1338 case gfx::YUVColorSpace::BT601
:
1340 case YUVColorSpace::BT709
:
1342 case YUVColorSpace::BT2020
:
1344 case YUVColorSpace::Identity
:
1347 MOZ_CRASH("Bad YUVColorSpace");
1351 /* static */ const float* gfxUtils::YuvToRgbMatrix4x4ColumnMajor(
1352 YUVColorSpace aYUVColorSpace
) {
1355 x[0], x[4], x[8], x[12], x[1], x[5], x[9], x[13], x[2], x[6], x[10], \
1356 x[14], x[3], x[7], x[11], x[15] \
1359 static const float rec601
[16] = X(kBT601NarrowYCbCrToRGB_RowMajor
);
1360 static const float rec709
[16] = X(kBT709NarrowYCbCrToRGB_RowMajor
);
1361 static const float rec2020
[16] = X(kBT2020NarrowYCbCrToRGB_RowMajor
);
1362 static const float identity
[16] = X(kIdentityNarrowYCbCrToRGB_RowMajor
);
1366 switch (aYUVColorSpace
) {
1367 case YUVColorSpace::BT601
:
1369 case YUVColorSpace::BT709
:
1371 case YUVColorSpace::BT2020
:
1373 case YUVColorSpace::Identity
:
1376 MOZ_CRASH("Bad YUVColorSpace");
1380 // Translate from CICP values to the color spaces we support, or return
1381 // Nothing() if there is no appropriate match to let the caller choose
1382 // a default or generate an error.
1384 // See Rec. ITU-T H.273 (12/2016) for details on CICP
1385 /* static */ Maybe
<gfx::YUVColorSpace
> gfxUtils::CicpToColorSpace(
1386 const CICP::MatrixCoefficients aMatrixCoefficients
,
1387 const CICP::ColourPrimaries aColourPrimaries
, LazyLogModule
& aLogger
) {
1388 switch (aMatrixCoefficients
) {
1389 case CICP::MatrixCoefficients::MC_BT2020_NCL
:
1390 case CICP::MatrixCoefficients::MC_BT2020_CL
:
1391 return Some(gfx::YUVColorSpace::BT2020
);
1392 case CICP::MatrixCoefficients::MC_BT601
:
1393 return Some(gfx::YUVColorSpace::BT601
);
1394 case CICP::MatrixCoefficients::MC_BT709
:
1395 return Some(gfx::YUVColorSpace::BT709
);
1396 case CICP::MatrixCoefficients::MC_IDENTITY
:
1397 return Some(gfx::YUVColorSpace::Identity
);
1398 case CICP::MatrixCoefficients::MC_CHROMAT_NCL
:
1399 case CICP::MatrixCoefficients::MC_CHROMAT_CL
:
1400 case CICP::MatrixCoefficients::MC_UNSPECIFIED
:
1401 switch (aColourPrimaries
) {
1402 case CICP::ColourPrimaries::CP_BT601
:
1403 return Some(gfx::YUVColorSpace::BT601
);
1404 case CICP::ColourPrimaries::CP_BT709
:
1405 return Some(gfx::YUVColorSpace::BT709
);
1406 case CICP::ColourPrimaries::CP_BT2020
:
1407 return Some(gfx::YUVColorSpace::BT2020
);
1409 MOZ_LOG(aLogger
, LogLevel::Debug
,
1410 ("Couldn't infer color matrix from primaries: %hhu",
1415 MOZ_LOG(aLogger
, LogLevel::Debug
,
1416 ("Unsupported color matrix value: %hhu", aMatrixCoefficients
));
1421 // Translate from CICP values to the color primaries we support, or return
1422 // Nothing() if there is no appropriate match to let the caller choose
1423 // a default or generate an error.
1425 // See Rec. ITU-T H.273 (12/2016) for details on CICP
1426 /* static */ Maybe
<gfx::ColorSpace2
> gfxUtils::CicpToColorPrimaries(
1427 const CICP::ColourPrimaries aColourPrimaries
, LazyLogModule
& aLogger
) {
1428 switch (aColourPrimaries
) {
1429 case CICP::ColourPrimaries::CP_BT709
:
1430 return Some(gfx::ColorSpace2::BT709
);
1431 case CICP::ColourPrimaries::CP_BT2020
:
1432 return Some(gfx::ColorSpace2::BT2020
);
1434 MOZ_LOG(aLogger
, LogLevel::Debug
,
1435 ("Unsupported color primaries value: %hhu", aColourPrimaries
));
1440 // Translate from CICP values to the transfer functions we support, or return
1441 // Nothing() if there is no appropriate match.
1443 /* static */ Maybe
<gfx::TransferFunction
> gfxUtils::CicpToTransferFunction(
1444 const CICP::TransferCharacteristics aTransferCharacteristics
) {
1445 switch (aTransferCharacteristics
) {
1446 case CICP::TransferCharacteristics::TC_BT709
:
1447 return Some(gfx::TransferFunction::BT709
);
1449 case CICP::TransferCharacteristics::TC_SRGB
:
1450 return Some(gfx::TransferFunction::SRGB
);
1452 case CICP::TransferCharacteristics::TC_SMPTE2084
:
1453 return Some(gfx::TransferFunction::PQ
);
1455 case CICP::TransferCharacteristics::TC_HLG
:
1456 return Some(gfx::TransferFunction::HLG
);
1464 void gfxUtils::WriteAsPNG(SourceSurface
* aSurface
, const nsAString
& aFile
) {
1465 WriteAsPNG(aSurface
, NS_ConvertUTF16toUTF8(aFile
).get());
1469 void gfxUtils::WriteAsPNG(SourceSurface
* aSurface
, const char* aFile
) {
1470 FILE* file
= fopen(aFile
, "wb");
1473 // Maybe the directory doesn't exist; try creating it, then fopen again.
1474 nsresult rv
= NS_ERROR_FAILURE
;
1475 nsCOMPtr
<nsIFile
> comFile
= do_CreateInstance("@mozilla.org/file/local;1");
1477 NS_ConvertUTF8toUTF16
utf16path((nsDependentCString(aFile
)));
1478 rv
= comFile
->InitWithPath(utf16path
);
1479 if (NS_SUCCEEDED(rv
)) {
1480 nsCOMPtr
<nsIFile
> dirPath
;
1481 comFile
->GetParent(getter_AddRefs(dirPath
));
1483 rv
= dirPath
->Create(nsIFile::DIRECTORY_TYPE
, 0777);
1484 if (NS_SUCCEEDED(rv
) || rv
== NS_ERROR_FILE_ALREADY_EXISTS
) {
1485 file
= fopen(aFile
, "wb");
1491 NS_WARNING("Failed to open file to create PNG!");
1496 EncodeSourceSurface(aSurface
, ImageType::PNG
, u
""_ns
, eBinaryEncode
, file
);
1501 void gfxUtils::WriteAsPNG(DrawTarget
* aDT
, const nsAString
& aFile
) {
1502 WriteAsPNG(aDT
, NS_ConvertUTF16toUTF8(aFile
).get());
1506 void gfxUtils::WriteAsPNG(DrawTarget
* aDT
, const char* aFile
) {
1507 RefPtr
<SourceSurface
> surface
= aDT
->Snapshot();
1509 WriteAsPNG(surface
, aFile
);
1511 NS_WARNING("Failed to get surface!");
1516 void gfxUtils::DumpAsDataURI(SourceSurface
* aSurface
, FILE* aFile
) {
1517 EncodeSourceSurface(aSurface
, ImageType::PNG
, u
""_ns
, eDataURIEncode
, aFile
);
1521 nsCString
gfxUtils::GetAsDataURI(SourceSurface
* aSurface
) {
1522 return EncodeSourceSurfaceAsPNGURI(aSurface
);
1526 void gfxUtils::DumpAsDataURI(DrawTarget
* aDT
, FILE* aFile
) {
1527 RefPtr
<SourceSurface
> surface
= aDT
->Snapshot();
1529 DumpAsDataURI(surface
, aFile
);
1531 NS_WARNING("Failed to get surface!");
1536 nsCString
gfxUtils::GetAsLZ4Base64Str(DataSourceSurface
* aSourceSurface
) {
1537 DataSourceSurface::ScopedMap
map(aSourceSurface
, DataSourceSurface::READ
);
1538 int32_t dataSize
= aSourceSurface
->GetSize().height
* map
.GetStride();
1539 auto compressedData
= MakeUnique
<char[]>(LZ4::maxCompressedSize(dataSize
));
1540 if (compressedData
) {
1542 LZ4::compress((char*)map
.GetData(), dataSize
, compressedData
.get());
1543 if (nDataSize
> 0) {
1545 string
.AppendPrintf("data:image/lz4bgra;base64,%i,%i,%i,",
1546 aSourceSurface
->GetSize().width
, map
.GetStride(),
1547 aSourceSurface
->GetSize().height
);
1548 nsresult rv
= Base64EncodeAppend(compressedData
.get(), nDataSize
, string
);
1558 nsCString
gfxUtils::GetAsDataURI(DrawTarget
* aDT
) {
1559 RefPtr
<SourceSurface
> surface
= aDT
->Snapshot();
1561 return EncodeSourceSurfaceAsPNGURI(surface
);
1563 NS_WARNING("Failed to get surface!");
1564 return nsCString("");
1569 void gfxUtils::CopyAsDataURI(SourceSurface
* aSurface
) {
1570 EncodeSourceSurface(aSurface
, ImageType::PNG
, u
""_ns
, eDataURIEncode
,
1575 void gfxUtils::CopyAsDataURI(DrawTarget
* aDT
) {
1576 RefPtr
<SourceSurface
> surface
= aDT
->Snapshot();
1578 CopyAsDataURI(surface
);
1580 NS_WARNING("Failed to get surface!");
1585 UniquePtr
<uint8_t[]> gfxUtils::GetImageBuffer(gfx::DataSourceSurface
* aSurface
,
1586 bool aIsAlphaPremultiplied
,
1587 int32_t* outFormat
) {
1590 DataSourceSurface::MappedSurface map
;
1591 if (!aSurface
->Map(DataSourceSurface::MapType::READ
, &map
)) return nullptr;
1593 uint32_t bufferSize
=
1594 aSurface
->GetSize().width
* aSurface
->GetSize().height
* 4;
1595 auto imageBuffer
= MakeUniqueFallible
<uint8_t[]>(bufferSize
);
1600 memcpy(imageBuffer
.get(), map
.mData
, bufferSize
);
1604 int32_t format
= imgIEncoder::INPUT_FORMAT_HOSTARGB
;
1605 if (!aIsAlphaPremultiplied
) {
1606 // We need to convert to INPUT_FORMAT_RGBA, otherwise
1607 // we are automatically considered premult, and unpremult'd.
1608 // Yes, it is THAT silly.
1609 // Except for different lossy conversions by color,
1610 // we could probably just change the label, and not change the data.
1611 gfxUtils::ConvertBGRAtoRGBA(imageBuffer
.get(), bufferSize
);
1612 format
= imgIEncoder::INPUT_FORMAT_RGBA
;
1615 *outFormat
= format
;
1620 UniquePtr
<uint8_t[]> gfxUtils::GetImageBufferWithRandomNoise(
1621 gfx::DataSourceSurface
* aSurface
, bool aIsAlphaPremultiplied
,
1622 nsICookieJarSettings
* aCookieJarSettings
, int32_t* outFormat
) {
1623 UniquePtr
<uint8_t[]> imageBuffer
=
1624 GetImageBuffer(aSurface
, aIsAlphaPremultiplied
, outFormat
);
1626 nsRFPService::RandomizePixels(
1627 aCookieJarSettings
, imageBuffer
.get(),
1628 aSurface
->GetSize().width
* aSurface
->GetSize().height
* 4,
1629 SurfaceFormat::A8R8G8B8_UINT32
);
1635 nsresult
gfxUtils::GetInputStream(gfx::DataSourceSurface
* aSurface
,
1636 bool aIsAlphaPremultiplied
,
1637 const char* aMimeType
,
1638 const nsAString
& aEncoderOptions
,
1639 nsIInputStream
** outStream
) {
1640 nsCString
enccid("@mozilla.org/image/encoder;2?type=");
1641 enccid
+= aMimeType
;
1642 nsCOMPtr
<imgIEncoder
> encoder
= do_CreateInstance(enccid
.get());
1643 if (!encoder
) return NS_ERROR_FAILURE
;
1646 UniquePtr
<uint8_t[]> imageBuffer
=
1647 GetImageBuffer(aSurface
, aIsAlphaPremultiplied
, &format
);
1648 if (!imageBuffer
) return NS_ERROR_FAILURE
;
1650 return dom::ImageEncoder::GetInputStream(
1651 aSurface
->GetSize().width
, aSurface
->GetSize().height
, imageBuffer
.get(),
1652 format
, encoder
, aEncoderOptions
, outStream
);
1656 nsresult
gfxUtils::GetInputStreamWithRandomNoise(
1657 gfx::DataSourceSurface
* aSurface
, bool aIsAlphaPremultiplied
,
1658 const char* aMimeType
, const nsAString
& aEncoderOptions
,
1659 nsICookieJarSettings
* aCookieJarSettings
, nsIInputStream
** outStream
) {
1660 nsCString
enccid("@mozilla.org/image/encoder;2?type=");
1661 enccid
+= aMimeType
;
1662 nsCOMPtr
<imgIEncoder
> encoder
= do_CreateInstance(enccid
.get());
1664 return NS_ERROR_FAILURE
;
1668 UniquePtr
<uint8_t[]> imageBuffer
=
1669 GetImageBuffer(aSurface
, aIsAlphaPremultiplied
, &format
);
1671 return NS_ERROR_FAILURE
;
1674 nsRFPService::RandomizePixels(
1675 aCookieJarSettings
, imageBuffer
.get(),
1676 aSurface
->GetSize().width
* aSurface
->GetSize().height
* 4,
1677 SurfaceFormat::A8R8G8B8_UINT32
);
1679 return dom::ImageEncoder::GetInputStream(
1680 aSurface
->GetSize().width
, aSurface
->GetSize().height
, imageBuffer
.get(),
1681 format
, encoder
, aEncoderOptions
, outStream
);
1684 class GetFeatureStatusWorkerRunnable final
1685 : public dom::WorkerMainThreadRunnable
{
1687 GetFeatureStatusWorkerRunnable(dom::WorkerPrivate
* workerPrivate
,
1688 const nsCOMPtr
<nsIGfxInfo
>& gfxInfo
,
1689 int32_t feature
, nsACString
& failureId
,
1691 : WorkerMainThreadRunnable(workerPrivate
, "GFX :: GetFeatureStatus"_ns
),
1695 mFailureId(failureId
),
1698 bool MainThreadRun() override
{
1700 mNSResult
= mGfxInfo
->GetFeatureStatus(mFeature
, mFailureId
, mStatus
);
1705 nsresult
GetNSResult() const { return mNSResult
; }
1708 ~GetFeatureStatusWorkerRunnable() = default;
1711 nsCOMPtr
<nsIGfxInfo
> mGfxInfo
;
1714 nsACString
& mFailureId
;
1718 #define GFX_SHADER_CHECK_BUILD_VERSION_PREF "gfx-shader-check.build-version"
1719 #define GFX_SHADER_CHECK_PTR_SIZE_PREF "gfx-shader-check.ptr-size"
1720 #define GFX_SHADER_CHECK_DEVICE_ID_PREF "gfx-shader-check.device-id"
1721 #define GFX_SHADER_CHECK_DRIVER_VERSION_PREF "gfx-shader-check.driver-version"
1724 void gfxUtils::RemoveShaderCacheFromDiskIfNecessary() {
1725 if (!gfxVars::UseWebRenderProgramBinaryDisk()) {
1729 nsCOMPtr
<nsIGfxInfo
> gfxInfo
= components::GfxInfo::Service();
1731 // Get current values
1732 nsCString
buildID(mozilla::PlatformBuildID());
1733 int ptrSize
= sizeof(void*);
1734 nsString deviceID
, driverVersion
;
1735 gfxInfo
->GetAdapterDeviceID(deviceID
);
1736 gfxInfo
->GetAdapterDriverVersion(driverVersion
);
1738 // Get pref stored values
1739 nsAutoCString buildIDChecked
;
1740 Preferences::GetCString(GFX_SHADER_CHECK_BUILD_VERSION_PREF
, buildIDChecked
);
1741 int ptrSizeChecked
= Preferences::GetInt(GFX_SHADER_CHECK_PTR_SIZE_PREF
, 0);
1742 nsAutoString deviceIDChecked
, driverVersionChecked
;
1743 Preferences::GetString(GFX_SHADER_CHECK_DEVICE_ID_PREF
, deviceIDChecked
);
1744 Preferences::GetString(GFX_SHADER_CHECK_DRIVER_VERSION_PREF
,
1745 driverVersionChecked
);
1747 if (buildID
== buildIDChecked
&& ptrSize
== ptrSizeChecked
&&
1748 deviceID
== deviceIDChecked
&& driverVersion
== driverVersionChecked
) {
1752 nsAutoString
path(gfx::gfxVars::ProfDirectory());
1754 if (!wr::remove_program_binary_disk_cache(&path
)) {
1755 // Failed to remove program binary disk cache. The disk cache might have
1756 // invalid data. Disable program binary disk cache usage.
1757 gfxVars::SetUseWebRenderProgramBinaryDisk(false);
1761 Preferences::SetCString(GFX_SHADER_CHECK_BUILD_VERSION_PREF
, buildID
);
1762 Preferences::SetInt(GFX_SHADER_CHECK_PTR_SIZE_PREF
, ptrSize
);
1763 Preferences::SetString(GFX_SHADER_CHECK_DEVICE_ID_PREF
, deviceID
);
1764 Preferences::SetString(GFX_SHADER_CHECK_DRIVER_VERSION_PREF
, driverVersion
);
1768 bool gfxUtils::DumpDisplayList() {
1769 return StaticPrefs::layout_display_list_dump() ||
1770 (StaticPrefs::layout_display_list_dump_parent() &&
1771 XRE_IsParentProcess()) ||
1772 (StaticPrefs::layout_display_list_dump_content() &&
1773 XRE_IsContentProcess());
1776 FILE* gfxUtils::sDumpPaintFile
= stderr
;
1781 DeviceColor
ToDeviceColor(const sRGBColor
& aColor
) {
1782 // aColor is pass-by-value since to get return value optimization goodness we
1783 // need to return the same object from all return points in this function. We
1784 // could declare a local Color variable and use that, but we might as well
1786 if (gfxPlatform::GetCMSMode() == CMSMode::All
) {
1787 qcms_transform
* transform
= gfxPlatform::GetCMSRGBTransform();
1789 return gfxPlatform::TransformPixel(aColor
, transform
);
1790 // Use the original alpha to avoid unnecessary float->byte->float
1791 // conversion errors
1794 return DeviceColor(aColor
.r
, aColor
.g
, aColor
.b
, aColor
.a
);
1797 DeviceColor
ToDeviceColor(nscolor aColor
) {
1798 return ToDeviceColor(sRGBColor::FromABGR(aColor
));
1801 DeviceColor
ToDeviceColor(const StyleAbsoluteColor
& aColor
) {
1802 return ToDeviceColor(aColor
.ToColor());
1805 sRGBColor
ToSRGBColor(const StyleAbsoluteColor
& aColor
) {
1806 auto srgb
= aColor
.ToColorSpace(StyleColorSpace::Srgb
);
1808 const auto ToComponent
= [](float aF
) -> float {
1809 float component
= std::min(std::max(0.0f
, aF
), 1.0f
);
1810 if (MOZ_UNLIKELY(!std::isfinite(component
))) {
1815 return {ToComponent(srgb
.components
._0
), ToComponent(srgb
.components
._1
),
1816 ToComponent(srgb
.components
._2
), ToComponent(srgb
.alpha
)};
1820 } // namespace mozilla