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/ImageEncoder.h"
20 #include "mozilla/dom/WorkerPrivate.h"
21 #include "mozilla/dom/WorkerRunnable.h"
22 #include "mozilla/ipc/CrossProcessSemaphore.h"
23 #include "mozilla/gfx/2D.h"
24 #include "mozilla/gfx/DataSurfaceHelpers.h"
25 #include "mozilla/gfx/Logging.h"
26 #include "mozilla/gfx/PathHelpers.h"
27 #include "mozilla/gfx/Swizzle.h"
28 #include "mozilla/gfx/gfxVars.h"
29 #include "mozilla/image/nsBMPEncoder.h"
30 #include "mozilla/image/nsICOEncoder.h"
31 #include "mozilla/image/nsJPEGEncoder.h"
32 #include "mozilla/image/nsPNGEncoder.h"
33 #include "mozilla/layers/SynchronousTask.h"
34 #include "mozilla/Maybe.h"
35 #include "mozilla/Preferences.h"
36 #include "mozilla/ProfilerLabels.h"
37 #include "mozilla/RefPtr.h"
38 #include "mozilla/ServoStyleConsts.h"
39 #include "mozilla/StaticPrefs_gfx.h"
40 #include "mozilla/StaticPrefs_layout.h"
41 #include "mozilla/UniquePtrExtensions.h"
42 #include "mozilla/Unused.h"
43 #include "mozilla/Vector.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 "ImageContainer.h"
55 #include "ImageRegion.h"
56 #include "gfx2DGlue.h"
59 # include "gfxWindowsPlatform.h"
62 using namespace mozilla
;
63 using namespace mozilla::image
;
64 using namespace mozilla::layers
;
65 using namespace mozilla::gfx
;
68 #include "mozilla/Compression.h"
70 using namespace mozilla::Compression
;
74 * Dump a raw image to the default log. This function is exported
75 * from libxul, so it can be called from any library in addition to
76 * (of course) from a debugger.
78 * Note: this helper currently assumes that all 2-bytepp images are
79 * r5g6b5, and that all 4-bytepp images are r8g8b8a8.
82 void mozilla_dump_image(void* bytes
, int width
, int height
, int bytepp
,
84 if (0 == strideBytes
) {
85 strideBytes
= width
* bytepp
;
88 // TODO more flexible; parse string?
91 format
= SurfaceFormat::R5G6B5_UINT16
;
95 format
= SurfaceFormat::R8G8B8A8
;
99 RefPtr
<DataSourceSurface
> surf
= Factory::CreateWrappingDataSourceSurface(
100 (uint8_t*)bytes
, strideBytes
, IntSize(width
, height
), format
);
101 gfxUtils::DumpAsDataURI(surf
);
105 static bool MapSrcDest(DataSourceSurface
* srcSurf
, DataSourceSurface
* destSurf
,
106 DataSourceSurface::MappedSurface
* out_srcMap
,
107 DataSourceSurface::MappedSurface
* out_destMap
) {
108 MOZ_ASSERT(srcSurf
&& destSurf
);
109 MOZ_ASSERT(out_srcMap
&& out_destMap
);
111 if (srcSurf
->GetSize() != destSurf
->GetSize()) {
112 MOZ_ASSERT(false, "Width and height must match.");
116 if (srcSurf
== destSurf
) {
117 DataSourceSurface::MappedSurface map
;
118 if (!srcSurf
->Map(DataSourceSurface::MapType::READ_WRITE
, &map
)) {
119 NS_WARNING("Couldn't Map srcSurf/destSurf.");
128 // Map src for reading.
129 DataSourceSurface::MappedSurface srcMap
;
130 if (!srcSurf
->Map(DataSourceSurface::MapType::READ
, &srcMap
)) {
131 NS_WARNING("Couldn't Map srcSurf.");
135 // Map dest for writing.
136 DataSourceSurface::MappedSurface destMap
;
137 if (!destSurf
->Map(DataSourceSurface::MapType::WRITE
, &destMap
)) {
138 NS_WARNING("Couldn't Map aDest.");
143 *out_srcMap
= srcMap
;
144 *out_destMap
= destMap
;
148 static void UnmapSrcDest(DataSourceSurface
* srcSurf
,
149 DataSourceSurface
* destSurf
) {
150 if (srcSurf
== destSurf
) {
158 bool gfxUtils::PremultiplyDataSurface(DataSourceSurface
* srcSurf
,
159 DataSourceSurface
* destSurf
) {
160 MOZ_ASSERT(srcSurf
&& destSurf
);
162 DataSourceSurface::MappedSurface srcMap
;
163 DataSourceSurface::MappedSurface destMap
;
164 if (!MapSrcDest(srcSurf
, destSurf
, &srcMap
, &destMap
)) return false;
166 PremultiplyData(srcMap
.mData
, srcMap
.mStride
, srcSurf
->GetFormat(),
167 destMap
.mData
, destMap
.mStride
, destSurf
->GetFormat(),
170 UnmapSrcDest(srcSurf
, destSurf
);
174 bool gfxUtils::UnpremultiplyDataSurface(DataSourceSurface
* srcSurf
,
175 DataSourceSurface
* destSurf
) {
176 MOZ_ASSERT(srcSurf
&& destSurf
);
178 DataSourceSurface::MappedSurface srcMap
;
179 DataSourceSurface::MappedSurface destMap
;
180 if (!MapSrcDest(srcSurf
, destSurf
, &srcMap
, &destMap
)) return false;
182 UnpremultiplyData(srcMap
.mData
, srcMap
.mStride
, srcSurf
->GetFormat(),
183 destMap
.mData
, destMap
.mStride
, destSurf
->GetFormat(),
186 UnmapSrcDest(srcSurf
, destSurf
);
190 static bool MapSrcAndCreateMappedDest(
191 DataSourceSurface
* srcSurf
, RefPtr
<DataSourceSurface
>* out_destSurf
,
192 DataSourceSurface::MappedSurface
* out_srcMap
,
193 DataSourceSurface::MappedSurface
* out_destMap
) {
195 MOZ_ASSERT(out_destSurf
&& out_srcMap
&& out_destMap
);
197 // Ok, map source for reading.
198 DataSourceSurface::MappedSurface srcMap
;
199 if (!srcSurf
->Map(DataSourceSurface::MapType::READ
, &srcMap
)) {
200 MOZ_ASSERT(false, "Couldn't Map srcSurf.");
204 // Make our dest surface based on the src.
205 RefPtr
<DataSourceSurface
> destSurf
=
206 Factory::CreateDataSourceSurfaceWithStride(
207 srcSurf
->GetSize(), srcSurf
->GetFormat(), srcMap
.mStride
);
208 if (NS_WARN_IF(!destSurf
)) {
212 DataSourceSurface::MappedSurface destMap
;
213 if (!destSurf
->Map(DataSourceSurface::MapType::WRITE
, &destMap
)) {
214 MOZ_ASSERT(false, "Couldn't Map destSurf.");
219 *out_destSurf
= destSurf
;
220 *out_srcMap
= srcMap
;
221 *out_destMap
= destMap
;
225 already_AddRefed
<DataSourceSurface
> gfxUtils::CreatePremultipliedDataSurface(
226 DataSourceSurface
* srcSurf
) {
227 RefPtr
<DataSourceSurface
> destSurf
;
228 DataSourceSurface::MappedSurface srcMap
;
229 DataSourceSurface::MappedSurface destMap
;
230 if (!MapSrcAndCreateMappedDest(srcSurf
, &destSurf
, &srcMap
, &destMap
)) {
231 MOZ_ASSERT(false, "MapSrcAndCreateMappedDest failed.");
232 RefPtr
<DataSourceSurface
> surface(srcSurf
);
233 return surface
.forget();
236 PremultiplyData(srcMap
.mData
, srcMap
.mStride
, srcSurf
->GetFormat(),
237 destMap
.mData
, destMap
.mStride
, destSurf
->GetFormat(),
240 UnmapSrcDest(srcSurf
, destSurf
);
241 return destSurf
.forget();
244 already_AddRefed
<DataSourceSurface
> gfxUtils::CreateUnpremultipliedDataSurface(
245 DataSourceSurface
* srcSurf
) {
246 RefPtr
<DataSourceSurface
> destSurf
;
247 DataSourceSurface::MappedSurface srcMap
;
248 DataSourceSurface::MappedSurface destMap
;
249 if (!MapSrcAndCreateMappedDest(srcSurf
, &destSurf
, &srcMap
, &destMap
)) {
250 MOZ_ASSERT(false, "MapSrcAndCreateMappedDest failed.");
251 RefPtr
<DataSourceSurface
> surface(srcSurf
);
252 return surface
.forget();
255 UnpremultiplyData(srcMap
.mData
, srcMap
.mStride
, srcSurf
->GetFormat(),
256 destMap
.mData
, destMap
.mStride
, destSurf
->GetFormat(),
259 UnmapSrcDest(srcSurf
, destSurf
);
260 return destSurf
.forget();
263 void gfxUtils::ConvertBGRAtoRGBA(uint8_t* aData
, uint32_t aLength
) {
264 MOZ_ASSERT((aLength
% 4) == 0, "Loop below will pass srcEnd!");
265 SwizzleData(aData
, aLength
, SurfaceFormat::B8G8R8A8
, aData
, aLength
,
266 SurfaceFormat::R8G8B8A8
, IntSize(aLength
/ 4, 1));
269 #if !defined(MOZ_GFX_OPTIMIZE_MOBILE)
271 * This returns the fastest operator to use for solid surfaces which have no
272 * alpha channel or their alpha channel is uniformly opaque.
273 * This differs per render mode.
275 static CompositionOp
OptimalFillOp() {
277 if (gfxWindowsPlatform::GetPlatform()->IsDirect2DBackend()) {
278 // D2D -really- hates operator source.
279 return CompositionOp::OP_OVER
;
282 return CompositionOp::OP_SOURCE
;
285 // EXTEND_PAD won't help us here; we have to create a temporary surface to hold
286 // the subimage of pixels we're allowed to sample.
287 static already_AddRefed
<gfxDrawable
> CreateSamplingRestrictedDrawable(
288 gfxDrawable
* aDrawable
, gfxContext
* aContext
, const ImageRegion
& aRegion
,
289 const SurfaceFormat aFormat
, bool aUseOptimalFillOp
) {
290 AUTO_PROFILER_LABEL("CreateSamplingRestrictedDrawable", GRAPHICS
);
292 DrawTarget
* destDrawTarget
= aContext
->GetDrawTarget();
293 // We've been not using CreateSamplingRestrictedDrawable in a bunch of places
294 // for a while. Let's disable it everywhere and confirm that it's ok to get
296 if (destDrawTarget
->GetBackendType() == BackendType::DIRECT2D1_1
|| (true)) {
300 gfxRect clipExtents
= aContext
->GetClipExtents();
302 // Inflate by one pixel because bilinear filtering will sample at most
303 // one pixel beyond the computed image pixel coordinate.
304 clipExtents
.Inflate(1.0);
306 gfxRect needed
= aRegion
.IntersectAndRestrict(clipExtents
);
309 // if 'needed' is empty, nothing will be drawn since aFill
310 // must be entirely outside the clip region, so it doesn't
311 // matter what we do here, but we should avoid trying to
312 // create a zero-size surface.
313 if (needed
.IsEmpty()) return nullptr;
315 IntSize
size(int32_t(needed
.Width()), int32_t(needed
.Height()));
317 RefPtr
<DrawTarget
> target
=
318 gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(size
,
320 if (!target
|| !target
->IsValid()) {
324 RefPtr
<gfxContext
> tmpCtx
= gfxContext::CreateOrNull(target
);
325 MOZ_ASSERT(tmpCtx
); // already checked the target above
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 RefPtr
<gfxContext
> tmpCtx
= gfxContext::CreateOrNull(scaledDT
);
478 MOZ_ASSERT(tmpCtx
); // already checked the target above
480 scaledDT
->SetTransform(scaleMatrix
);
481 gfxRect
gfxImageRect(aImageRect
.x
, aImageRect
.y
, aImageRect
.width
,
484 // Since this is just the scaled image, we don't want to repeat anything yet.
485 aDrawable
->Draw(tmpCtx
, gfxImageRect
, ExtendMode::CLAMP
, aSamplingFilter
, 1.0,
488 RefPtr
<SourceSurface
> scaledImage
= scaledDT
->Snapshot();
491 gfxContextMatrixAutoSaveRestore
autoSR(aContext
);
492 Matrix withoutScale
= aContext
->CurrentMatrix();
493 DrawTarget
* destDrawTarget
= aContext
->GetDrawTarget();
495 // The translation still is in scaled units
496 withoutScale
.PreScale(1.0f
/ scaleFactor
.xScale
, 1.0f
/ scaleFactor
.yScale
);
497 aContext
->SetMatrix(withoutScale
);
499 DrawOptions
drawOptions(aOpacity
, aContext
->CurrentOp(),
500 aContext
->CurrentAntialiasMode());
502 SurfacePattern
scaledImagePattern(scaledImage
, aExtendMode
, Matrix(),
504 destDrawTarget
->FillRect(scaledNeededRect
, scaledImagePattern
, drawOptions
);
508 #endif // MOZ_WIDGET_COCOA
511 void gfxUtils::DrawPixelSnapped(gfxContext
* aContext
, gfxDrawable
* aDrawable
,
512 const gfxSize
& aImageSize
,
513 const ImageRegion
& aRegion
,
514 const SurfaceFormat aFormat
,
515 SamplingFilter aSamplingFilter
,
516 uint32_t aImageFlags
, gfxFloat aOpacity
,
517 bool aUseOptimalFillOp
) {
518 AUTO_PROFILER_LABEL("gfxUtils::DrawPixelSnapped", GRAPHICS
);
520 gfxRect
imageRect(gfxPoint(0, 0), aImageSize
);
521 gfxRect
region(aRegion
.Rect());
522 ExtendMode extendMode
= aRegion
.GetExtendMode();
524 RefPtr
<gfxDrawable
> drawable
= aDrawable
;
526 aSamplingFilter
= ReduceResamplingFilter(aSamplingFilter
, imageRect
.Width(),
527 imageRect
.Height(), region
.Width(),
530 // OK now, the hard part left is to account for the subimage sampling
531 // restriction. If all the transforms involved are just integer
532 // translations, then we assume no resampling will occur so there's
534 // XXX if only we had source-clipping in cairo!
536 if (aContext
->CurrentMatrix().HasNonIntegerTranslation()) {
537 if ((extendMode
!= ExtendMode::CLAMP
) ||
538 !aRegion
.RestrictionContains(imageRect
)) {
539 if (drawable
->DrawWithSamplingRect(
540 aContext
->GetDrawTarget(), aContext
->CurrentOp(),
541 aContext
->CurrentAntialiasMode(), aRegion
.Rect(),
542 aRegion
.Restriction(), extendMode
, aSamplingFilter
, aOpacity
)) {
546 #ifdef MOZ_WIDGET_COCOA
547 if (PrescaleAndTileDrawable(aDrawable
, aContext
, aRegion
,
548 ToRect(imageRect
), aSamplingFilter
, aFormat
,
549 aOpacity
, extendMode
)) {
554 // On Mobile, we don't ever want to do this; it has the potential for
555 // allocating very large temporary surfaces, especially since we'll
556 // do full-page snapshots often (see bug 749426).
557 #if !defined(MOZ_GFX_OPTIMIZE_MOBILE)
558 RefPtr
<gfxDrawable
> restrictedDrawable
= CreateSamplingRestrictedDrawable(
559 aDrawable
, aContext
, aRegion
, aFormat
, aUseOptimalFillOp
);
560 if (restrictedDrawable
) {
561 drawable
.swap(restrictedDrawable
);
563 // We no longer need to tile: Either we never needed to, or we already
564 // filled a surface with the tiled pattern; this surface can now be
565 // drawn without tiling.
566 extendMode
= ExtendMode::CLAMP
;
572 drawable
->Draw(aContext
, aRegion
.Rect(), extendMode
, aSamplingFilter
,
573 aOpacity
, gfxMatrix());
577 int gfxUtils::ImageFormatToDepth(gfxImageFormat aFormat
) {
579 case SurfaceFormat::A8R8G8B8_UINT32
:
581 case SurfaceFormat::X8R8G8B8_UINT32
:
583 case SurfaceFormat::R5G6B5_UINT16
:
592 void gfxUtils::ClipToRegion(gfxContext
* aContext
, const nsIntRegion
& aRegion
) {
594 for (auto iter
= aRegion
.RectIter(); !iter
.Done(); iter
.Next()) {
595 const IntRect
& r
= iter
.Get();
596 aContext
->Rectangle(gfxRect(r
.X(), r
.Y(), r
.Width(), r
.Height()));
602 void gfxUtils::ClipToRegion(DrawTarget
* aTarget
, const nsIntRegion
& aRegion
) {
603 uint32_t numRects
= aRegion
.GetNumRects();
604 // If there is only one rect, then the region bounds are equivalent to the
605 // contents. So just use push a single clip rect with the bounds.
607 aTarget
->PushClipRect(Rect(aRegion
.GetBounds()));
611 // Check if the target's transform will preserve axis-alignment and
612 // pixel-alignment for each rect. For now, just handle the common case
613 // of integer translations.
614 Matrix transform
= aTarget
->GetTransform();
615 if (transform
.IsIntegerTranslation()) {
616 IntPoint translation
= RoundedToInt(transform
.GetTranslation());
617 AutoTArray
<IntRect
, 16> rects
;
618 rects
.SetLength(numRects
);
620 // Build the list of transformed rects by adding in the translation.
621 for (auto iter
= aRegion
.RectIter(); !iter
.Done(); iter
.Next()) {
622 IntRect rect
= iter
.Get();
623 rect
.MoveBy(translation
);
626 aTarget
->PushDeviceSpaceClipRects(rects
.Elements(), rects
.Length());
628 // The transform does not produce axis-aligned rects or a rect was not
629 // pixel-aligned. So just build a path with all the rects and clip to it
631 RefPtr
<PathBuilder
> pathBuilder
= aTarget
->CreatePathBuilder();
632 for (auto iter
= aRegion
.RectIter(); !iter
.Done(); iter
.Next()) {
633 AppendRectToPath(pathBuilder
, Rect(iter
.Get()));
635 RefPtr
<Path
> path
= pathBuilder
->Finish();
636 aTarget
->PushClip(path
);
641 float gfxUtils::ClampToScaleFactor(float aVal
, bool aRoundDown
) {
642 // Arbitary scale factor limitation. We can increase this
643 // for better scaling performance at the cost of worse
645 static const float kScaleResolution
= 2;
647 // Negative scaling is just a flip and irrelevant to
648 // our resolution calculation.
653 bool inverse
= false;
659 float power
= logf(aVal
) / logf(kScaleResolution
);
661 // If power is within 1e-5 of an integer, round to nearest to
662 // prevent floating point errors, otherwise round up to the
663 // next integer value.
664 if (fabs(power
- NS_round(power
)) < 1e-5) {
665 power
= NS_round(power
);
666 // Use floor when we are either inverted or rounding down, but
668 } else if (inverse
!= aRoundDown
) {
669 power
= floor(power
);
670 // Otherwise, ceil when we are not inverted and not rounding
671 // down, or we are inverted and rounding down.
676 float scale
= powf(kScaleResolution
, power
);
685 gfxMatrix
gfxUtils::TransformRectToRect(const gfxRect
& aFrom
,
686 const gfxPoint
& aToTopLeft
,
687 const gfxPoint
& aToTopRight
,
688 const gfxPoint
& aToBottomRight
) {
690 if (aToTopRight
.y
== aToTopLeft
.y
&& aToTopRight
.x
== aToBottomRight
.x
) {
691 // Not a rotation, so xy and yx are zero
693 m
._11
= (aToBottomRight
.x
- aToTopLeft
.x
) / aFrom
.Width();
694 m
._22
= (aToBottomRight
.y
- aToTopLeft
.y
) / aFrom
.Height();
695 m
._31
= aToTopLeft
.x
- m
._11
* aFrom
.X();
696 m
._32
= aToTopLeft
.y
- m
._22
* aFrom
.Y();
699 aToTopRight
.y
== aToBottomRight
.y
&& aToTopRight
.x
== aToTopLeft
.x
,
700 "Destination rectangle not axis-aligned");
702 m
._21
= (aToBottomRight
.x
- aToTopLeft
.x
) / aFrom
.Height();
703 m
._12
= (aToBottomRight
.y
- aToTopLeft
.y
) / aFrom
.Width();
704 m
._31
= aToTopLeft
.x
- m
._21
* aFrom
.Y();
705 m
._32
= aToTopLeft
.y
- m
._12
* aFrom
.X();
710 Matrix
gfxUtils::TransformRectToRect(const gfxRect
& aFrom
,
711 const IntPoint
& aToTopLeft
,
712 const IntPoint
& aToTopRight
,
713 const IntPoint
& aToBottomRight
) {
715 if (aToTopRight
.y
== aToTopLeft
.y
&& aToTopRight
.x
== aToBottomRight
.x
) {
716 // Not a rotation, so xy and yx are zero
718 m
._11
= (aToBottomRight
.x
- aToTopLeft
.x
) / aFrom
.Width();
719 m
._22
= (aToBottomRight
.y
- aToTopLeft
.y
) / aFrom
.Height();
720 m
._31
= aToTopLeft
.x
- m
._11
* aFrom
.X();
721 m
._32
= aToTopLeft
.y
- m
._22
* aFrom
.Y();
724 aToTopRight
.y
== aToBottomRight
.y
&& aToTopRight
.x
== aToTopLeft
.x
,
725 "Destination rectangle not axis-aligned");
727 m
._21
= (aToBottomRight
.x
- aToTopLeft
.x
) / aFrom
.Height();
728 m
._12
= (aToBottomRight
.y
- aToTopLeft
.y
) / aFrom
.Width();
729 m
._31
= aToTopLeft
.x
- m
._21
* aFrom
.Y();
730 m
._32
= aToTopLeft
.y
- m
._12
* aFrom
.X();
735 /* This function is sort of shitty. We truncate doubles
736 * to ints then convert those ints back to doubles to make sure that
737 * they equal the doubles that we got in. */
738 bool gfxUtils::GfxRectToIntRect(const gfxRect
& aIn
, IntRect
* aOut
) {
739 *aOut
= IntRect(int32_t(aIn
.X()), int32_t(aIn
.Y()), int32_t(aIn
.Width()),
740 int32_t(aIn
.Height()));
741 return gfxRect(aOut
->X(), aOut
->Y(), aOut
->Width(), aOut
->Height())
745 /* Clamp r to CAIRO_COORD_MIN .. CAIRO_COORD_MAX
746 * these are to be device coordinates.
748 * Cairo is currently using 24.8 fixed point,
749 * so -2^24 .. 2^24-1 is our valid
752 void gfxUtils::ConditionRect(gfxRect
& aRect
) {
753 #define CAIRO_COORD_MAX (16777215.0)
754 #define CAIRO_COORD_MIN (-16777216.0)
755 // if either x or y is way out of bounds;
756 // note that we don't handle negative w/h here
757 if (aRect
.X() > CAIRO_COORD_MAX
) {
758 aRect
.SetRectX(CAIRO_COORD_MAX
, 0.0);
761 if (aRect
.Y() > CAIRO_COORD_MAX
) {
762 aRect
.SetRectY(CAIRO_COORD_MAX
, 0.0);
765 if (aRect
.X() < CAIRO_COORD_MIN
) {
766 aRect
.SetWidth(aRect
.XMost() - CAIRO_COORD_MIN
);
767 if (aRect
.Width() < 0.0) {
770 aRect
.MoveToX(CAIRO_COORD_MIN
);
773 if (aRect
.Y() < CAIRO_COORD_MIN
) {
774 aRect
.SetHeight(aRect
.YMost() - CAIRO_COORD_MIN
);
775 if (aRect
.Height() < 0.0) {
776 aRect
.SetHeight(0.0);
778 aRect
.MoveToY(CAIRO_COORD_MIN
);
781 if (aRect
.XMost() > CAIRO_COORD_MAX
) {
782 aRect
.SetRightEdge(CAIRO_COORD_MAX
);
785 if (aRect
.YMost() > CAIRO_COORD_MAX
) {
786 aRect
.SetBottomEdge(CAIRO_COORD_MAX
);
788 #undef CAIRO_COORD_MAX
789 #undef CAIRO_COORD_MIN
793 gfxQuad
gfxUtils::TransformToQuad(const gfxRect
& aRect
,
794 const mozilla::gfx::Matrix4x4
& aMatrix
) {
797 points
[0] = aMatrix
.TransformPoint(aRect
.TopLeft());
798 points
[1] = aMatrix
.TransformPoint(aRect
.TopRight());
799 points
[2] = aMatrix
.TransformPoint(aRect
.BottomRight());
800 points
[3] = aMatrix
.TransformPoint(aRect
.BottomLeft());
802 // Could this ever result in lines that intersect? I don't think so.
803 return gfxQuad(points
[0], points
[1], points
[2], points
[3]);
806 Matrix4x4
gfxUtils::SnapTransformTranslation(const Matrix4x4
& aTransform
,
807 Matrix
* aResidualTransform
) {
808 if (aResidualTransform
) {
809 *aResidualTransform
= Matrix();
813 if (aTransform
.CanDraw2D(&matrix2D
) && !matrix2D
.HasNonTranslation() &&
814 matrix2D
.HasNonIntegerTranslation()) {
815 return Matrix4x4::From2D(
816 SnapTransformTranslation(matrix2D
, aResidualTransform
));
819 return SnapTransformTranslation3D(aTransform
, aResidualTransform
);
822 Matrix
gfxUtils::SnapTransformTranslation(const Matrix
& aTransform
,
823 Matrix
* aResidualTransform
) {
824 if (aResidualTransform
) {
825 *aResidualTransform
= Matrix();
828 if (!aTransform
.HasNonTranslation() &&
829 aTransform
.HasNonIntegerTranslation()) {
830 auto snappedTranslation
= IntPoint::Round(aTransform
.GetTranslation());
831 Matrix snappedMatrix
=
832 Matrix::Translation(snappedTranslation
.x
, snappedTranslation
.y
);
833 if (aResidualTransform
) {
834 // set aResidualTransform so that aResidual * snappedMatrix == matrix2D.
835 // (I.e., appying snappedMatrix after aResidualTransform gives the
837 *aResidualTransform
=
838 Matrix::Translation(aTransform
._31
- snappedTranslation
.x
,
839 aTransform
._32
- snappedTranslation
.y
);
841 return snappedMatrix
;
847 Matrix4x4
gfxUtils::SnapTransformTranslation3D(const Matrix4x4
& aTransform
,
848 Matrix
* aResidualTransform
) {
849 if (aTransform
.IsSingular() || aTransform
.HasPerspectiveComponent() ||
850 aTransform
.HasNonTranslation() ||
851 !aTransform
.HasNonIntegerTranslation()) {
852 // For a singular transform, there is no reversed matrix, so we
854 // For a perspective transform, the content is transformed in
855 // non-linear, so we don't snap it too.
859 // Snap for 3D Transforms
861 Point3D transformedOrigin
= aTransform
.TransformPoint(Point3D());
863 // Compute the transformed snap by rounding the values of
864 // transformed origin.
865 auto transformedSnapXY
=
866 IntPoint::Round(transformedOrigin
.x
, transformedOrigin
.y
);
867 Matrix4x4 inverse
= aTransform
;
869 // see Matrix4x4::ProjectPoint()
870 Float transformedSnapZ
=
872 : (-(transformedSnapXY
.x
* inverse
._13
+
873 transformedSnapXY
.y
* inverse
._23
+ inverse
._43
) /
875 Point3D transformedSnap
=
876 Point3D(transformedSnapXY
.x
, transformedSnapXY
.y
, transformedSnapZ
);
877 if (transformedOrigin
== transformedSnap
) {
881 // Compute the snap from the transformed snap.
882 Point3D snap
= inverse
.TransformPoint(transformedSnap
);
883 if (snap
.z
> 0.001 || snap
.z
< -0.001) {
884 // Allow some level of accumulated computation error.
885 MOZ_ASSERT(inverse
._33
== 0.0);
889 // The difference between the origin and snap is the residual transform.
890 if (aResidualTransform
) {
891 // The residual transform is to translate the snap to the origin
892 // of the content buffer.
893 *aResidualTransform
= Matrix::Translation(-snap
.x
, -snap
.y
);
896 // Translate transformed origin to transformed snap since the
897 // residual transform would trnslate the snap to the origin.
898 Point3D transformedShift
= transformedSnap
- transformedOrigin
;
899 Matrix4x4 result
= aTransform
;
900 result
.PostTranslate(transformedShift
.x
, transformedShift
.y
,
903 // For non-2d transform, residual translation could be more than
904 // 0.5 pixels for every axis.
909 Matrix4x4
gfxUtils::SnapTransform(const Matrix4x4
& aTransform
,
910 const gfxRect
& aSnapRect
,
911 Matrix
* aResidualTransform
) {
912 if (aResidualTransform
) {
913 *aResidualTransform
= Matrix();
917 if (aTransform
.Is2D(&matrix2D
)) {
918 return Matrix4x4::From2D(
919 SnapTransform(matrix2D
, aSnapRect
, aResidualTransform
));
924 Matrix
gfxUtils::SnapTransform(const Matrix
& aTransform
,
925 const gfxRect
& aSnapRect
,
926 Matrix
* aResidualTransform
) {
927 if (aResidualTransform
) {
928 *aResidualTransform
= Matrix();
931 if (gfxSize(1.0, 1.0) <= aSnapRect
.Size() &&
932 aTransform
.PreservesAxisAlignedRectangles()) {
933 auto transformedTopLeft
= IntPoint::Round(
934 aTransform
.TransformPoint(ToPoint(aSnapRect
.TopLeft())));
935 auto transformedTopRight
= IntPoint::Round(
936 aTransform
.TransformPoint(ToPoint(aSnapRect
.TopRight())));
937 auto transformedBottomRight
= IntPoint::Round(
938 aTransform
.TransformPoint(ToPoint(aSnapRect
.BottomRight())));
940 Matrix snappedMatrix
= gfxUtils::TransformRectToRect(
941 aSnapRect
, transformedTopLeft
, transformedTopRight
,
942 transformedBottomRight
);
944 if (aResidualTransform
&& !snappedMatrix
.IsSingular()) {
945 // set aResidualTransform so that aResidual * snappedMatrix == matrix2D.
946 // (i.e., appying snappedMatrix after aResidualTransform gives the
948 Matrix snappedMatrixInverse
= snappedMatrix
;
949 snappedMatrixInverse
.Invert();
950 *aResidualTransform
= aTransform
* snappedMatrixInverse
;
952 return snappedMatrix
;
958 void gfxUtils::ClearThebesSurface(gfxASurface
* aSurface
) {
959 if (aSurface
->CairoStatus()) {
962 cairo_surface_t
* surf
= aSurface
->CairoSurface();
963 if (cairo_surface_status(surf
)) {
966 cairo_t
* ctx
= cairo_create(surf
);
967 cairo_set_source_rgba(ctx
, 0.0, 0.0, 0.0, 0.0);
968 cairo_set_operator(ctx
, CAIRO_OPERATOR_SOURCE
);
969 IntRect
bounds(nsIntPoint(0, 0), aSurface
->GetSize());
970 cairo_rectangle(ctx
, bounds
.X(), bounds
.Y(), bounds
.Width(), bounds
.Height());
976 already_AddRefed
<DataSourceSurface
>
977 gfxUtils::CopySurfaceToDataSourceSurfaceWithFormat(SourceSurface
* aSurface
,
978 SurfaceFormat aFormat
) {
979 MOZ_ASSERT(aFormat
!= aSurface
->GetFormat(),
980 "Unnecessary - and very expersive - surface format conversion");
982 Rect
bounds(0, 0, aSurface
->GetSize().width
, aSurface
->GetSize().height
);
984 if (!aSurface
->IsDataSourceSurface()) {
985 // If the surface is NOT of type DATA then its data is not mapped into main
986 // memory. Format conversion is probably faster on the GPU, and by doing it
987 // there we can avoid any expensive uploads/readbacks except for (possibly)
988 // a single readback due to the unavoidable GetDataSurface() call. Using
989 // CreateOffscreenContentDrawTarget ensures the conversion happens on the
991 RefPtr
<DrawTarget
> dt
=
992 gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
993 aSurface
->GetSize(), aFormat
);
995 gfxWarning() << "gfxUtils::CopySurfaceToDataSourceSurfaceWithFormat "
996 "failed in CreateOffscreenContentDrawTarget";
1000 // Using DrawSurface() here rather than CopySurface() because CopySurface
1001 // is optimized for memcpy and therefore isn't good for format conversion.
1002 // Using OP_OVER since in our case it's equivalent to OP_SOURCE and
1003 // generally more optimized.
1004 dt
->DrawSurface(aSurface
, bounds
, bounds
, DrawSurfaceOptions(),
1005 DrawOptions(1.0f
, CompositionOp::OP_OVER
));
1006 RefPtr
<SourceSurface
> surface
= dt
->Snapshot();
1007 return surface
->GetDataSurface();
1010 // If the surface IS of type DATA then it may or may not be in main memory
1011 // depending on whether or not it has been mapped yet. We have no way of
1012 // knowing, so we can't be sure if it's best to create a data wrapping
1013 // DrawTarget for the conversion or an offscreen content DrawTarget. We could
1014 // guess it's not mapped and create an offscreen content DrawTarget, but if
1015 // it is then we'll end up uploading the surface data, and most likely the
1016 // caller is going to be accessing the resulting surface data, resulting in a
1017 // readback (both very expensive operations). Alternatively we could guess
1018 // the data is mapped and create a data wrapping DrawTarget and, if the
1019 // surface is not in main memory, then we will incure a readback. The latter
1020 // of these two "wrong choices" is the least costly (a readback, vs an
1021 // upload and a readback), and more than likely the DATA surface that we've
1022 // been passed actually IS in main memory anyway. For these reasons it's most
1023 // likely best to create a data wrapping DrawTarget here to do the format
1025 RefPtr
<DataSourceSurface
> dataSurface
=
1026 Factory::CreateDataSourceSurface(aSurface
->GetSize(), aFormat
);
1027 DataSourceSurface::MappedSurface map
;
1029 !dataSurface
->Map(DataSourceSurface::MapType::READ_WRITE
, &map
)) {
1032 RefPtr
<DrawTarget
> dt
= Factory::CreateDrawTargetForData(
1033 BackendType::CAIRO
, map
.mData
, dataSurface
->GetSize(), map
.mStride
,
1036 dataSurface
->Unmap();
1039 // Using DrawSurface() here rather than CopySurface() because CopySurface
1040 // is optimized for memcpy and therefore isn't good for format conversion.
1041 // Using OP_OVER since in our case it's equivalent to OP_SOURCE and
1042 // generally more optimized.
1043 dt
->DrawSurface(aSurface
, bounds
, bounds
, DrawSurfaceOptions(),
1044 DrawOptions(1.0f
, CompositionOp::OP_OVER
));
1045 dataSurface
->Unmap();
1046 return dataSurface
.forget();
1049 const uint32_t gfxUtils::sNumFrameColors
= 8;
1052 const gfx::DeviceColor
& gfxUtils::GetColorForFrameNumber(
1053 uint64_t aFrameNumber
) {
1054 static bool initialized
= false;
1055 static gfx::DeviceColor colors
[sNumFrameColors
];
1058 // This isn't truly device color, but it is just for debug.
1060 colors
[i
++] = gfx::DeviceColor::FromABGR(0xffff0000);
1061 colors
[i
++] = gfx::DeviceColor::FromABGR(0xffcc00ff);
1062 colors
[i
++] = gfx::DeviceColor::FromABGR(0xff0066cc);
1063 colors
[i
++] = gfx::DeviceColor::FromABGR(0xff00ff00);
1064 colors
[i
++] = gfx::DeviceColor::FromABGR(0xff33ffff);
1065 colors
[i
++] = gfx::DeviceColor::FromABGR(0xffff0099);
1066 colors
[i
++] = gfx::DeviceColor::FromABGR(0xff0000ff);
1067 colors
[i
++] = gfx::DeviceColor::FromABGR(0xff999999);
1068 MOZ_ASSERT(i
== sNumFrameColors
);
1072 return colors
[aFrameNumber
% sNumFrameColors
];
1076 nsresult
gfxUtils::EncodeSourceSurface(SourceSurface
* aSurface
,
1077 const ImageType aImageType
,
1078 const nsAString
& aOutputOptions
,
1079 BinaryOrData aBinaryOrData
, FILE* aFile
,
1080 nsACString
* aStrOut
) {
1081 MOZ_ASSERT(aBinaryOrData
== gfxUtils::eDataURIEncode
|| aFile
|| aStrOut
,
1082 "Copying binary encoding to clipboard not currently supported");
1084 const IntSize size
= aSurface
->GetSize();
1085 if (size
.IsEmpty()) {
1086 return NS_ERROR_INVALID_ARG
;
1088 const Size
floatSize(size
.width
, size
.height
);
1090 RefPtr
<DataSourceSurface
> dataSurface
;
1091 if (aSurface
->GetFormat() != SurfaceFormat::B8G8R8A8
) {
1092 // FIXME bug 995807 (B8G8R8X8), bug 831898 (R5G6B5)
1093 dataSurface
= gfxUtils::CopySurfaceToDataSourceSurfaceWithFormat(
1094 aSurface
, SurfaceFormat::B8G8R8A8
);
1096 dataSurface
= aSurface
->GetDataSurface();
1099 return NS_ERROR_FAILURE
;
1102 DataSourceSurface::MappedSurface map
;
1103 if (!dataSurface
->Map(DataSourceSurface::MapType::READ
, &map
)) {
1104 return NS_ERROR_FAILURE
;
1107 RefPtr
<imgIEncoder
> encoder
= nullptr;
1109 switch (aImageType
) {
1110 case ImageType::BMP
:
1111 encoder
= MakeRefPtr
<nsBMPEncoder
>();
1114 case ImageType::ICO
:
1115 encoder
= MakeRefPtr
<nsICOEncoder
>();
1118 case ImageType::JPEG
:
1119 encoder
= MakeRefPtr
<nsJPEGEncoder
>();
1122 case ImageType::PNG
:
1123 encoder
= MakeRefPtr
<nsPNGEncoder
>();
1130 MOZ_RELEASE_ASSERT(encoder
!= nullptr);
1132 nsresult rv
= encoder
->InitFromData(
1133 map
.mData
, BufferSizeFromStrideAndHeight(map
.mStride
, size
.height
),
1134 size
.width
, size
.height
, map
.mStride
, imgIEncoder::INPUT_FORMAT_HOSTARGB
,
1136 dataSurface
->Unmap();
1137 NS_ENSURE_SUCCESS(rv
, rv
);
1139 nsCOMPtr
<nsIInputStream
> imgStream(encoder
);
1141 return NS_ERROR_FAILURE
;
1145 rv
= imgStream
->Available(&bufSize64
);
1146 NS_ENSURE_SUCCESS(rv
, rv
);
1148 NS_ENSURE_TRUE(bufSize64
< UINT32_MAX
- 16, NS_ERROR_FAILURE
);
1150 uint32_t bufSize
= (uint32_t)bufSize64
;
1152 // ...leave a little extra room so we can call read again and make sure we
1153 // got everything. 16 bytes for better padding (maybe)
1155 uint32_t imgSize
= 0;
1156 Vector
<char> imgData
;
1157 if (!imgData
.initCapacity(bufSize
)) {
1158 return NS_ERROR_OUT_OF_MEMORY
;
1160 uint32_t numReadThisTime
= 0;
1161 while ((rv
= imgStream
->Read(imgData
.begin() + imgSize
, bufSize
- imgSize
,
1162 &numReadThisTime
)) == NS_OK
&&
1163 numReadThisTime
> 0) {
1164 // Update the length of the vector without overwriting the new data.
1165 if (!imgData
.growByUninitialized(numReadThisTime
)) {
1166 return NS_ERROR_OUT_OF_MEMORY
;
1169 imgSize
+= numReadThisTime
;
1170 if (imgSize
== bufSize
) {
1171 // need a bigger buffer, just double
1173 if (!imgData
.resizeUninitialized(bufSize
)) {
1174 return NS_ERROR_OUT_OF_MEMORY
;
1178 NS_ENSURE_SUCCESS(rv
, rv
);
1179 NS_ENSURE_TRUE(!imgData
.empty(), NS_ERROR_FAILURE
);
1181 if (aBinaryOrData
== gfxUtils::eBinaryEncode
) {
1183 Unused
<< fwrite(imgData
.begin(), 1, imgSize
, aFile
);
1188 nsCString stringBuf
;
1189 nsACString
& dataURI
= aStrOut
? *aStrOut
: stringBuf
;
1190 dataURI
.AppendLiteral("data:");
1192 switch (aImageType
) {
1193 case ImageType::BMP
:
1194 dataURI
.AppendLiteral(IMAGE_BMP
);
1197 case ImageType::ICO
:
1198 dataURI
.AppendLiteral(IMAGE_ICO_MS
);
1200 case ImageType::JPEG
:
1201 dataURI
.AppendLiteral(IMAGE_JPEG
);
1204 case ImageType::PNG
:
1205 dataURI
.AppendLiteral(IMAGE_PNG
);
1212 dataURI
.AppendLiteral(";base64,");
1213 rv
= Base64EncodeAppend(imgData
.begin(), imgSize
, dataURI
);
1214 NS_ENSURE_SUCCESS(rv
, rv
);
1218 if (aFile
== stdout
|| aFile
== stderr
) {
1219 // ADB logcat cuts off long strings so we will break it down
1220 const char* cStr
= dataURI
.BeginReading();
1221 size_t len
= strlen(cStr
);
1223 printf_stderr("IMG: %.140s\n", cStr
);
1224 if (len
<= 140) break;
1230 fprintf(aFile
, "%s", dataURI
.BeginReading());
1231 } else if (!aStrOut
) {
1232 nsCOMPtr
<nsIClipboardHelper
> clipboard(
1233 do_GetService("@mozilla.org/widget/clipboardhelper;1", &rv
));
1235 clipboard
->CopyString(NS_ConvertASCIItoUTF16(dataURI
));
1241 static nsCString
EncodeSourceSurfaceAsPNGURI(SourceSurface
* aSurface
) {
1243 gfxUtils::EncodeSourceSurface(aSurface
, ImageType::PNG
, u
""_ns
,
1244 gfxUtils::eDataURIEncode
, nullptr, &string
);
1248 // https://jdashg.github.io/misc/colors/from-coeffs.html
1249 const float kBT601NarrowYCbCrToRGB_RowMajor
[16] = {
1250 1.16438f
, 0.00000f
, 1.59603f
, -0.87420f
, 1.16438f
, -0.39176f
,
1251 -0.81297f
, 0.53167f
, 1.16438f
, 2.01723f
, 0.00000f
, -1.08563f
,
1252 0.00000f
, 0.00000f
, 0.00000f
, 1.00000f
};
1253 const float kBT709NarrowYCbCrToRGB_RowMajor
[16] = {
1254 1.16438f
, 0.00000f
, 1.79274f
, -0.97295f
, 1.16438f
, -0.21325f
,
1255 -0.53291f
, 0.30148f
, 1.16438f
, 2.11240f
, 0.00000f
, -1.13340f
,
1256 0.00000f
, 0.00000f
, 0.00000f
, 1.00000f
};
1257 const float kBT2020NarrowYCbCrToRGB_RowMajor
[16] = {
1258 1.16438f
, 0.00000f
, 1.67867f
, -0.91569f
, 1.16438f
, -0.18733f
,
1259 -0.65042f
, 0.34746f
, 1.16438f
, 2.14177f
, 0.00000f
, -1.14815f
,
1260 0.00000f
, 0.00000f
, 0.00000f
, 1.00000f
};
1261 const float kIdentityNarrowYCbCrToRGB_RowMajor
[16] = {
1262 0.00000f
, 0.00000f
, 1.00000f
, 0.00000f
, 1.00000f
, 0.00000f
,
1263 0.00000f
, 0.00000f
, 0.00000f
, 1.00000f
, 0.00000f
, 0.00000f
,
1264 0.00000f
, 0.00000f
, 0.00000f
, 1.00000f
};
1266 /* static */ const float* gfxUtils::YuvToRgbMatrix4x3RowMajor(
1267 gfx::YUVColorSpace aYUVColorSpace
) {
1269 { x[0], x[1], x[2], 0.0f, x[4], x[5], x[6], 0.0f, x[8], x[9], x[10], 0.0f }
1271 static const float rec601
[12] = X(kBT601NarrowYCbCrToRGB_RowMajor
);
1272 static const float rec709
[12] = X(kBT709NarrowYCbCrToRGB_RowMajor
);
1273 static const float rec2020
[12] = X(kBT2020NarrowYCbCrToRGB_RowMajor
);
1274 static const float identity
[12] = X(kIdentityNarrowYCbCrToRGB_RowMajor
);
1278 switch (aYUVColorSpace
) {
1279 case gfx::YUVColorSpace::BT601
:
1281 case gfx::YUVColorSpace::BT709
:
1283 case gfx::YUVColorSpace::BT2020
:
1285 case gfx::YUVColorSpace::Identity
:
1288 MOZ_CRASH("Bad YUVColorSpace");
1292 /* static */ const float* gfxUtils::YuvToRgbMatrix3x3ColumnMajor(
1293 gfx::YUVColorSpace aYUVColorSpace
) {
1295 { x[0], x[4], x[8], x[1], x[5], x[9], x[2], x[6], x[10] }
1297 static const float rec601
[9] = X(kBT601NarrowYCbCrToRGB_RowMajor
);
1298 static const float rec709
[9] = X(kBT709NarrowYCbCrToRGB_RowMajor
);
1299 static const float rec2020
[9] = X(kBT2020NarrowYCbCrToRGB_RowMajor
);
1300 static const float identity
[9] = X(kIdentityNarrowYCbCrToRGB_RowMajor
);
1304 switch (aYUVColorSpace
) {
1305 case gfx::YUVColorSpace::BT601
:
1307 case YUVColorSpace::BT709
:
1309 case YUVColorSpace::BT2020
:
1311 case YUVColorSpace::Identity
:
1314 MOZ_CRASH("Bad YUVColorSpace");
1318 /* static */ const float* gfxUtils::YuvToRgbMatrix4x4ColumnMajor(
1319 YUVColorSpace aYUVColorSpace
) {
1322 x[0], x[4], x[8], x[12], x[1], x[5], x[9], x[13], x[2], x[6], x[10], \
1323 x[14], x[3], x[7], x[11], x[15] \
1326 static const float rec601
[16] = X(kBT601NarrowYCbCrToRGB_RowMajor
);
1327 static const float rec709
[16] = X(kBT709NarrowYCbCrToRGB_RowMajor
);
1328 static const float rec2020
[16] = X(kBT2020NarrowYCbCrToRGB_RowMajor
);
1329 static const float identity
[16] = X(kIdentityNarrowYCbCrToRGB_RowMajor
);
1333 switch (aYUVColorSpace
) {
1334 case YUVColorSpace::BT601
:
1336 case YUVColorSpace::BT709
:
1338 case YUVColorSpace::BT2020
:
1340 case YUVColorSpace::Identity
:
1343 MOZ_CRASH("Bad YUVColorSpace");
1347 // Translate from CICP values to the color spaces we support, or return
1348 // Nothing() if there is no appropriate match to let the caller choose
1349 // a default or generate an error.
1351 // See Rec. ITU-T H.273 (12/2016) for details on CICP
1352 /* static */ Maybe
<gfx::YUVColorSpace
> gfxUtils::CicpToColorSpace(
1353 const CICP::MatrixCoefficients aMatrixCoefficients
,
1354 const CICP::ColourPrimaries aColourPrimaries
, LazyLogModule
& aLogger
) {
1355 switch (aMatrixCoefficients
) {
1356 case CICP::MatrixCoefficients::MC_BT2020_NCL
:
1357 case CICP::MatrixCoefficients::MC_BT2020_CL
:
1358 return Some(gfx::YUVColorSpace::BT2020
);
1359 case CICP::MatrixCoefficients::MC_BT601
:
1360 return Some(gfx::YUVColorSpace::BT601
);
1361 case CICP::MatrixCoefficients::MC_BT709
:
1362 return Some(gfx::YUVColorSpace::BT709
);
1363 case CICP::MatrixCoefficients::MC_IDENTITY
:
1364 return Some(gfx::YUVColorSpace::Identity
);
1365 case CICP::MatrixCoefficients::MC_CHROMAT_NCL
:
1366 case CICP::MatrixCoefficients::MC_CHROMAT_CL
:
1367 case CICP::MatrixCoefficients::MC_UNSPECIFIED
:
1368 switch (aColourPrimaries
) {
1369 case CICP::ColourPrimaries::CP_BT601
:
1370 return Some(gfx::YUVColorSpace::BT601
);
1371 case CICP::ColourPrimaries::CP_BT709
:
1372 return Some(gfx::YUVColorSpace::BT709
);
1373 case CICP::ColourPrimaries::CP_BT2020
:
1374 return Some(gfx::YUVColorSpace::BT2020
);
1376 MOZ_LOG(aLogger
, LogLevel::Debug
,
1377 ("Couldn't infer color matrix from primaries: %hhu",
1382 MOZ_LOG(aLogger
, LogLevel::Debug
,
1383 ("Unsupported color matrix value: %hhu", aMatrixCoefficients
));
1388 // Translate from CICP values to the color primaries we support, or return
1389 // Nothing() if there is no appropriate match to let the caller choose
1390 // a default or generate an error.
1392 // See Rec. ITU-T H.273 (12/2016) for details on CICP
1393 /* static */ Maybe
<gfx::ColorSpace2
> gfxUtils::CicpToColorPrimaries(
1394 const CICP::ColourPrimaries aColourPrimaries
, LazyLogModule
& aLogger
) {
1395 switch (aColourPrimaries
) {
1396 case CICP::ColourPrimaries::CP_BT709
:
1397 return Some(gfx::ColorSpace2::BT709
);
1398 case CICP::ColourPrimaries::CP_BT2020
:
1399 return Some(gfx::ColorSpace2::BT2020
);
1401 MOZ_LOG(aLogger
, LogLevel::Debug
,
1402 ("Unsupported color primaries value: %hhu", aColourPrimaries
));
1407 // Translate from CICP values to the transfer functions we support, or return
1408 // Nothing() if there is no appropriate match.
1410 /* static */ Maybe
<gfx::TransferFunction
> gfxUtils::CicpToTransferFunction(
1411 const CICP::TransferCharacteristics aTransferCharacteristics
) {
1412 switch (aTransferCharacteristics
) {
1413 case CICP::TransferCharacteristics::TC_BT709
:
1414 return Some(gfx::TransferFunction::BT709
);
1416 case CICP::TransferCharacteristics::TC_SRGB
:
1417 return Some(gfx::TransferFunction::SRGB
);
1419 case CICP::TransferCharacteristics::TC_SMPTE2084
:
1420 return Some(gfx::TransferFunction::PQ
);
1422 case CICP::TransferCharacteristics::TC_HLG
:
1423 return Some(gfx::TransferFunction::HLG
);
1431 void gfxUtils::WriteAsPNG(SourceSurface
* aSurface
, const nsAString
& aFile
) {
1432 WriteAsPNG(aSurface
, NS_ConvertUTF16toUTF8(aFile
).get());
1436 void gfxUtils::WriteAsPNG(SourceSurface
* aSurface
, const char* aFile
) {
1437 FILE* file
= fopen(aFile
, "wb");
1440 // Maybe the directory doesn't exist; try creating it, then fopen again.
1441 nsresult rv
= NS_ERROR_FAILURE
;
1442 nsCOMPtr
<nsIFile
> comFile
= do_CreateInstance("@mozilla.org/file/local;1");
1444 NS_ConvertUTF8toUTF16
utf16path((nsDependentCString(aFile
)));
1445 rv
= comFile
->InitWithPath(utf16path
);
1446 if (NS_SUCCEEDED(rv
)) {
1447 nsCOMPtr
<nsIFile
> dirPath
;
1448 comFile
->GetParent(getter_AddRefs(dirPath
));
1450 rv
= dirPath
->Create(nsIFile::DIRECTORY_TYPE
, 0777);
1451 if (NS_SUCCEEDED(rv
) || rv
== NS_ERROR_FILE_ALREADY_EXISTS
) {
1452 file
= fopen(aFile
, "wb");
1458 NS_WARNING("Failed to open file to create PNG!");
1463 EncodeSourceSurface(aSurface
, ImageType::PNG
, u
""_ns
, eBinaryEncode
, file
);
1468 void gfxUtils::WriteAsPNG(DrawTarget
* aDT
, const nsAString
& aFile
) {
1469 WriteAsPNG(aDT
, NS_ConvertUTF16toUTF8(aFile
).get());
1473 void gfxUtils::WriteAsPNG(DrawTarget
* aDT
, const char* aFile
) {
1474 RefPtr
<SourceSurface
> surface
= aDT
->Snapshot();
1476 WriteAsPNG(surface
, aFile
);
1478 NS_WARNING("Failed to get surface!");
1483 void gfxUtils::DumpAsDataURI(SourceSurface
* aSurface
, FILE* aFile
) {
1484 EncodeSourceSurface(aSurface
, ImageType::PNG
, u
""_ns
, eDataURIEncode
, aFile
);
1488 nsCString
gfxUtils::GetAsDataURI(SourceSurface
* aSurface
) {
1489 return EncodeSourceSurfaceAsPNGURI(aSurface
);
1493 void gfxUtils::DumpAsDataURI(DrawTarget
* aDT
, FILE* aFile
) {
1494 RefPtr
<SourceSurface
> surface
= aDT
->Snapshot();
1496 DumpAsDataURI(surface
, aFile
);
1498 NS_WARNING("Failed to get surface!");
1503 nsCString
gfxUtils::GetAsLZ4Base64Str(DataSourceSurface
* aSourceSurface
) {
1504 DataSourceSurface::ScopedMap
map(aSourceSurface
, DataSourceSurface::READ
);
1505 int32_t dataSize
= aSourceSurface
->GetSize().height
* map
.GetStride();
1506 auto compressedData
= MakeUnique
<char[]>(LZ4::maxCompressedSize(dataSize
));
1507 if (compressedData
) {
1509 LZ4::compress((char*)map
.GetData(), dataSize
, compressedData
.get());
1510 if (nDataSize
> 0) {
1512 string
.AppendPrintf("data:image/lz4bgra;base64,%i,%i,%i,",
1513 aSourceSurface
->GetSize().width
, map
.GetStride(),
1514 aSourceSurface
->GetSize().height
);
1515 nsresult rv
= Base64EncodeAppend(compressedData
.get(), nDataSize
, string
);
1525 nsCString
gfxUtils::GetAsDataURI(DrawTarget
* aDT
) {
1526 RefPtr
<SourceSurface
> surface
= aDT
->Snapshot();
1528 return EncodeSourceSurfaceAsPNGURI(surface
);
1530 NS_WARNING("Failed to get surface!");
1531 return nsCString("");
1536 void gfxUtils::CopyAsDataURI(SourceSurface
* aSurface
) {
1537 EncodeSourceSurface(aSurface
, ImageType::PNG
, u
""_ns
, eDataURIEncode
,
1542 void gfxUtils::CopyAsDataURI(DrawTarget
* aDT
) {
1543 RefPtr
<SourceSurface
> surface
= aDT
->Snapshot();
1545 CopyAsDataURI(surface
);
1547 NS_WARNING("Failed to get surface!");
1551 /* static */ UniquePtr
<uint8_t[]> gfxUtils::GetImageBuffer(
1552 gfx::DataSourceSurface
* aSurface
, bool aIsAlphaPremultiplied
,
1553 int32_t* outFormat
) {
1556 DataSourceSurface::MappedSurface map
;
1557 if (!aSurface
->Map(DataSourceSurface::MapType::READ
, &map
)) return nullptr;
1559 uint32_t bufferSize
=
1560 aSurface
->GetSize().width
* aSurface
->GetSize().height
* 4;
1561 auto imageBuffer
= MakeUniqueFallible
<uint8_t[]>(bufferSize
);
1566 memcpy(imageBuffer
.get(), map
.mData
, bufferSize
);
1570 int32_t format
= imgIEncoder::INPUT_FORMAT_HOSTARGB
;
1571 if (!aIsAlphaPremultiplied
) {
1572 // We need to convert to INPUT_FORMAT_RGBA, otherwise
1573 // we are automatically considered premult, and unpremult'd.
1574 // Yes, it is THAT silly.
1575 // Except for different lossy conversions by color,
1576 // we could probably just change the label, and not change the data.
1577 gfxUtils::ConvertBGRAtoRGBA(imageBuffer
.get(), bufferSize
);
1578 format
= imgIEncoder::INPUT_FORMAT_RGBA
;
1581 *outFormat
= format
;
1586 nsresult
gfxUtils::GetInputStream(gfx::DataSourceSurface
* aSurface
,
1587 bool aIsAlphaPremultiplied
,
1588 const char* aMimeType
,
1589 const nsAString
& aEncoderOptions
,
1590 nsIInputStream
** outStream
) {
1591 nsCString
enccid("@mozilla.org/image/encoder;2?type=");
1592 enccid
+= aMimeType
;
1593 nsCOMPtr
<imgIEncoder
> encoder
= do_CreateInstance(enccid
.get());
1594 if (!encoder
) return NS_ERROR_FAILURE
;
1597 UniquePtr
<uint8_t[]> imageBuffer
=
1598 GetImageBuffer(aSurface
, aIsAlphaPremultiplied
, &format
);
1599 if (!imageBuffer
) return NS_ERROR_FAILURE
;
1601 return dom::ImageEncoder::GetInputStream(
1602 aSurface
->GetSize().width
, aSurface
->GetSize().height
, imageBuffer
.get(),
1603 format
, encoder
, aEncoderOptions
, outStream
);
1606 class GetFeatureStatusWorkerRunnable final
1607 : public dom::WorkerMainThreadRunnable
{
1609 GetFeatureStatusWorkerRunnable(dom::WorkerPrivate
* workerPrivate
,
1610 const nsCOMPtr
<nsIGfxInfo
>& gfxInfo
,
1611 int32_t feature
, nsACString
& failureId
,
1613 : WorkerMainThreadRunnable(workerPrivate
, "GFX :: GetFeatureStatus"_ns
),
1617 mFailureId(failureId
),
1620 bool MainThreadRun() override
{
1622 mNSResult
= mGfxInfo
->GetFeatureStatus(mFeature
, mFailureId
, mStatus
);
1627 nsresult
GetNSResult() const { return mNSResult
; }
1630 ~GetFeatureStatusWorkerRunnable() = default;
1633 nsCOMPtr
<nsIGfxInfo
> mGfxInfo
;
1636 nsACString
& mFailureId
;
1640 #define GFX_SHADER_CHECK_BUILD_VERSION_PREF "gfx-shader-check.build-version"
1641 #define GFX_SHADER_CHECK_PTR_SIZE_PREF "gfx-shader-check.ptr-size"
1642 #define GFX_SHADER_CHECK_DEVICE_ID_PREF "gfx-shader-check.device-id"
1643 #define GFX_SHADER_CHECK_DRIVER_VERSION_PREF "gfx-shader-check.driver-version"
1646 void gfxUtils::RemoveShaderCacheFromDiskIfNecessary() {
1647 if (!gfxVars::UseWebRenderProgramBinaryDisk()) {
1651 nsCOMPtr
<nsIGfxInfo
> gfxInfo
= components::GfxInfo::Service();
1653 // Get current values
1654 nsCString
buildID(mozilla::PlatformBuildID());
1655 int ptrSize
= sizeof(void*);
1656 nsString deviceID
, driverVersion
;
1657 gfxInfo
->GetAdapterDeviceID(deviceID
);
1658 gfxInfo
->GetAdapterDriverVersion(driverVersion
);
1660 // Get pref stored values
1661 nsAutoCString buildIDChecked
;
1662 Preferences::GetCString(GFX_SHADER_CHECK_BUILD_VERSION_PREF
, buildIDChecked
);
1663 int ptrSizeChecked
= Preferences::GetInt(GFX_SHADER_CHECK_PTR_SIZE_PREF
, 0);
1664 nsAutoString deviceIDChecked
, driverVersionChecked
;
1665 Preferences::GetString(GFX_SHADER_CHECK_DEVICE_ID_PREF
, deviceIDChecked
);
1666 Preferences::GetString(GFX_SHADER_CHECK_DRIVER_VERSION_PREF
,
1667 driverVersionChecked
);
1669 if (buildID
== buildIDChecked
&& ptrSize
== ptrSizeChecked
&&
1670 deviceID
== deviceIDChecked
&& driverVersion
== driverVersionChecked
) {
1674 nsAutoString
path(gfx::gfxVars::ProfDirectory());
1676 if (!wr::remove_program_binary_disk_cache(&path
)) {
1677 // Failed to remove program binary disk cache. The disk cache might have
1678 // invalid data. Disable program binary disk cache usage.
1679 gfxVars::SetUseWebRenderProgramBinaryDisk(false);
1683 Preferences::SetCString(GFX_SHADER_CHECK_BUILD_VERSION_PREF
, buildID
);
1684 Preferences::SetInt(GFX_SHADER_CHECK_PTR_SIZE_PREF
, ptrSize
);
1685 Preferences::SetString(GFX_SHADER_CHECK_DEVICE_ID_PREF
, deviceID
);
1686 Preferences::SetString(GFX_SHADER_CHECK_DRIVER_VERSION_PREF
, driverVersion
);
1690 bool gfxUtils::DumpDisplayList() {
1691 return StaticPrefs::layout_display_list_dump() ||
1692 (StaticPrefs::layout_display_list_dump_parent() &&
1693 XRE_IsParentProcess()) ||
1694 (StaticPrefs::layout_display_list_dump_content() &&
1695 XRE_IsContentProcess());
1698 FILE* gfxUtils::sDumpPaintFile
= stderr
;
1703 DeviceColor
ToDeviceColor(const sRGBColor
& aColor
) {
1704 // aColor is pass-by-value since to get return value optimization goodness we
1705 // need to return the same object from all return points in this function. We
1706 // could declare a local Color variable and use that, but we might as well
1708 if (gfxPlatform::GetCMSMode() == CMSMode::All
) {
1709 qcms_transform
* transform
= gfxPlatform::GetCMSRGBTransform();
1711 return gfxPlatform::TransformPixel(aColor
, transform
);
1712 // Use the original alpha to avoid unnecessary float->byte->float
1713 // conversion errors
1716 return DeviceColor(aColor
.r
, aColor
.g
, aColor
.b
, aColor
.a
);
1719 DeviceColor
ToDeviceColor(nscolor aColor
) {
1720 return ToDeviceColor(sRGBColor::FromABGR(aColor
));
1723 DeviceColor
ToDeviceColor(const StyleRGBA
& aColor
) {
1724 return ToDeviceColor(aColor
.ToColor());
1727 sRGBColor
ToSRGBColor(const StyleAnimatedRGBA
& aColor
) {
1728 const auto ToComponent
= [](float aF
) -> float {
1729 float component
= std::min(std::max(0.0f
, aF
), 1.0f
);
1730 if (MOZ_UNLIKELY(!std::isfinite(component
))) {
1735 return {ToComponent(aColor
.red
), ToComponent(aColor
.green
),
1736 ToComponent(aColor
.blue
), ToComponent(aColor
.alpha
)};
1739 DeviceColor
ToDeviceColor(const StyleAnimatedRGBA
& aColor
) {
1740 return ToDeviceColor(ToSRGBColor(aColor
));
1744 } // namespace mozilla