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 gfxContext
tmpCtx(target
);
326 if (aUseOptimalFillOp
) {
327 tmpCtx
.SetOp(OptimalFillOp());
329 aDrawable
->Draw(&tmpCtx
, needed
- needed
.TopLeft(), ExtendMode::REPEAT
,
330 SamplingFilter::LINEAR
, 1.0,
331 gfxMatrix::Translation(needed
.TopLeft()));
332 RefPtr
<SourceSurface
> surface
= target
->Snapshot();
334 RefPtr
<gfxDrawable
> drawable
= new gfxSurfaceDrawable(
335 surface
, size
, gfxMatrix::Translation(-needed
.TopLeft()));
336 return drawable
.forget();
338 #endif // !MOZ_GFX_OPTIMIZE_MOBILE
340 /* These heuristics are based on
341 * Source/WebCore/platform/graphics/skia/ImageSkia.cpp:computeResamplingMode()
343 #ifdef MOZ_GFX_OPTIMIZE_MOBILE
344 static SamplingFilter
ReduceResamplingFilter(SamplingFilter aSamplingFilter
,
345 int aImgWidth
, int aImgHeight
,
347 float aSourceHeight
) {
348 // Images smaller than this in either direction are considered "small" and
349 // are not resampled ever (see below).
350 const int kSmallImageSizeThreshold
= 8;
352 // The amount an image can be stretched in a single direction before we
353 // say that it is being stretched so much that it must be a line or
354 // background that doesn't need resampling.
355 const float kLargeStretch
= 3.0f
;
357 if (aImgWidth
<= kSmallImageSizeThreshold
||
358 aImgHeight
<= kSmallImageSizeThreshold
) {
359 // Never resample small images. These are often used for borders and
360 // rules (think 1x1 images used to make lines).
361 return SamplingFilter::POINT
;
364 if (aImgHeight
* kLargeStretch
<= aSourceHeight
||
365 aImgWidth
* kLargeStretch
<= aSourceWidth
) {
366 // Large image tiling detected.
368 // Don't resample if it is being tiled a lot in only one direction.
369 // This is trying to catch cases where somebody has created a border
370 // (which might be large) and then is stretching it to fill some part
372 if (fabs(aSourceWidth
- aImgWidth
) / aImgWidth
< 0.5 ||
373 fabs(aSourceHeight
- aImgHeight
) / aImgHeight
< 0.5)
374 return SamplingFilter::POINT
;
376 // The image is growing a lot and in more than one direction. Resampling
377 // is slow and doesn't give us very much when growing a lot.
378 return aSamplingFilter
;
381 /* Some notes on other heuristics:
382 The Skia backend also uses nearest for backgrounds that are stretched by
383 a large amount. I'm not sure this is common enough for us to worry about
384 now. It also uses nearest for backgrounds/avoids high quality for images
385 that are very slightly scaled. I'm also not sure that very slightly
386 scaled backgrounds are common enough us to worry about.
388 We don't currently have much support for doing high quality interpolation.
389 The only place this currently happens is on Quartz and we don't have as
390 much control over it as would be needed. Webkit avoids using high quality
391 resampling during load. It also avoids high quality if the transformation
392 is not just a scale and translation
394 WebKit bug #40045 added code to avoid resampling different parts
395 of an image with different methods by using a resampling hint size.
396 It currently looks unused in WebKit but it's something to watch out for.
399 return aSamplingFilter
;
402 static SamplingFilter
ReduceResamplingFilter(SamplingFilter aSamplingFilter
,
403 int aImgWidth
, int aImgHeight
,
406 // Just pass the filter through unchanged
407 return aSamplingFilter
;
411 #ifdef MOZ_WIDGET_COCOA
412 // Only prescale a temporary surface if we're going to repeat it often.
413 // Scaling is expensive on OS X and without prescaling, we'd scale
414 // every tile of the repeated rect. However, using a temp surface also
415 // potentially uses more memory if the scaled image is large. So only prescale
416 // on a temp surface if we know we're going to repeat the image in either the X
417 // or Y axis multiple times.
418 static bool ShouldUseTempSurface(Rect aImageRect
, Rect aNeededRect
) {
419 int repeatX
= aNeededRect
.width
/ aImageRect
.width
;
420 int repeatY
= aNeededRect
.height
/ aImageRect
.height
;
421 return (repeatX
>= 5) || (repeatY
>= 5);
424 static bool PrescaleAndTileDrawable(gfxDrawable
* aDrawable
,
425 gfxContext
* aContext
,
426 const ImageRegion
& aRegion
, Rect aImageRect
,
427 const SamplingFilter aSamplingFilter
,
428 const SurfaceFormat aFormat
,
429 gfxFloat aOpacity
, ExtendMode aExtendMode
) {
430 MatrixScales scaleFactor
=
431 aContext
->CurrentMatrix().ScaleFactors().ConvertTo
<float>();
432 Matrix scaleMatrix
= Matrix::Scaling(scaleFactor
.xScale
, scaleFactor
.yScale
);
433 const float fuzzFactor
= 0.01;
435 // If we aren't scaling or translating, don't go down this path
436 if ((FuzzyEqual(scaleFactor
.xScale
, 1.0f
, fuzzFactor
) &&
437 FuzzyEqual(scaleFactor
.yScale
, 1.0f
, fuzzFactor
)) ||
438 aContext
->CurrentMatrix().HasNonAxisAlignedTransform()) {
442 gfxRect clipExtents
= aContext
->GetClipExtents();
444 // Inflate by one pixel because bilinear filtering will sample at most
445 // one pixel beyond the computed image pixel coordinate.
446 clipExtents
.Inflate(1.0);
448 gfxRect needed
= aRegion
.IntersectAndRestrict(clipExtents
);
449 Rect scaledNeededRect
= scaleMatrix
.TransformBounds(ToRect(needed
));
450 scaledNeededRect
.RoundOut();
451 if (scaledNeededRect
.IsEmpty()) {
455 Rect scaledImageRect
= scaleMatrix
.TransformBounds(aImageRect
);
456 if (!ShouldUseTempSurface(scaledImageRect
, scaledNeededRect
)) {
460 IntSize
scaledImageSize((int32_t)scaledImageRect
.width
,
461 (int32_t)scaledImageRect
.height
);
462 if (scaledImageSize
.width
!= scaledImageRect
.width
||
463 scaledImageSize
.height
!= scaledImageRect
.height
) {
464 // If the scaled image isn't pixel aligned, we'll get artifacts
465 // so we have to take the slow path.
469 RefPtr
<DrawTarget
> scaledDT
=
470 gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
471 scaledImageSize
, aFormat
);
472 if (!scaledDT
|| !scaledDT
->IsValid()) {
476 gfxContext
tmpCtx(scaledDT
);
478 scaledDT
->SetTransform(scaleMatrix
);
479 gfxRect
gfxImageRect(aImageRect
.x
, aImageRect
.y
, aImageRect
.width
,
482 // Since this is just the scaled image, we don't want to repeat anything yet.
483 aDrawable
->Draw(&tmpCtx
, gfxImageRect
, ExtendMode::CLAMP
, aSamplingFilter
,
486 RefPtr
<SourceSurface
> scaledImage
= scaledDT
->Snapshot();
489 gfxContextMatrixAutoSaveRestore
autoSR(aContext
);
490 Matrix withoutScale
= aContext
->CurrentMatrix();
491 DrawTarget
* destDrawTarget
= aContext
->GetDrawTarget();
493 // The translation still is in scaled units
494 withoutScale
.PreScale(1.0f
/ scaleFactor
.xScale
, 1.0f
/ scaleFactor
.yScale
);
495 aContext
->SetMatrix(withoutScale
);
497 DrawOptions
drawOptions(aOpacity
, aContext
->CurrentOp(),
498 aContext
->CurrentAntialiasMode());
500 SurfacePattern
scaledImagePattern(scaledImage
, aExtendMode
, Matrix(),
502 destDrawTarget
->FillRect(scaledNeededRect
, scaledImagePattern
, drawOptions
);
506 #endif // MOZ_WIDGET_COCOA
509 void gfxUtils::DrawPixelSnapped(gfxContext
* aContext
, gfxDrawable
* aDrawable
,
510 const gfxSize
& aImageSize
,
511 const ImageRegion
& aRegion
,
512 const SurfaceFormat aFormat
,
513 SamplingFilter aSamplingFilter
,
514 uint32_t aImageFlags
, gfxFloat aOpacity
,
515 bool aUseOptimalFillOp
) {
516 AUTO_PROFILER_LABEL("gfxUtils::DrawPixelSnapped", GRAPHICS
);
518 gfxRect
imageRect(gfxPoint(0, 0), aImageSize
);
519 gfxRect
region(aRegion
.Rect());
520 ExtendMode extendMode
= aRegion
.GetExtendMode();
522 RefPtr
<gfxDrawable
> drawable
= aDrawable
;
524 aSamplingFilter
= ReduceResamplingFilter(aSamplingFilter
, imageRect
.Width(),
525 imageRect
.Height(), region
.Width(),
528 // OK now, the hard part left is to account for the subimage sampling
529 // restriction. If all the transforms involved are just integer
530 // translations, then we assume no resampling will occur so there's
532 // XXX if only we had source-clipping in cairo!
534 if (aContext
->CurrentMatrix().HasNonIntegerTranslation()) {
535 if ((extendMode
!= ExtendMode::CLAMP
) ||
536 !aRegion
.RestrictionContains(imageRect
)) {
537 if (drawable
->DrawWithSamplingRect(
538 aContext
->GetDrawTarget(), aContext
->CurrentOp(),
539 aContext
->CurrentAntialiasMode(), aRegion
.Rect(),
540 aRegion
.Restriction(), extendMode
, aSamplingFilter
, aOpacity
)) {
544 #ifdef MOZ_WIDGET_COCOA
545 if (PrescaleAndTileDrawable(aDrawable
, aContext
, aRegion
,
546 ToRect(imageRect
), aSamplingFilter
, aFormat
,
547 aOpacity
, extendMode
)) {
552 // On Mobile, we don't ever want to do this; it has the potential for
553 // allocating very large temporary surfaces, especially since we'll
554 // do full-page snapshots often (see bug 749426).
555 #if !defined(MOZ_GFX_OPTIMIZE_MOBILE)
556 RefPtr
<gfxDrawable
> restrictedDrawable
= CreateSamplingRestrictedDrawable(
557 aDrawable
, aContext
, aRegion
, aFormat
, aUseOptimalFillOp
);
558 if (restrictedDrawable
) {
559 drawable
.swap(restrictedDrawable
);
561 // We no longer need to tile: Either we never needed to, or we already
562 // filled a surface with the tiled pattern; this surface can now be
563 // drawn without tiling.
564 extendMode
= ExtendMode::CLAMP
;
570 drawable
->Draw(aContext
, aRegion
.Rect(), extendMode
, aSamplingFilter
,
571 aOpacity
, gfxMatrix());
575 int gfxUtils::ImageFormatToDepth(gfxImageFormat aFormat
) {
577 case SurfaceFormat::A8R8G8B8_UINT32
:
579 case SurfaceFormat::X8R8G8B8_UINT32
:
581 case SurfaceFormat::R5G6B5_UINT16
:
590 void gfxUtils::ClipToRegion(gfxContext
* aContext
, const nsIntRegion
& aRegion
) {
592 for (auto iter
= aRegion
.RectIter(); !iter
.Done(); iter
.Next()) {
593 const IntRect
& r
= iter
.Get();
594 aContext
->Rectangle(gfxRect(r
.X(), r
.Y(), r
.Width(), r
.Height()));
600 void gfxUtils::ClipToRegion(DrawTarget
* aTarget
, const nsIntRegion
& aRegion
) {
601 uint32_t numRects
= aRegion
.GetNumRects();
602 // If there is only one rect, then the region bounds are equivalent to the
603 // contents. So just use push a single clip rect with the bounds.
605 aTarget
->PushClipRect(Rect(aRegion
.GetBounds()));
609 // Check if the target's transform will preserve axis-alignment and
610 // pixel-alignment for each rect. For now, just handle the common case
611 // of integer translations.
612 Matrix transform
= aTarget
->GetTransform();
613 if (transform
.IsIntegerTranslation()) {
614 IntPoint translation
= RoundedToInt(transform
.GetTranslation());
615 AutoTArray
<IntRect
, 16> rects
;
616 rects
.SetLength(numRects
);
618 // Build the list of transformed rects by adding in the translation.
619 for (auto iter
= aRegion
.RectIter(); !iter
.Done(); iter
.Next()) {
620 IntRect rect
= iter
.Get();
621 rect
.MoveBy(translation
);
624 aTarget
->PushDeviceSpaceClipRects(rects
.Elements(), rects
.Length());
626 // The transform does not produce axis-aligned rects or a rect was not
627 // pixel-aligned. So just build a path with all the rects and clip to it
629 RefPtr
<PathBuilder
> pathBuilder
= aTarget
->CreatePathBuilder();
630 for (auto iter
= aRegion
.RectIter(); !iter
.Done(); iter
.Next()) {
631 AppendRectToPath(pathBuilder
, Rect(iter
.Get()));
633 RefPtr
<Path
> path
= pathBuilder
->Finish();
634 aTarget
->PushClip(path
);
639 float gfxUtils::ClampToScaleFactor(float aVal
, bool aRoundDown
) {
640 // Arbitary scale factor limitation. We can increase this
641 // for better scaling performance at the cost of worse
643 static const float kScaleResolution
= 2;
645 // Negative scaling is just a flip and irrelevant to
646 // our resolution calculation.
651 bool inverse
= false;
657 float power
= logf(aVal
) / logf(kScaleResolution
);
659 // If power is within 1e-5 of an integer, round to nearest to
660 // prevent floating point errors, otherwise round up to the
661 // next integer value.
662 if (fabs(power
- NS_round(power
)) < 1e-5) {
663 power
= NS_round(power
);
664 // Use floor when we are either inverted or rounding down, but
666 } else if (inverse
!= aRoundDown
) {
667 power
= floor(power
);
668 // Otherwise, ceil when we are not inverted and not rounding
669 // down, or we are inverted and rounding down.
674 float scale
= powf(kScaleResolution
, power
);
683 gfxMatrix
gfxUtils::TransformRectToRect(const gfxRect
& aFrom
,
684 const gfxPoint
& aToTopLeft
,
685 const gfxPoint
& aToTopRight
,
686 const gfxPoint
& aToBottomRight
) {
688 if (aToTopRight
.y
== aToTopLeft
.y
&& aToTopRight
.x
== aToBottomRight
.x
) {
689 // Not a rotation, so xy and yx are zero
691 m
._11
= (aToBottomRight
.x
- aToTopLeft
.x
) / aFrom
.Width();
692 m
._22
= (aToBottomRight
.y
- aToTopLeft
.y
) / aFrom
.Height();
693 m
._31
= aToTopLeft
.x
- m
._11
* aFrom
.X();
694 m
._32
= aToTopLeft
.y
- m
._22
* aFrom
.Y();
697 aToTopRight
.y
== aToBottomRight
.y
&& aToTopRight
.x
== aToTopLeft
.x
,
698 "Destination rectangle not axis-aligned");
700 m
._21
= (aToBottomRight
.x
- aToTopLeft
.x
) / aFrom
.Height();
701 m
._12
= (aToBottomRight
.y
- aToTopLeft
.y
) / aFrom
.Width();
702 m
._31
= aToTopLeft
.x
- m
._21
* aFrom
.Y();
703 m
._32
= aToTopLeft
.y
- m
._12
* aFrom
.X();
708 Matrix
gfxUtils::TransformRectToRect(const gfxRect
& aFrom
,
709 const IntPoint
& aToTopLeft
,
710 const IntPoint
& aToTopRight
,
711 const IntPoint
& aToBottomRight
) {
713 if (aToTopRight
.y
== aToTopLeft
.y
&& aToTopRight
.x
== aToBottomRight
.x
) {
714 // Not a rotation, so xy and yx are zero
716 m
._11
= (aToBottomRight
.x
- aToTopLeft
.x
) / aFrom
.Width();
717 m
._22
= (aToBottomRight
.y
- aToTopLeft
.y
) / aFrom
.Height();
718 m
._31
= aToTopLeft
.x
- m
._11
* aFrom
.X();
719 m
._32
= aToTopLeft
.y
- m
._22
* aFrom
.Y();
722 aToTopRight
.y
== aToBottomRight
.y
&& aToTopRight
.x
== aToTopLeft
.x
,
723 "Destination rectangle not axis-aligned");
725 m
._21
= (aToBottomRight
.x
- aToTopLeft
.x
) / aFrom
.Height();
726 m
._12
= (aToBottomRight
.y
- aToTopLeft
.y
) / aFrom
.Width();
727 m
._31
= aToTopLeft
.x
- m
._21
* aFrom
.Y();
728 m
._32
= aToTopLeft
.y
- m
._12
* aFrom
.X();
733 /* This function is sort of shitty. We truncate doubles
734 * to ints then convert those ints back to doubles to make sure that
735 * they equal the doubles that we got in. */
736 bool gfxUtils::GfxRectToIntRect(const gfxRect
& aIn
, IntRect
* aOut
) {
737 *aOut
= IntRect(int32_t(aIn
.X()), int32_t(aIn
.Y()), int32_t(aIn
.Width()),
738 int32_t(aIn
.Height()));
739 return gfxRect(aOut
->X(), aOut
->Y(), aOut
->Width(), aOut
->Height())
743 /* Clamp r to CAIRO_COORD_MIN .. CAIRO_COORD_MAX
744 * these are to be device coordinates.
746 * Cairo is currently using 24.8 fixed point,
747 * so -2^24 .. 2^24-1 is our valid
750 void gfxUtils::ConditionRect(gfxRect
& aRect
) {
751 #define CAIRO_COORD_MAX (16777215.0)
752 #define CAIRO_COORD_MIN (-16777216.0)
753 // if either x or y is way out of bounds;
754 // note that we don't handle negative w/h here
755 if (aRect
.X() > CAIRO_COORD_MAX
) {
756 aRect
.SetRectX(CAIRO_COORD_MAX
, 0.0);
759 if (aRect
.Y() > CAIRO_COORD_MAX
) {
760 aRect
.SetRectY(CAIRO_COORD_MAX
, 0.0);
763 if (aRect
.X() < CAIRO_COORD_MIN
) {
764 aRect
.SetWidth(aRect
.XMost() - CAIRO_COORD_MIN
);
765 if (aRect
.Width() < 0.0) {
768 aRect
.MoveToX(CAIRO_COORD_MIN
);
771 if (aRect
.Y() < CAIRO_COORD_MIN
) {
772 aRect
.SetHeight(aRect
.YMost() - CAIRO_COORD_MIN
);
773 if (aRect
.Height() < 0.0) {
774 aRect
.SetHeight(0.0);
776 aRect
.MoveToY(CAIRO_COORD_MIN
);
779 if (aRect
.XMost() > CAIRO_COORD_MAX
) {
780 aRect
.SetRightEdge(CAIRO_COORD_MAX
);
783 if (aRect
.YMost() > CAIRO_COORD_MAX
) {
784 aRect
.SetBottomEdge(CAIRO_COORD_MAX
);
786 #undef CAIRO_COORD_MAX
787 #undef CAIRO_COORD_MIN
791 gfxQuad
gfxUtils::TransformToQuad(const gfxRect
& aRect
,
792 const mozilla::gfx::Matrix4x4
& aMatrix
) {
795 points
[0] = aMatrix
.TransformPoint(aRect
.TopLeft());
796 points
[1] = aMatrix
.TransformPoint(aRect
.TopRight());
797 points
[2] = aMatrix
.TransformPoint(aRect
.BottomRight());
798 points
[3] = aMatrix
.TransformPoint(aRect
.BottomLeft());
800 // Could this ever result in lines that intersect? I don't think so.
801 return gfxQuad(points
[0], points
[1], points
[2], points
[3]);
804 Matrix4x4
gfxUtils::SnapTransformTranslation(const Matrix4x4
& aTransform
,
805 Matrix
* aResidualTransform
) {
806 if (aResidualTransform
) {
807 *aResidualTransform
= Matrix();
811 if (aTransform
.CanDraw2D(&matrix2D
) && !matrix2D
.HasNonTranslation() &&
812 matrix2D
.HasNonIntegerTranslation()) {
813 return Matrix4x4::From2D(
814 SnapTransformTranslation(matrix2D
, aResidualTransform
));
817 return SnapTransformTranslation3D(aTransform
, aResidualTransform
);
820 Matrix
gfxUtils::SnapTransformTranslation(const Matrix
& aTransform
,
821 Matrix
* aResidualTransform
) {
822 if (aResidualTransform
) {
823 *aResidualTransform
= Matrix();
826 if (!aTransform
.HasNonTranslation() &&
827 aTransform
.HasNonIntegerTranslation()) {
828 auto snappedTranslation
= IntPoint::Round(aTransform
.GetTranslation());
829 Matrix snappedMatrix
=
830 Matrix::Translation(snappedTranslation
.x
, snappedTranslation
.y
);
831 if (aResidualTransform
) {
832 // set aResidualTransform so that aResidual * snappedMatrix == matrix2D.
833 // (I.e., appying snappedMatrix after aResidualTransform gives the
835 *aResidualTransform
=
836 Matrix::Translation(aTransform
._31
- snappedTranslation
.x
,
837 aTransform
._32
- snappedTranslation
.y
);
839 return snappedMatrix
;
845 Matrix4x4
gfxUtils::SnapTransformTranslation3D(const Matrix4x4
& aTransform
,
846 Matrix
* aResidualTransform
) {
847 if (aTransform
.IsSingular() || aTransform
.HasPerspectiveComponent() ||
848 aTransform
.HasNonTranslation() ||
849 !aTransform
.HasNonIntegerTranslation()) {
850 // For a singular transform, there is no reversed matrix, so we
852 // For a perspective transform, the content is transformed in
853 // non-linear, so we don't snap it too.
857 // Snap for 3D Transforms
859 Point3D transformedOrigin
= aTransform
.TransformPoint(Point3D());
861 // Compute the transformed snap by rounding the values of
862 // transformed origin.
863 auto transformedSnapXY
=
864 IntPoint::Round(transformedOrigin
.x
, transformedOrigin
.y
);
865 Matrix4x4 inverse
= aTransform
;
867 // see Matrix4x4::ProjectPoint()
868 Float transformedSnapZ
=
870 : (-(transformedSnapXY
.x
* inverse
._13
+
871 transformedSnapXY
.y
* inverse
._23
+ inverse
._43
) /
873 Point3D transformedSnap
=
874 Point3D(transformedSnapXY
.x
, transformedSnapXY
.y
, transformedSnapZ
);
875 if (transformedOrigin
== transformedSnap
) {
879 // Compute the snap from the transformed snap.
880 Point3D snap
= inverse
.TransformPoint(transformedSnap
);
881 if (snap
.z
> 0.001 || snap
.z
< -0.001) {
882 // Allow some level of accumulated computation error.
883 MOZ_ASSERT(inverse
._33
== 0.0);
887 // The difference between the origin and snap is the residual transform.
888 if (aResidualTransform
) {
889 // The residual transform is to translate the snap to the origin
890 // of the content buffer.
891 *aResidualTransform
= Matrix::Translation(-snap
.x
, -snap
.y
);
894 // Translate transformed origin to transformed snap since the
895 // residual transform would trnslate the snap to the origin.
896 Point3D transformedShift
= transformedSnap
- transformedOrigin
;
897 Matrix4x4 result
= aTransform
;
898 result
.PostTranslate(transformedShift
.x
, transformedShift
.y
,
901 // For non-2d transform, residual translation could be more than
902 // 0.5 pixels for every axis.
907 Matrix4x4
gfxUtils::SnapTransform(const Matrix4x4
& aTransform
,
908 const gfxRect
& aSnapRect
,
909 Matrix
* aResidualTransform
) {
910 if (aResidualTransform
) {
911 *aResidualTransform
= Matrix();
915 if (aTransform
.Is2D(&matrix2D
)) {
916 return Matrix4x4::From2D(
917 SnapTransform(matrix2D
, aSnapRect
, aResidualTransform
));
922 Matrix
gfxUtils::SnapTransform(const Matrix
& aTransform
,
923 const gfxRect
& aSnapRect
,
924 Matrix
* aResidualTransform
) {
925 if (aResidualTransform
) {
926 *aResidualTransform
= Matrix();
929 if (gfxSize(1.0, 1.0) <= aSnapRect
.Size() &&
930 aTransform
.PreservesAxisAlignedRectangles()) {
931 auto transformedTopLeft
= IntPoint::Round(
932 aTransform
.TransformPoint(ToPoint(aSnapRect
.TopLeft())));
933 auto transformedTopRight
= IntPoint::Round(
934 aTransform
.TransformPoint(ToPoint(aSnapRect
.TopRight())));
935 auto transformedBottomRight
= IntPoint::Round(
936 aTransform
.TransformPoint(ToPoint(aSnapRect
.BottomRight())));
938 Matrix snappedMatrix
= gfxUtils::TransformRectToRect(
939 aSnapRect
, transformedTopLeft
, transformedTopRight
,
940 transformedBottomRight
);
942 if (aResidualTransform
&& !snappedMatrix
.IsSingular()) {
943 // set aResidualTransform so that aResidual * snappedMatrix == matrix2D.
944 // (i.e., appying snappedMatrix after aResidualTransform gives the
946 Matrix snappedMatrixInverse
= snappedMatrix
;
947 snappedMatrixInverse
.Invert();
948 *aResidualTransform
= aTransform
* snappedMatrixInverse
;
950 return snappedMatrix
;
956 void gfxUtils::ClearThebesSurface(gfxASurface
* aSurface
) {
957 if (aSurface
->CairoStatus()) {
960 cairo_surface_t
* surf
= aSurface
->CairoSurface();
961 if (cairo_surface_status(surf
)) {
964 cairo_t
* ctx
= cairo_create(surf
);
965 cairo_set_source_rgba(ctx
, 0.0, 0.0, 0.0, 0.0);
966 cairo_set_operator(ctx
, CAIRO_OPERATOR_SOURCE
);
967 IntRect
bounds(nsIntPoint(0, 0), aSurface
->GetSize());
968 cairo_rectangle(ctx
, bounds
.X(), bounds
.Y(), bounds
.Width(), bounds
.Height());
974 already_AddRefed
<DataSourceSurface
>
975 gfxUtils::CopySurfaceToDataSourceSurfaceWithFormat(SourceSurface
* aSurface
,
976 SurfaceFormat aFormat
) {
977 MOZ_ASSERT(aFormat
!= aSurface
->GetFormat(),
978 "Unnecessary - and very expersive - surface format conversion");
980 Rect
bounds(0, 0, aSurface
->GetSize().width
, aSurface
->GetSize().height
);
982 if (!aSurface
->IsDataSourceSurface()) {
983 // If the surface is NOT of type DATA then its data is not mapped into main
984 // memory. Format conversion is probably faster on the GPU, and by doing it
985 // there we can avoid any expensive uploads/readbacks except for (possibly)
986 // a single readback due to the unavoidable GetDataSurface() call. Using
987 // CreateOffscreenContentDrawTarget ensures the conversion happens on the
989 RefPtr
<DrawTarget
> dt
=
990 gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
991 aSurface
->GetSize(), aFormat
);
993 gfxWarning() << "gfxUtils::CopySurfaceToDataSourceSurfaceWithFormat "
994 "failed in CreateOffscreenContentDrawTarget";
998 // Using DrawSurface() here rather than CopySurface() because CopySurface
999 // is optimized for memcpy and therefore isn't good for format conversion.
1000 // Using OP_OVER since in our case it's equivalent to OP_SOURCE and
1001 // generally more optimized.
1002 dt
->DrawSurface(aSurface
, bounds
, bounds
, DrawSurfaceOptions(),
1003 DrawOptions(1.0f
, CompositionOp::OP_OVER
));
1004 RefPtr
<SourceSurface
> surface
= dt
->Snapshot();
1005 return surface
->GetDataSurface();
1008 // If the surface IS of type DATA then it may or may not be in main memory
1009 // depending on whether or not it has been mapped yet. We have no way of
1010 // knowing, so we can't be sure if it's best to create a data wrapping
1011 // DrawTarget for the conversion or an offscreen content DrawTarget. We could
1012 // guess it's not mapped and create an offscreen content DrawTarget, but if
1013 // it is then we'll end up uploading the surface data, and most likely the
1014 // caller is going to be accessing the resulting surface data, resulting in a
1015 // readback (both very expensive operations). Alternatively we could guess
1016 // the data is mapped and create a data wrapping DrawTarget and, if the
1017 // surface is not in main memory, then we will incure a readback. The latter
1018 // of these two "wrong choices" is the least costly (a readback, vs an
1019 // upload and a readback), and more than likely the DATA surface that we've
1020 // been passed actually IS in main memory anyway. For these reasons it's most
1021 // likely best to create a data wrapping DrawTarget here to do the format
1023 RefPtr
<DataSourceSurface
> dataSurface
=
1024 Factory::CreateDataSourceSurface(aSurface
->GetSize(), aFormat
);
1025 DataSourceSurface::MappedSurface map
;
1027 !dataSurface
->Map(DataSourceSurface::MapType::READ_WRITE
, &map
)) {
1030 RefPtr
<DrawTarget
> dt
= Factory::CreateDrawTargetForData(
1031 BackendType::CAIRO
, map
.mData
, dataSurface
->GetSize(), map
.mStride
,
1034 dataSurface
->Unmap();
1037 // Using DrawSurface() here rather than CopySurface() because CopySurface
1038 // is optimized for memcpy and therefore isn't good for format conversion.
1039 // Using OP_OVER since in our case it's equivalent to OP_SOURCE and
1040 // generally more optimized.
1041 dt
->DrawSurface(aSurface
, bounds
, bounds
, DrawSurfaceOptions(),
1042 DrawOptions(1.0f
, CompositionOp::OP_OVER
));
1043 dataSurface
->Unmap();
1044 return dataSurface
.forget();
1047 const uint32_t gfxUtils::sNumFrameColors
= 8;
1050 const gfx::DeviceColor
& gfxUtils::GetColorForFrameNumber(
1051 uint64_t aFrameNumber
) {
1052 static bool initialized
= false;
1053 static gfx::DeviceColor colors
[sNumFrameColors
];
1056 // This isn't truly device color, but it is just for debug.
1058 colors
[i
++] = gfx::DeviceColor::FromABGR(0xffff0000);
1059 colors
[i
++] = gfx::DeviceColor::FromABGR(0xffcc00ff);
1060 colors
[i
++] = gfx::DeviceColor::FromABGR(0xff0066cc);
1061 colors
[i
++] = gfx::DeviceColor::FromABGR(0xff00ff00);
1062 colors
[i
++] = gfx::DeviceColor::FromABGR(0xff33ffff);
1063 colors
[i
++] = gfx::DeviceColor::FromABGR(0xffff0099);
1064 colors
[i
++] = gfx::DeviceColor::FromABGR(0xff0000ff);
1065 colors
[i
++] = gfx::DeviceColor::FromABGR(0xff999999);
1066 MOZ_ASSERT(i
== sNumFrameColors
);
1070 return colors
[aFrameNumber
% sNumFrameColors
];
1074 nsresult
gfxUtils::EncodeSourceSurface(SourceSurface
* aSurface
,
1075 const ImageType aImageType
,
1076 const nsAString
& aOutputOptions
,
1077 BinaryOrData aBinaryOrData
, FILE* aFile
,
1078 nsACString
* aStrOut
) {
1079 MOZ_ASSERT(aBinaryOrData
== gfxUtils::eDataURIEncode
|| aFile
|| aStrOut
,
1080 "Copying binary encoding to clipboard not currently supported");
1082 const IntSize size
= aSurface
->GetSize();
1083 if (size
.IsEmpty()) {
1084 return NS_ERROR_INVALID_ARG
;
1086 const Size
floatSize(size
.width
, size
.height
);
1088 RefPtr
<DataSourceSurface
> dataSurface
;
1089 if (aSurface
->GetFormat() != SurfaceFormat::B8G8R8A8
) {
1090 // FIXME bug 995807 (B8G8R8X8), bug 831898 (R5G6B5)
1091 dataSurface
= gfxUtils::CopySurfaceToDataSourceSurfaceWithFormat(
1092 aSurface
, SurfaceFormat::B8G8R8A8
);
1094 dataSurface
= aSurface
->GetDataSurface();
1097 return NS_ERROR_FAILURE
;
1100 DataSourceSurface::MappedSurface map
;
1101 if (!dataSurface
->Map(DataSourceSurface::MapType::READ
, &map
)) {
1102 return NS_ERROR_FAILURE
;
1105 RefPtr
<imgIEncoder
> encoder
= nullptr;
1107 switch (aImageType
) {
1108 case ImageType::BMP
:
1109 encoder
= MakeRefPtr
<nsBMPEncoder
>();
1112 case ImageType::ICO
:
1113 encoder
= MakeRefPtr
<nsICOEncoder
>();
1116 case ImageType::JPEG
:
1117 encoder
= MakeRefPtr
<nsJPEGEncoder
>();
1120 case ImageType::PNG
:
1121 encoder
= MakeRefPtr
<nsPNGEncoder
>();
1128 MOZ_RELEASE_ASSERT(encoder
!= nullptr);
1130 nsresult rv
= encoder
->InitFromData(
1131 map
.mData
, BufferSizeFromStrideAndHeight(map
.mStride
, size
.height
),
1132 size
.width
, size
.height
, map
.mStride
, imgIEncoder::INPUT_FORMAT_HOSTARGB
,
1134 dataSurface
->Unmap();
1135 NS_ENSURE_SUCCESS(rv
, rv
);
1137 nsCOMPtr
<nsIInputStream
> imgStream(encoder
);
1139 return NS_ERROR_FAILURE
;
1143 rv
= imgStream
->Available(&bufSize64
);
1144 NS_ENSURE_SUCCESS(rv
, rv
);
1146 NS_ENSURE_TRUE(bufSize64
< UINT32_MAX
- 16, NS_ERROR_FAILURE
);
1148 uint32_t bufSize
= (uint32_t)bufSize64
;
1150 // ...leave a little extra room so we can call read again and make sure we
1151 // got everything. 16 bytes for better padding (maybe)
1153 uint32_t imgSize
= 0;
1154 Vector
<char> imgData
;
1155 if (!imgData
.initCapacity(bufSize
)) {
1156 return NS_ERROR_OUT_OF_MEMORY
;
1158 uint32_t numReadThisTime
= 0;
1159 while ((rv
= imgStream
->Read(imgData
.begin() + imgSize
, bufSize
- imgSize
,
1160 &numReadThisTime
)) == NS_OK
&&
1161 numReadThisTime
> 0) {
1162 // Update the length of the vector without overwriting the new data.
1163 if (!imgData
.growByUninitialized(numReadThisTime
)) {
1164 return NS_ERROR_OUT_OF_MEMORY
;
1167 imgSize
+= numReadThisTime
;
1168 if (imgSize
== bufSize
) {
1169 // need a bigger buffer, just double
1171 if (!imgData
.resizeUninitialized(bufSize
)) {
1172 return NS_ERROR_OUT_OF_MEMORY
;
1176 NS_ENSURE_SUCCESS(rv
, rv
);
1177 NS_ENSURE_TRUE(!imgData
.empty(), NS_ERROR_FAILURE
);
1179 if (aBinaryOrData
== gfxUtils::eBinaryEncode
) {
1181 Unused
<< fwrite(imgData
.begin(), 1, imgSize
, aFile
);
1186 nsCString stringBuf
;
1187 nsACString
& dataURI
= aStrOut
? *aStrOut
: stringBuf
;
1188 dataURI
.AppendLiteral("data:");
1190 switch (aImageType
) {
1191 case ImageType::BMP
:
1192 dataURI
.AppendLiteral(IMAGE_BMP
);
1195 case ImageType::ICO
:
1196 dataURI
.AppendLiteral(IMAGE_ICO_MS
);
1198 case ImageType::JPEG
:
1199 dataURI
.AppendLiteral(IMAGE_JPEG
);
1202 case ImageType::PNG
:
1203 dataURI
.AppendLiteral(IMAGE_PNG
);
1210 dataURI
.AppendLiteral(";base64,");
1211 rv
= Base64EncodeAppend(imgData
.begin(), imgSize
, dataURI
);
1212 NS_ENSURE_SUCCESS(rv
, rv
);
1216 if (aFile
== stdout
|| aFile
== stderr
) {
1217 // ADB logcat cuts off long strings so we will break it down
1218 const char* cStr
= dataURI
.BeginReading();
1219 size_t len
= strlen(cStr
);
1221 printf_stderr("IMG: %.140s\n", cStr
);
1222 if (len
<= 140) break;
1228 fprintf(aFile
, "%s", dataURI
.BeginReading());
1229 } else if (!aStrOut
) {
1230 nsCOMPtr
<nsIClipboardHelper
> clipboard(
1231 do_GetService("@mozilla.org/widget/clipboardhelper;1", &rv
));
1233 clipboard
->CopyString(NS_ConvertASCIItoUTF16(dataURI
));
1239 static nsCString
EncodeSourceSurfaceAsPNGURI(SourceSurface
* aSurface
) {
1241 gfxUtils::EncodeSourceSurface(aSurface
, ImageType::PNG
, u
""_ns
,
1242 gfxUtils::eDataURIEncode
, nullptr, &string
);
1246 // https://jdashg.github.io/misc/colors/from-coeffs.html
1247 const float kBT601NarrowYCbCrToRGB_RowMajor
[16] = {
1248 1.16438f
, 0.00000f
, 1.59603f
, -0.87420f
, 1.16438f
, -0.39176f
,
1249 -0.81297f
, 0.53167f
, 1.16438f
, 2.01723f
, 0.00000f
, -1.08563f
,
1250 0.00000f
, 0.00000f
, 0.00000f
, 1.00000f
};
1251 const float kBT709NarrowYCbCrToRGB_RowMajor
[16] = {
1252 1.16438f
, 0.00000f
, 1.79274f
, -0.97295f
, 1.16438f
, -0.21325f
,
1253 -0.53291f
, 0.30148f
, 1.16438f
, 2.11240f
, 0.00000f
, -1.13340f
,
1254 0.00000f
, 0.00000f
, 0.00000f
, 1.00000f
};
1255 const float kBT2020NarrowYCbCrToRGB_RowMajor
[16] = {
1256 1.16438f
, 0.00000f
, 1.67867f
, -0.91569f
, 1.16438f
, -0.18733f
,
1257 -0.65042f
, 0.34746f
, 1.16438f
, 2.14177f
, 0.00000f
, -1.14815f
,
1258 0.00000f
, 0.00000f
, 0.00000f
, 1.00000f
};
1259 const float kIdentityNarrowYCbCrToRGB_RowMajor
[16] = {
1260 0.00000f
, 0.00000f
, 1.00000f
, 0.00000f
, 1.00000f
, 0.00000f
,
1261 0.00000f
, 0.00000f
, 0.00000f
, 1.00000f
, 0.00000f
, 0.00000f
,
1262 0.00000f
, 0.00000f
, 0.00000f
, 1.00000f
};
1264 /* static */ const float* gfxUtils::YuvToRgbMatrix4x3RowMajor(
1265 gfx::YUVColorSpace aYUVColorSpace
) {
1267 { x[0], x[1], x[2], 0.0f, x[4], x[5], x[6], 0.0f, x[8], x[9], x[10], 0.0f }
1269 static const float rec601
[12] = X(kBT601NarrowYCbCrToRGB_RowMajor
);
1270 static const float rec709
[12] = X(kBT709NarrowYCbCrToRGB_RowMajor
);
1271 static const float rec2020
[12] = X(kBT2020NarrowYCbCrToRGB_RowMajor
);
1272 static const float identity
[12] = X(kIdentityNarrowYCbCrToRGB_RowMajor
);
1276 switch (aYUVColorSpace
) {
1277 case gfx::YUVColorSpace::BT601
:
1279 case gfx::YUVColorSpace::BT709
:
1281 case gfx::YUVColorSpace::BT2020
:
1283 case gfx::YUVColorSpace::Identity
:
1286 MOZ_CRASH("Bad YUVColorSpace");
1290 /* static */ const float* gfxUtils::YuvToRgbMatrix3x3ColumnMajor(
1291 gfx::YUVColorSpace aYUVColorSpace
) {
1293 { x[0], x[4], x[8], x[1], x[5], x[9], x[2], x[6], x[10] }
1295 static const float rec601
[9] = X(kBT601NarrowYCbCrToRGB_RowMajor
);
1296 static const float rec709
[9] = X(kBT709NarrowYCbCrToRGB_RowMajor
);
1297 static const float rec2020
[9] = X(kBT2020NarrowYCbCrToRGB_RowMajor
);
1298 static const float identity
[9] = X(kIdentityNarrowYCbCrToRGB_RowMajor
);
1302 switch (aYUVColorSpace
) {
1303 case gfx::YUVColorSpace::BT601
:
1305 case YUVColorSpace::BT709
:
1307 case YUVColorSpace::BT2020
:
1309 case YUVColorSpace::Identity
:
1312 MOZ_CRASH("Bad YUVColorSpace");
1316 /* static */ const float* gfxUtils::YuvToRgbMatrix4x4ColumnMajor(
1317 YUVColorSpace aYUVColorSpace
) {
1320 x[0], x[4], x[8], x[12], x[1], x[5], x[9], x[13], x[2], x[6], x[10], \
1321 x[14], x[3], x[7], x[11], x[15] \
1324 static const float rec601
[16] = X(kBT601NarrowYCbCrToRGB_RowMajor
);
1325 static const float rec709
[16] = X(kBT709NarrowYCbCrToRGB_RowMajor
);
1326 static const float rec2020
[16] = X(kBT2020NarrowYCbCrToRGB_RowMajor
);
1327 static const float identity
[16] = X(kIdentityNarrowYCbCrToRGB_RowMajor
);
1331 switch (aYUVColorSpace
) {
1332 case YUVColorSpace::BT601
:
1334 case YUVColorSpace::BT709
:
1336 case YUVColorSpace::BT2020
:
1338 case YUVColorSpace::Identity
:
1341 MOZ_CRASH("Bad YUVColorSpace");
1345 // Translate from CICP values to the color spaces we support, or return
1346 // Nothing() if there is no appropriate match to let the caller choose
1347 // a default or generate an error.
1349 // See Rec. ITU-T H.273 (12/2016) for details on CICP
1350 /* static */ Maybe
<gfx::YUVColorSpace
> gfxUtils::CicpToColorSpace(
1351 const CICP::MatrixCoefficients aMatrixCoefficients
,
1352 const CICP::ColourPrimaries aColourPrimaries
, LazyLogModule
& aLogger
) {
1353 switch (aMatrixCoefficients
) {
1354 case CICP::MatrixCoefficients::MC_BT2020_NCL
:
1355 case CICP::MatrixCoefficients::MC_BT2020_CL
:
1356 return Some(gfx::YUVColorSpace::BT2020
);
1357 case CICP::MatrixCoefficients::MC_BT601
:
1358 return Some(gfx::YUVColorSpace::BT601
);
1359 case CICP::MatrixCoefficients::MC_BT709
:
1360 return Some(gfx::YUVColorSpace::BT709
);
1361 case CICP::MatrixCoefficients::MC_IDENTITY
:
1362 return Some(gfx::YUVColorSpace::Identity
);
1363 case CICP::MatrixCoefficients::MC_CHROMAT_NCL
:
1364 case CICP::MatrixCoefficients::MC_CHROMAT_CL
:
1365 case CICP::MatrixCoefficients::MC_UNSPECIFIED
:
1366 switch (aColourPrimaries
) {
1367 case CICP::ColourPrimaries::CP_BT601
:
1368 return Some(gfx::YUVColorSpace::BT601
);
1369 case CICP::ColourPrimaries::CP_BT709
:
1370 return Some(gfx::YUVColorSpace::BT709
);
1371 case CICP::ColourPrimaries::CP_BT2020
:
1372 return Some(gfx::YUVColorSpace::BT2020
);
1374 MOZ_LOG(aLogger
, LogLevel::Debug
,
1375 ("Couldn't infer color matrix from primaries: %hhu",
1380 MOZ_LOG(aLogger
, LogLevel::Debug
,
1381 ("Unsupported color matrix value: %hhu", aMatrixCoefficients
));
1386 // Translate from CICP values to the color primaries we support, or return
1387 // Nothing() if there is no appropriate match to let the caller choose
1388 // a default or generate an error.
1390 // See Rec. ITU-T H.273 (12/2016) for details on CICP
1391 /* static */ Maybe
<gfx::ColorSpace2
> gfxUtils::CicpToColorPrimaries(
1392 const CICP::ColourPrimaries aColourPrimaries
, LazyLogModule
& aLogger
) {
1393 switch (aColourPrimaries
) {
1394 case CICP::ColourPrimaries::CP_BT709
:
1395 return Some(gfx::ColorSpace2::BT709
);
1396 case CICP::ColourPrimaries::CP_BT2020
:
1397 return Some(gfx::ColorSpace2::BT2020
);
1399 MOZ_LOG(aLogger
, LogLevel::Debug
,
1400 ("Unsupported color primaries value: %hhu", aColourPrimaries
));
1405 // Translate from CICP values to the transfer functions we support, or return
1406 // Nothing() if there is no appropriate match.
1408 /* static */ Maybe
<gfx::TransferFunction
> gfxUtils::CicpToTransferFunction(
1409 const CICP::TransferCharacteristics aTransferCharacteristics
) {
1410 switch (aTransferCharacteristics
) {
1411 case CICP::TransferCharacteristics::TC_BT709
:
1412 return Some(gfx::TransferFunction::BT709
);
1414 case CICP::TransferCharacteristics::TC_SRGB
:
1415 return Some(gfx::TransferFunction::SRGB
);
1417 case CICP::TransferCharacteristics::TC_SMPTE2084
:
1418 return Some(gfx::TransferFunction::PQ
);
1420 case CICP::TransferCharacteristics::TC_HLG
:
1421 return Some(gfx::TransferFunction::HLG
);
1429 void gfxUtils::WriteAsPNG(SourceSurface
* aSurface
, const nsAString
& aFile
) {
1430 WriteAsPNG(aSurface
, NS_ConvertUTF16toUTF8(aFile
).get());
1434 void gfxUtils::WriteAsPNG(SourceSurface
* aSurface
, const char* aFile
) {
1435 FILE* file
= fopen(aFile
, "wb");
1438 // Maybe the directory doesn't exist; try creating it, then fopen again.
1439 nsresult rv
= NS_ERROR_FAILURE
;
1440 nsCOMPtr
<nsIFile
> comFile
= do_CreateInstance("@mozilla.org/file/local;1");
1442 NS_ConvertUTF8toUTF16
utf16path((nsDependentCString(aFile
)));
1443 rv
= comFile
->InitWithPath(utf16path
);
1444 if (NS_SUCCEEDED(rv
)) {
1445 nsCOMPtr
<nsIFile
> dirPath
;
1446 comFile
->GetParent(getter_AddRefs(dirPath
));
1448 rv
= dirPath
->Create(nsIFile::DIRECTORY_TYPE
, 0777);
1449 if (NS_SUCCEEDED(rv
) || rv
== NS_ERROR_FILE_ALREADY_EXISTS
) {
1450 file
= fopen(aFile
, "wb");
1456 NS_WARNING("Failed to open file to create PNG!");
1461 EncodeSourceSurface(aSurface
, ImageType::PNG
, u
""_ns
, eBinaryEncode
, file
);
1466 void gfxUtils::WriteAsPNG(DrawTarget
* aDT
, const nsAString
& aFile
) {
1467 WriteAsPNG(aDT
, NS_ConvertUTF16toUTF8(aFile
).get());
1471 void gfxUtils::WriteAsPNG(DrawTarget
* aDT
, const char* aFile
) {
1472 RefPtr
<SourceSurface
> surface
= aDT
->Snapshot();
1474 WriteAsPNG(surface
, aFile
);
1476 NS_WARNING("Failed to get surface!");
1481 void gfxUtils::DumpAsDataURI(SourceSurface
* aSurface
, FILE* aFile
) {
1482 EncodeSourceSurface(aSurface
, ImageType::PNG
, u
""_ns
, eDataURIEncode
, aFile
);
1486 nsCString
gfxUtils::GetAsDataURI(SourceSurface
* aSurface
) {
1487 return EncodeSourceSurfaceAsPNGURI(aSurface
);
1491 void gfxUtils::DumpAsDataURI(DrawTarget
* aDT
, FILE* aFile
) {
1492 RefPtr
<SourceSurface
> surface
= aDT
->Snapshot();
1494 DumpAsDataURI(surface
, aFile
);
1496 NS_WARNING("Failed to get surface!");
1501 nsCString
gfxUtils::GetAsLZ4Base64Str(DataSourceSurface
* aSourceSurface
) {
1502 DataSourceSurface::ScopedMap
map(aSourceSurface
, DataSourceSurface::READ
);
1503 int32_t dataSize
= aSourceSurface
->GetSize().height
* map
.GetStride();
1504 auto compressedData
= MakeUnique
<char[]>(LZ4::maxCompressedSize(dataSize
));
1505 if (compressedData
) {
1507 LZ4::compress((char*)map
.GetData(), dataSize
, compressedData
.get());
1508 if (nDataSize
> 0) {
1510 string
.AppendPrintf("data:image/lz4bgra;base64,%i,%i,%i,",
1511 aSourceSurface
->GetSize().width
, map
.GetStride(),
1512 aSourceSurface
->GetSize().height
);
1513 nsresult rv
= Base64EncodeAppend(compressedData
.get(), nDataSize
, string
);
1523 nsCString
gfxUtils::GetAsDataURI(DrawTarget
* aDT
) {
1524 RefPtr
<SourceSurface
> surface
= aDT
->Snapshot();
1526 return EncodeSourceSurfaceAsPNGURI(surface
);
1528 NS_WARNING("Failed to get surface!");
1529 return nsCString("");
1534 void gfxUtils::CopyAsDataURI(SourceSurface
* aSurface
) {
1535 EncodeSourceSurface(aSurface
, ImageType::PNG
, u
""_ns
, eDataURIEncode
,
1540 void gfxUtils::CopyAsDataURI(DrawTarget
* aDT
) {
1541 RefPtr
<SourceSurface
> surface
= aDT
->Snapshot();
1543 CopyAsDataURI(surface
);
1545 NS_WARNING("Failed to get surface!");
1549 /* static */ UniquePtr
<uint8_t[]> gfxUtils::GetImageBuffer(
1550 gfx::DataSourceSurface
* aSurface
, bool aIsAlphaPremultiplied
,
1551 int32_t* outFormat
) {
1554 DataSourceSurface::MappedSurface map
;
1555 if (!aSurface
->Map(DataSourceSurface::MapType::READ
, &map
)) return nullptr;
1557 uint32_t bufferSize
=
1558 aSurface
->GetSize().width
* aSurface
->GetSize().height
* 4;
1559 auto imageBuffer
= MakeUniqueFallible
<uint8_t[]>(bufferSize
);
1564 memcpy(imageBuffer
.get(), map
.mData
, bufferSize
);
1568 int32_t format
= imgIEncoder::INPUT_FORMAT_HOSTARGB
;
1569 if (!aIsAlphaPremultiplied
) {
1570 // We need to convert to INPUT_FORMAT_RGBA, otherwise
1571 // we are automatically considered premult, and unpremult'd.
1572 // Yes, it is THAT silly.
1573 // Except for different lossy conversions by color,
1574 // we could probably just change the label, and not change the data.
1575 gfxUtils::ConvertBGRAtoRGBA(imageBuffer
.get(), bufferSize
);
1576 format
= imgIEncoder::INPUT_FORMAT_RGBA
;
1579 *outFormat
= format
;
1584 nsresult
gfxUtils::GetInputStream(gfx::DataSourceSurface
* aSurface
,
1585 bool aIsAlphaPremultiplied
,
1586 const char* aMimeType
,
1587 const nsAString
& aEncoderOptions
,
1588 nsIInputStream
** outStream
) {
1589 nsCString
enccid("@mozilla.org/image/encoder;2?type=");
1590 enccid
+= aMimeType
;
1591 nsCOMPtr
<imgIEncoder
> encoder
= do_CreateInstance(enccid
.get());
1592 if (!encoder
) return NS_ERROR_FAILURE
;
1595 UniquePtr
<uint8_t[]> imageBuffer
=
1596 GetImageBuffer(aSurface
, aIsAlphaPremultiplied
, &format
);
1597 if (!imageBuffer
) return NS_ERROR_FAILURE
;
1599 return dom::ImageEncoder::GetInputStream(
1600 aSurface
->GetSize().width
, aSurface
->GetSize().height
, imageBuffer
.get(),
1601 format
, encoder
, aEncoderOptions
, outStream
);
1604 class GetFeatureStatusWorkerRunnable final
1605 : public dom::WorkerMainThreadRunnable
{
1607 GetFeatureStatusWorkerRunnable(dom::WorkerPrivate
* workerPrivate
,
1608 const nsCOMPtr
<nsIGfxInfo
>& gfxInfo
,
1609 int32_t feature
, nsACString
& failureId
,
1611 : WorkerMainThreadRunnable(workerPrivate
, "GFX :: GetFeatureStatus"_ns
),
1615 mFailureId(failureId
),
1618 bool MainThreadRun() override
{
1620 mNSResult
= mGfxInfo
->GetFeatureStatus(mFeature
, mFailureId
, mStatus
);
1625 nsresult
GetNSResult() const { return mNSResult
; }
1628 ~GetFeatureStatusWorkerRunnable() = default;
1631 nsCOMPtr
<nsIGfxInfo
> mGfxInfo
;
1634 nsACString
& mFailureId
;
1638 #define GFX_SHADER_CHECK_BUILD_VERSION_PREF "gfx-shader-check.build-version"
1639 #define GFX_SHADER_CHECK_PTR_SIZE_PREF "gfx-shader-check.ptr-size"
1640 #define GFX_SHADER_CHECK_DEVICE_ID_PREF "gfx-shader-check.device-id"
1641 #define GFX_SHADER_CHECK_DRIVER_VERSION_PREF "gfx-shader-check.driver-version"
1644 void gfxUtils::RemoveShaderCacheFromDiskIfNecessary() {
1645 if (!gfxVars::UseWebRenderProgramBinaryDisk()) {
1649 nsCOMPtr
<nsIGfxInfo
> gfxInfo
= components::GfxInfo::Service();
1651 // Get current values
1652 nsCString
buildID(mozilla::PlatformBuildID());
1653 int ptrSize
= sizeof(void*);
1654 nsString deviceID
, driverVersion
;
1655 gfxInfo
->GetAdapterDeviceID(deviceID
);
1656 gfxInfo
->GetAdapterDriverVersion(driverVersion
);
1658 // Get pref stored values
1659 nsAutoCString buildIDChecked
;
1660 Preferences::GetCString(GFX_SHADER_CHECK_BUILD_VERSION_PREF
, buildIDChecked
);
1661 int ptrSizeChecked
= Preferences::GetInt(GFX_SHADER_CHECK_PTR_SIZE_PREF
, 0);
1662 nsAutoString deviceIDChecked
, driverVersionChecked
;
1663 Preferences::GetString(GFX_SHADER_CHECK_DEVICE_ID_PREF
, deviceIDChecked
);
1664 Preferences::GetString(GFX_SHADER_CHECK_DRIVER_VERSION_PREF
,
1665 driverVersionChecked
);
1667 if (buildID
== buildIDChecked
&& ptrSize
== ptrSizeChecked
&&
1668 deviceID
== deviceIDChecked
&& driverVersion
== driverVersionChecked
) {
1672 nsAutoString
path(gfx::gfxVars::ProfDirectory());
1674 if (!wr::remove_program_binary_disk_cache(&path
)) {
1675 // Failed to remove program binary disk cache. The disk cache might have
1676 // invalid data. Disable program binary disk cache usage.
1677 gfxVars::SetUseWebRenderProgramBinaryDisk(false);
1681 Preferences::SetCString(GFX_SHADER_CHECK_BUILD_VERSION_PREF
, buildID
);
1682 Preferences::SetInt(GFX_SHADER_CHECK_PTR_SIZE_PREF
, ptrSize
);
1683 Preferences::SetString(GFX_SHADER_CHECK_DEVICE_ID_PREF
, deviceID
);
1684 Preferences::SetString(GFX_SHADER_CHECK_DRIVER_VERSION_PREF
, driverVersion
);
1688 bool gfxUtils::DumpDisplayList() {
1689 return StaticPrefs::layout_display_list_dump() ||
1690 (StaticPrefs::layout_display_list_dump_parent() &&
1691 XRE_IsParentProcess()) ||
1692 (StaticPrefs::layout_display_list_dump_content() &&
1693 XRE_IsContentProcess());
1696 FILE* gfxUtils::sDumpPaintFile
= stderr
;
1701 DeviceColor
ToDeviceColor(const sRGBColor
& aColor
) {
1702 // aColor is pass-by-value since to get return value optimization goodness we
1703 // need to return the same object from all return points in this function. We
1704 // could declare a local Color variable and use that, but we might as well
1706 if (gfxPlatform::GetCMSMode() == CMSMode::All
) {
1707 qcms_transform
* transform
= gfxPlatform::GetCMSRGBTransform();
1709 return gfxPlatform::TransformPixel(aColor
, transform
);
1710 // Use the original alpha to avoid unnecessary float->byte->float
1711 // conversion errors
1714 return DeviceColor(aColor
.r
, aColor
.g
, aColor
.b
, aColor
.a
);
1717 DeviceColor
ToDeviceColor(nscolor aColor
) {
1718 return ToDeviceColor(sRGBColor::FromABGR(aColor
));
1721 DeviceColor
ToDeviceColor(const StyleAbsoluteColor
& aColor
) {
1722 // TODO(tlouw): aColor might not be in sRGB.
1723 MOZ_ASSERT(aColor
.color_space
== StyleColorSpace::Srgb
,
1724 "color should be in sRGB");
1726 return ToDeviceColor(aColor
.ToColor());
1729 sRGBColor
ToSRGBColor(const StyleAbsoluteColor
& aColor
) {
1730 // TODO(tlouw): aColor might not be in sRGB.
1731 MOZ_ASSERT(aColor
.color_space
== StyleColorSpace::Srgb
,
1732 "color should be in sRGB");
1734 const auto ToComponent
= [](float aF
) -> float {
1735 float component
= std::min(std::max(0.0f
, aF
), 1.0f
);
1736 if (MOZ_UNLIKELY(!std::isfinite(component
))) {
1741 return {ToComponent(aColor
.components
._0
), ToComponent(aColor
.components
._1
),
1742 ToComponent(aColor
.components
._2
), ToComponent(aColor
.alpha
)};
1746 } // namespace mozilla