Bug 1874684 - Part 28: Return DateDuration from DifferenceISODateTime. r=mgaudet
[gecko.git] / gfx / thebes / gfxUtils.cpp
blob98e68ae862550749e2add44930fa9e5de05741ad
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/. */
6 #include "gfxUtils.h"
8 #include "cairo.h"
9 #include "gfxContext.h"
10 #include "gfxEnv.h"
11 #include "gfxImageSurface.h"
12 #include "gfxPlatform.h"
13 #include "gfxDrawable.h"
14 #include "gfxQuad.h"
15 #include "imgIEncoder.h"
16 #include "mozilla/Base64.h"
17 #include "mozilla/StyleColorInlines.h"
18 #include "mozilla/Components.h"
19 #include "mozilla/dom/Document.h"
20 #include "mozilla/dom/ImageEncoder.h"
21 #include "mozilla/dom/WorkerPrivate.h"
22 #include "mozilla/dom/WorkerRunnable.h"
23 #include "mozilla/ipc/CrossProcessSemaphore.h"
24 #include "mozilla/gfx/2D.h"
25 #include "mozilla/gfx/DataSurfaceHelpers.h"
26 #include "mozilla/gfx/Logging.h"
27 #include "mozilla/gfx/PathHelpers.h"
28 #include "mozilla/gfx/Swizzle.h"
29 #include "mozilla/gfx/gfxVars.h"
30 #include "mozilla/image/nsBMPEncoder.h"
31 #include "mozilla/image/nsICOEncoder.h"
32 #include "mozilla/image/nsJPEGEncoder.h"
33 #include "mozilla/image/nsPNGEncoder.h"
34 #include "mozilla/layers/SynchronousTask.h"
35 #include "mozilla/Maybe.h"
36 #include "mozilla/Preferences.h"
37 #include "mozilla/ProfilerLabels.h"
38 #include "mozilla/RefPtr.h"
39 #include "mozilla/ServoStyleConsts.h"
40 #include "mozilla/StaticPrefs_gfx.h"
41 #include "mozilla/StaticPrefs_layout.h"
42 #include "mozilla/UniquePtrExtensions.h"
43 #include "mozilla/Unused.h"
44 #include "mozilla/webrender/webrender_ffi.h"
45 #include "nsAppRunner.h"
46 #include "nsComponentManagerUtils.h"
47 #include "nsIClipboardHelper.h"
48 #include "nsIFile.h"
49 #include "nsIGfxInfo.h"
50 #include "nsMimeTypes.h"
51 #include "nsPresContext.h"
52 #include "nsRegion.h"
53 #include "nsServiceManagerUtils.h"
54 #include "nsRFPService.h"
55 #include "ImageContainer.h"
56 #include "ImageRegion.h"
57 #include "gfx2DGlue.h"
59 #ifdef XP_WIN
60 # include "gfxWindowsPlatform.h"
61 #endif
63 using namespace mozilla;
64 using namespace mozilla::image;
65 using namespace mozilla::layers;
66 using namespace mozilla::gfx;
68 #undef compress
69 #include "mozilla/Compression.h"
71 using namespace mozilla::Compression;
72 extern "C" {
74 /**
75 * Dump a raw image to the default log. This function is exported
76 * from libxul, so it can be called from any library in addition to
77 * (of course) from a debugger.
79 * Note: this helper currently assumes that all 2-bytepp images are
80 * r5g6b5, and that all 4-bytepp images are r8g8b8a8.
82 NS_EXPORT
83 void mozilla_dump_image(void* bytes, int width, int height, int bytepp,
84 int strideBytes) {
85 if (0 == strideBytes) {
86 strideBytes = width * bytepp;
88 SurfaceFormat format;
89 // TODO more flexible; parse string?
90 switch (bytepp) {
91 case 2:
92 format = SurfaceFormat::R5G6B5_UINT16;
93 break;
94 case 4:
95 default:
96 format = SurfaceFormat::R8G8B8A8;
97 break;
100 RefPtr<DataSourceSurface> surf = Factory::CreateWrappingDataSourceSurface(
101 (uint8_t*)bytes, strideBytes, IntSize(width, height), format);
102 gfxUtils::DumpAsDataURI(surf);
106 static bool MapSrcDest(DataSourceSurface* srcSurf, DataSourceSurface* destSurf,
107 DataSourceSurface::MappedSurface* out_srcMap,
108 DataSourceSurface::MappedSurface* out_destMap) {
109 MOZ_ASSERT(srcSurf && destSurf);
110 MOZ_ASSERT(out_srcMap && out_destMap);
112 if (srcSurf->GetSize() != destSurf->GetSize()) {
113 MOZ_ASSERT(false, "Width and height must match.");
114 return false;
117 if (srcSurf == destSurf) {
118 DataSourceSurface::MappedSurface map;
119 if (!srcSurf->Map(DataSourceSurface::MapType::READ_WRITE, &map)) {
120 NS_WARNING("Couldn't Map srcSurf/destSurf.");
121 return false;
124 *out_srcMap = map;
125 *out_destMap = map;
126 return true;
129 // Map src for reading.
130 DataSourceSurface::MappedSurface srcMap;
131 if (!srcSurf->Map(DataSourceSurface::MapType::READ, &srcMap)) {
132 NS_WARNING("Couldn't Map srcSurf.");
133 return false;
136 // Map dest for writing.
137 DataSourceSurface::MappedSurface destMap;
138 if (!destSurf->Map(DataSourceSurface::MapType::WRITE, &destMap)) {
139 NS_WARNING("Couldn't Map aDest.");
140 srcSurf->Unmap();
141 return false;
144 *out_srcMap = srcMap;
145 *out_destMap = destMap;
146 return true;
149 static void UnmapSrcDest(DataSourceSurface* srcSurf,
150 DataSourceSurface* destSurf) {
151 if (srcSurf == destSurf) {
152 srcSurf->Unmap();
153 } else {
154 srcSurf->Unmap();
155 destSurf->Unmap();
159 bool gfxUtils::PremultiplyDataSurface(DataSourceSurface* srcSurf,
160 DataSourceSurface* destSurf) {
161 MOZ_ASSERT(srcSurf && destSurf);
163 DataSourceSurface::MappedSurface srcMap;
164 DataSourceSurface::MappedSurface destMap;
165 if (!MapSrcDest(srcSurf, destSurf, &srcMap, &destMap)) return false;
167 PremultiplyData(srcMap.mData, srcMap.mStride, srcSurf->GetFormat(),
168 destMap.mData, destMap.mStride, destSurf->GetFormat(),
169 srcSurf->GetSize());
171 UnmapSrcDest(srcSurf, destSurf);
172 return true;
175 bool gfxUtils::UnpremultiplyDataSurface(DataSourceSurface* srcSurf,
176 DataSourceSurface* destSurf) {
177 MOZ_ASSERT(srcSurf && destSurf);
179 DataSourceSurface::MappedSurface srcMap;
180 DataSourceSurface::MappedSurface destMap;
181 if (!MapSrcDest(srcSurf, destSurf, &srcMap, &destMap)) return false;
183 UnpremultiplyData(srcMap.mData, srcMap.mStride, srcSurf->GetFormat(),
184 destMap.mData, destMap.mStride, destSurf->GetFormat(),
185 srcSurf->GetSize());
187 UnmapSrcDest(srcSurf, destSurf);
188 return true;
191 static bool MapSrcAndCreateMappedDest(
192 DataSourceSurface* srcSurf, RefPtr<DataSourceSurface>* out_destSurf,
193 DataSourceSurface::MappedSurface* out_srcMap,
194 DataSourceSurface::MappedSurface* out_destMap) {
195 MOZ_ASSERT(srcSurf);
196 MOZ_ASSERT(out_destSurf && out_srcMap && out_destMap);
198 // Ok, map source for reading.
199 DataSourceSurface::MappedSurface srcMap;
200 if (!srcSurf->Map(DataSourceSurface::MapType::READ, &srcMap)) {
201 MOZ_ASSERT(false, "Couldn't Map srcSurf.");
202 return false;
205 // Make our dest surface based on the src.
206 RefPtr<DataSourceSurface> destSurf =
207 Factory::CreateDataSourceSurfaceWithStride(
208 srcSurf->GetSize(), srcSurf->GetFormat(), srcMap.mStride);
209 if (NS_WARN_IF(!destSurf)) {
210 return false;
213 DataSourceSurface::MappedSurface destMap;
214 if (!destSurf->Map(DataSourceSurface::MapType::WRITE, &destMap)) {
215 MOZ_ASSERT(false, "Couldn't Map destSurf.");
216 srcSurf->Unmap();
217 return false;
220 *out_destSurf = destSurf;
221 *out_srcMap = srcMap;
222 *out_destMap = destMap;
223 return true;
226 already_AddRefed<DataSourceSurface> gfxUtils::CreatePremultipliedDataSurface(
227 DataSourceSurface* srcSurf) {
228 RefPtr<DataSourceSurface> destSurf;
229 DataSourceSurface::MappedSurface srcMap;
230 DataSourceSurface::MappedSurface destMap;
231 if (!MapSrcAndCreateMappedDest(srcSurf, &destSurf, &srcMap, &destMap)) {
232 MOZ_ASSERT(false, "MapSrcAndCreateMappedDest failed.");
233 RefPtr<DataSourceSurface> surface(srcSurf);
234 return surface.forget();
237 PremultiplyData(srcMap.mData, srcMap.mStride, srcSurf->GetFormat(),
238 destMap.mData, destMap.mStride, destSurf->GetFormat(),
239 srcSurf->GetSize());
241 UnmapSrcDest(srcSurf, destSurf);
242 return destSurf.forget();
245 already_AddRefed<DataSourceSurface> gfxUtils::CreateUnpremultipliedDataSurface(
246 DataSourceSurface* srcSurf) {
247 RefPtr<DataSourceSurface> destSurf;
248 DataSourceSurface::MappedSurface srcMap;
249 DataSourceSurface::MappedSurface destMap;
250 if (!MapSrcAndCreateMappedDest(srcSurf, &destSurf, &srcMap, &destMap)) {
251 MOZ_ASSERT(false, "MapSrcAndCreateMappedDest failed.");
252 RefPtr<DataSourceSurface> surface(srcSurf);
253 return surface.forget();
256 UnpremultiplyData(srcMap.mData, srcMap.mStride, srcSurf->GetFormat(),
257 destMap.mData, destMap.mStride, destSurf->GetFormat(),
258 srcSurf->GetSize());
260 UnmapSrcDest(srcSurf, destSurf);
261 return destSurf.forget();
264 void gfxUtils::ConvertBGRAtoRGBA(uint8_t* aData, uint32_t aLength) {
265 MOZ_ASSERT((aLength % 4) == 0, "Loop below will pass srcEnd!");
266 SwizzleData(aData, aLength, SurfaceFormat::B8G8R8A8, aData, aLength,
267 SurfaceFormat::R8G8B8A8, IntSize(aLength / 4, 1));
270 #if !defined(MOZ_GFX_OPTIMIZE_MOBILE)
272 * This returns the fastest operator to use for solid surfaces which have no
273 * alpha channel or their alpha channel is uniformly opaque.
274 * This differs per render mode.
276 static CompositionOp OptimalFillOp() {
277 # ifdef XP_WIN
278 if (gfxWindowsPlatform::GetPlatform()->IsDirect2DBackend()) {
279 // D2D -really- hates operator source.
280 return CompositionOp::OP_OVER;
282 # endif
283 return CompositionOp::OP_SOURCE;
286 // EXTEND_PAD won't help us here; we have to create a temporary surface to hold
287 // the subimage of pixels we're allowed to sample.
288 static already_AddRefed<gfxDrawable> CreateSamplingRestrictedDrawable(
289 gfxDrawable* aDrawable, gfxContext* aContext, const ImageRegion& aRegion,
290 const SurfaceFormat aFormat, bool aUseOptimalFillOp) {
291 AUTO_PROFILER_LABEL("CreateSamplingRestrictedDrawable", GRAPHICS);
293 DrawTarget* destDrawTarget = aContext->GetDrawTarget();
294 // We've been not using CreateSamplingRestrictedDrawable in a bunch of places
295 // for a while. Let's disable it everywhere and confirm that it's ok to get
296 // rid of.
297 if (destDrawTarget->GetBackendType() == BackendType::DIRECT2D1_1 || (true)) {
298 return nullptr;
301 gfxRect clipExtents = aContext->GetClipExtents();
303 // Inflate by one pixel because bilinear filtering will sample at most
304 // one pixel beyond the computed image pixel coordinate.
305 clipExtents.Inflate(1.0);
307 gfxRect needed = aRegion.IntersectAndRestrict(clipExtents);
308 needed.RoundOut();
310 // if 'needed' is empty, nothing will be drawn since aFill
311 // must be entirely outside the clip region, so it doesn't
312 // matter what we do here, but we should avoid trying to
313 // create a zero-size surface.
314 if (needed.IsEmpty()) return nullptr;
316 IntSize size(int32_t(needed.Width()), int32_t(needed.Height()));
318 RefPtr<DrawTarget> target =
319 gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(size,
320 aFormat);
321 if (!target || !target->IsValid()) {
322 return nullptr;
325 gfxContext tmpCtx(target);
327 if (aUseOptimalFillOp) {
328 tmpCtx.SetOp(OptimalFillOp());
330 aDrawable->Draw(&tmpCtx, needed - needed.TopLeft(), ExtendMode::REPEAT,
331 SamplingFilter::LINEAR, 1.0,
332 gfxMatrix::Translation(needed.TopLeft()));
333 RefPtr<SourceSurface> surface = target->Snapshot();
335 RefPtr<gfxDrawable> drawable = new gfxSurfaceDrawable(
336 surface, size, gfxMatrix::Translation(-needed.TopLeft()));
337 return drawable.forget();
339 #endif // !MOZ_GFX_OPTIMIZE_MOBILE
341 /* These heuristics are based on
342 * Source/WebCore/platform/graphics/skia/ImageSkia.cpp:computeResamplingMode()
344 #ifdef MOZ_GFX_OPTIMIZE_MOBILE
345 static SamplingFilter ReduceResamplingFilter(SamplingFilter aSamplingFilter,
346 int aImgWidth, int aImgHeight,
347 float aSourceWidth,
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
372 // of the page.
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;
402 #else
403 static SamplingFilter ReduceResamplingFilter(SamplingFilter aSamplingFilter,
404 int aImgWidth, int aImgHeight,
405 int aSourceWidth,
406 int aSourceHeight) {
407 // Just pass the filter through unchanged
408 return aSamplingFilter;
410 #endif
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()) {
440 return false;
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()) {
453 return false;
456 Rect scaledImageRect = scaleMatrix.TransformBounds(aImageRect);
457 if (!ShouldUseTempSurface(scaledImageRect, scaledNeededRect)) {
458 return false;
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.
467 return false;
470 RefPtr<DrawTarget> scaledDT =
471 gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
472 scaledImageSize, aFormat);
473 if (!scaledDT || !scaledDT->IsValid()) {
474 return false;
477 gfxContext tmpCtx(scaledDT);
479 scaledDT->SetTransform(scaleMatrix);
480 gfxRect gfxImageRect(aImageRect.x, aImageRect.y, aImageRect.width,
481 aImageRect.height);
483 // Since this is just the scaled image, we don't want to repeat anything yet.
484 aDrawable->Draw(&tmpCtx, gfxImageRect, ExtendMode::CLAMP, aSamplingFilter,
485 1.0, gfxMatrix());
487 RefPtr<SourceSurface> scaledImage = scaledDT->Snapshot();
490 gfxContextMatrixAutoSaveRestore autoSR(aContext);
491 Matrix withoutScale = aContext->CurrentMatrix();
492 DrawTarget* destDrawTarget = aContext->GetDrawTarget();
494 // The translation still is in scaled units
495 withoutScale.PreScale(1.0f / scaleFactor.xScale, 1.0f / scaleFactor.yScale);
496 aContext->SetMatrix(withoutScale);
498 DrawOptions drawOptions(aOpacity, aContext->CurrentOp(),
499 aContext->CurrentAntialiasMode());
501 SurfacePattern scaledImagePattern(scaledImage, aExtendMode, Matrix(),
502 aSamplingFilter);
503 destDrawTarget->FillRect(scaledNeededRect, scaledImagePattern, drawOptions);
505 return true;
507 #endif // MOZ_WIDGET_COCOA
509 /* static */
510 void gfxUtils::DrawPixelSnapped(gfxContext* aContext, gfxDrawable* aDrawable,
511 const gfxSize& aImageSize,
512 const ImageRegion& aRegion,
513 const SurfaceFormat aFormat,
514 SamplingFilter aSamplingFilter,
515 uint32_t aImageFlags, gfxFloat aOpacity,
516 bool aUseOptimalFillOp) {
517 AUTO_PROFILER_LABEL("gfxUtils::DrawPixelSnapped", GRAPHICS);
519 gfxRect imageRect(gfxPoint(0, 0), aImageSize);
520 gfxRect region(aRegion.Rect());
521 ExtendMode extendMode = aRegion.GetExtendMode();
523 RefPtr<gfxDrawable> drawable = aDrawable;
525 aSamplingFilter = ReduceResamplingFilter(aSamplingFilter, imageRect.Width(),
526 imageRect.Height(), region.Width(),
527 region.Height());
529 // OK now, the hard part left is to account for the subimage sampling
530 // restriction. If all the transforms involved are just integer
531 // translations, then we assume no resampling will occur so there's
532 // nothing to do.
533 // XXX if only we had source-clipping in cairo!
535 if (aContext->CurrentMatrix().HasNonIntegerTranslation()) {
536 if ((extendMode != ExtendMode::CLAMP) ||
537 !aRegion.RestrictionContains(imageRect)) {
538 if (drawable->DrawWithSamplingRect(
539 aContext->GetDrawTarget(), aContext->CurrentOp(),
540 aContext->CurrentAntialiasMode(), aRegion.Rect(),
541 aRegion.Restriction(), extendMode, aSamplingFilter, aOpacity)) {
542 return;
545 #ifdef MOZ_WIDGET_COCOA
546 if (PrescaleAndTileDrawable(aDrawable, aContext, aRegion,
547 ToRect(imageRect), aSamplingFilter, aFormat,
548 aOpacity, extendMode)) {
549 return;
551 #endif
553 // On Mobile, we don't ever want to do this; it has the potential for
554 // allocating very large temporary surfaces, especially since we'll
555 // do full-page snapshots often (see bug 749426).
556 #if !defined(MOZ_GFX_OPTIMIZE_MOBILE)
557 RefPtr<gfxDrawable> restrictedDrawable = CreateSamplingRestrictedDrawable(
558 aDrawable, aContext, aRegion, aFormat, aUseOptimalFillOp);
559 if (restrictedDrawable) {
560 drawable.swap(restrictedDrawable);
562 // We no longer need to tile: Either we never needed to, or we already
563 // filled a surface with the tiled pattern; this surface can now be
564 // drawn without tiling.
565 extendMode = ExtendMode::CLAMP;
567 #endif
571 drawable->Draw(aContext, aRegion.Rect(), extendMode, aSamplingFilter,
572 aOpacity, gfxMatrix());
575 /* static */
576 int gfxUtils::ImageFormatToDepth(gfxImageFormat aFormat) {
577 switch (aFormat) {
578 case SurfaceFormat::A8R8G8B8_UINT32:
579 return 32;
580 case SurfaceFormat::X8R8G8B8_UINT32:
581 return 24;
582 case SurfaceFormat::R5G6B5_UINT16:
583 return 16;
584 default:
585 break;
587 return 0;
590 /*static*/
591 void gfxUtils::ClipToRegion(gfxContext* aContext, const nsIntRegion& aRegion) {
592 aContext->NewPath();
593 for (auto iter = aRegion.RectIter(); !iter.Done(); iter.Next()) {
594 const IntRect& r = iter.Get();
595 aContext->Rectangle(gfxRect(r.X(), r.Y(), r.Width(), r.Height()));
597 aContext->Clip();
600 /*static*/
601 void gfxUtils::ClipToRegion(DrawTarget* aTarget, const nsIntRegion& aRegion) {
602 uint32_t numRects = aRegion.GetNumRects();
603 // If there is only one rect, then the region bounds are equivalent to the
604 // contents. So just use push a single clip rect with the bounds.
605 if (numRects == 1) {
606 aTarget->PushClipRect(Rect(aRegion.GetBounds()));
607 return;
610 // Check if the target's transform will preserve axis-alignment and
611 // pixel-alignment for each rect. For now, just handle the common case
612 // of integer translations.
613 Matrix transform = aTarget->GetTransform();
614 if (transform.IsIntegerTranslation()) {
615 IntPoint translation = RoundedToInt(transform.GetTranslation());
616 AutoTArray<IntRect, 16> rects;
617 rects.SetLength(numRects);
618 uint32_t i = 0;
619 // Build the list of transformed rects by adding in the translation.
620 for (auto iter = aRegion.RectIter(); !iter.Done(); iter.Next()) {
621 IntRect rect = iter.Get();
622 rect.MoveBy(translation);
623 rects[i++] = rect;
625 aTarget->PushDeviceSpaceClipRects(rects.Elements(), rects.Length());
626 } else {
627 // The transform does not produce axis-aligned rects or a rect was not
628 // pixel-aligned. So just build a path with all the rects and clip to it
629 // instead.
630 RefPtr<PathBuilder> pathBuilder = aTarget->CreatePathBuilder();
631 for (auto iter = aRegion.RectIter(); !iter.Done(); iter.Next()) {
632 AppendRectToPath(pathBuilder, Rect(iter.Get()));
634 RefPtr<Path> path = pathBuilder->Finish();
635 aTarget->PushClip(path);
639 /*static*/
640 float gfxUtils::ClampToScaleFactor(float aVal, bool aRoundDown) {
641 // Arbitary scale factor limitation. We can increase this
642 // for better scaling performance at the cost of worse
643 // quality.
644 static const float kScaleResolution = 2;
646 // Negative scaling is just a flip and irrelevant to
647 // our resolution calculation.
648 if (aVal < 0.0) {
649 aVal = -aVal;
652 bool inverse = false;
653 if (aVal < 1.0) {
654 inverse = true;
655 aVal = 1 / aVal;
658 float power = logf(aVal) / logf(kScaleResolution);
660 // If power is within 1e-5 of an integer, round to nearest to
661 // prevent floating point errors, otherwise round up to the
662 // next integer value.
663 if (fabs(power - NS_round(power)) < 1e-5) {
664 power = NS_round(power);
665 // Use floor when we are either inverted or rounding down, but
666 // not both.
667 } else if (inverse != aRoundDown) {
668 power = floor(power);
669 // Otherwise, ceil when we are not inverted and not rounding
670 // down, or we are inverted and rounding down.
671 } else {
672 power = ceil(power);
675 float scale = powf(kScaleResolution, power);
677 if (inverse) {
678 scale = 1 / scale;
681 return scale;
684 gfxMatrix gfxUtils::TransformRectToRect(const gfxRect& aFrom,
685 const gfxPoint& aToTopLeft,
686 const gfxPoint& aToTopRight,
687 const gfxPoint& aToBottomRight) {
688 gfxMatrix m;
689 if (aToTopRight.y == aToTopLeft.y && aToTopRight.x == aToBottomRight.x) {
690 // Not a rotation, so xy and yx are zero
691 m._21 = m._12 = 0.0;
692 m._11 = (aToBottomRight.x - aToTopLeft.x) / aFrom.Width();
693 m._22 = (aToBottomRight.y - aToTopLeft.y) / aFrom.Height();
694 m._31 = aToTopLeft.x - m._11 * aFrom.X();
695 m._32 = aToTopLeft.y - m._22 * aFrom.Y();
696 } else {
697 NS_ASSERTION(
698 aToTopRight.y == aToBottomRight.y && aToTopRight.x == aToTopLeft.x,
699 "Destination rectangle not axis-aligned");
700 m._11 = m._22 = 0.0;
701 m._21 = (aToBottomRight.x - aToTopLeft.x) / aFrom.Height();
702 m._12 = (aToBottomRight.y - aToTopLeft.y) / aFrom.Width();
703 m._31 = aToTopLeft.x - m._21 * aFrom.Y();
704 m._32 = aToTopLeft.y - m._12 * aFrom.X();
706 return m;
709 Matrix gfxUtils::TransformRectToRect(const gfxRect& aFrom,
710 const IntPoint& aToTopLeft,
711 const IntPoint& aToTopRight,
712 const IntPoint& aToBottomRight) {
713 Matrix m;
714 if (aToTopRight.y == aToTopLeft.y && aToTopRight.x == aToBottomRight.x) {
715 // Not a rotation, so xy and yx are zero
716 m._12 = m._21 = 0.0;
717 m._11 = (aToBottomRight.x - aToTopLeft.x) / aFrom.Width();
718 m._22 = (aToBottomRight.y - aToTopLeft.y) / aFrom.Height();
719 m._31 = aToTopLeft.x - m._11 * aFrom.X();
720 m._32 = aToTopLeft.y - m._22 * aFrom.Y();
721 } else {
722 NS_ASSERTION(
723 aToTopRight.y == aToBottomRight.y && aToTopRight.x == aToTopLeft.x,
724 "Destination rectangle not axis-aligned");
725 m._11 = m._22 = 0.0;
726 m._21 = (aToBottomRight.x - aToTopLeft.x) / aFrom.Height();
727 m._12 = (aToBottomRight.y - aToTopLeft.y) / aFrom.Width();
728 m._31 = aToTopLeft.x - m._21 * aFrom.Y();
729 m._32 = aToTopLeft.y - m._12 * aFrom.X();
731 return m;
734 /* This function is sort of shitty. We truncate doubles
735 * to ints then convert those ints back to doubles to make sure that
736 * they equal the doubles that we got in. */
737 bool gfxUtils::GfxRectToIntRect(const gfxRect& aIn, IntRect* aOut) {
738 *aOut = IntRect(int32_t(aIn.X()), int32_t(aIn.Y()), int32_t(aIn.Width()),
739 int32_t(aIn.Height()));
740 return gfxRect(aOut->X(), aOut->Y(), aOut->Width(), aOut->Height())
741 .IsEqualEdges(aIn);
744 /* Clamp r to CAIRO_COORD_MIN .. CAIRO_COORD_MAX
745 * these are to be device coordinates.
747 * Cairo is currently using 24.8 fixed point,
748 * so -2^24 .. 2^24-1 is our valid
750 /*static*/
751 void gfxUtils::ConditionRect(gfxRect& aRect) {
752 #define CAIRO_COORD_MAX (16777215.0)
753 #define CAIRO_COORD_MIN (-16777216.0)
754 // if either x or y is way out of bounds;
755 // note that we don't handle negative w/h here
756 if (aRect.X() > CAIRO_COORD_MAX) {
757 aRect.SetRectX(CAIRO_COORD_MAX, 0.0);
760 if (aRect.Y() > CAIRO_COORD_MAX) {
761 aRect.SetRectY(CAIRO_COORD_MAX, 0.0);
764 if (aRect.X() < CAIRO_COORD_MIN) {
765 aRect.SetWidth(aRect.XMost() - CAIRO_COORD_MIN);
766 if (aRect.Width() < 0.0) {
767 aRect.SetWidth(0.0);
769 aRect.MoveToX(CAIRO_COORD_MIN);
772 if (aRect.Y() < CAIRO_COORD_MIN) {
773 aRect.SetHeight(aRect.YMost() - CAIRO_COORD_MIN);
774 if (aRect.Height() < 0.0) {
775 aRect.SetHeight(0.0);
777 aRect.MoveToY(CAIRO_COORD_MIN);
780 if (aRect.XMost() > CAIRO_COORD_MAX) {
781 aRect.SetRightEdge(CAIRO_COORD_MAX);
784 if (aRect.YMost() > CAIRO_COORD_MAX) {
785 aRect.SetBottomEdge(CAIRO_COORD_MAX);
787 #undef CAIRO_COORD_MAX
788 #undef CAIRO_COORD_MIN
791 /*static*/
792 gfxQuad gfxUtils::TransformToQuad(const gfxRect& aRect,
793 const mozilla::gfx::Matrix4x4& aMatrix) {
794 gfxPoint points[4];
796 points[0] = aMatrix.TransformPoint(aRect.TopLeft());
797 points[1] = aMatrix.TransformPoint(aRect.TopRight());
798 points[2] = aMatrix.TransformPoint(aRect.BottomRight());
799 points[3] = aMatrix.TransformPoint(aRect.BottomLeft());
801 // Could this ever result in lines that intersect? I don't think so.
802 return gfxQuad(points[0], points[1], points[2], points[3]);
805 Matrix4x4 gfxUtils::SnapTransformTranslation(const Matrix4x4& aTransform,
806 Matrix* aResidualTransform) {
807 if (aResidualTransform) {
808 *aResidualTransform = Matrix();
811 Matrix matrix2D;
812 if (aTransform.CanDraw2D(&matrix2D) && !matrix2D.HasNonTranslation() &&
813 matrix2D.HasNonIntegerTranslation()) {
814 return Matrix4x4::From2D(
815 SnapTransformTranslation(matrix2D, aResidualTransform));
818 return SnapTransformTranslation3D(aTransform, aResidualTransform);
821 Matrix gfxUtils::SnapTransformTranslation(const Matrix& aTransform,
822 Matrix* aResidualTransform) {
823 if (aResidualTransform) {
824 *aResidualTransform = Matrix();
827 if (!aTransform.HasNonTranslation() &&
828 aTransform.HasNonIntegerTranslation()) {
829 auto snappedTranslation = IntPoint::Round(aTransform.GetTranslation());
830 Matrix snappedMatrix =
831 Matrix::Translation(snappedTranslation.x, snappedTranslation.y);
832 if (aResidualTransform) {
833 // set aResidualTransform so that aResidual * snappedMatrix == matrix2D.
834 // (I.e., appying snappedMatrix after aResidualTransform gives the
835 // ideal transform.)
836 *aResidualTransform =
837 Matrix::Translation(aTransform._31 - snappedTranslation.x,
838 aTransform._32 - snappedTranslation.y);
840 return snappedMatrix;
843 return aTransform;
846 Matrix4x4 gfxUtils::SnapTransformTranslation3D(const Matrix4x4& aTransform,
847 Matrix* aResidualTransform) {
848 if (aTransform.IsSingular() || aTransform.HasPerspectiveComponent() ||
849 aTransform.HasNonTranslation() ||
850 !aTransform.HasNonIntegerTranslation()) {
851 // For a singular transform, there is no reversed matrix, so we
852 // don't snap it.
853 // For a perspective transform, the content is transformed in
854 // non-linear, so we don't snap it too.
855 return aTransform;
858 // Snap for 3D Transforms
860 Point3D transformedOrigin = aTransform.TransformPoint(Point3D());
862 // Compute the transformed snap by rounding the values of
863 // transformed origin.
864 auto transformedSnapXY =
865 IntPoint::Round(transformedOrigin.x, transformedOrigin.y);
866 Matrix4x4 inverse = aTransform;
867 inverse.Invert();
868 // see Matrix4x4::ProjectPoint()
869 Float transformedSnapZ =
870 inverse._33 == 0 ? 0
871 : (-(transformedSnapXY.x * inverse._13 +
872 transformedSnapXY.y * inverse._23 + inverse._43) /
873 inverse._33);
874 Point3D transformedSnap =
875 Point3D(transformedSnapXY.x, transformedSnapXY.y, transformedSnapZ);
876 if (transformedOrigin == transformedSnap) {
877 return aTransform;
880 // Compute the snap from the transformed snap.
881 Point3D snap = inverse.TransformPoint(transformedSnap);
882 if (snap.z > 0.001 || snap.z < -0.001) {
883 // Allow some level of accumulated computation error.
884 MOZ_ASSERT(inverse._33 == 0.0);
885 return aTransform;
888 // The difference between the origin and snap is the residual transform.
889 if (aResidualTransform) {
890 // The residual transform is to translate the snap to the origin
891 // of the content buffer.
892 *aResidualTransform = Matrix::Translation(-snap.x, -snap.y);
895 // Translate transformed origin to transformed snap since the
896 // residual transform would trnslate the snap to the origin.
897 Point3D transformedShift = transformedSnap - transformedOrigin;
898 Matrix4x4 result = aTransform;
899 result.PostTranslate(transformedShift.x, transformedShift.y,
900 transformedShift.z);
902 // For non-2d transform, residual translation could be more than
903 // 0.5 pixels for every axis.
905 return result;
908 Matrix4x4 gfxUtils::SnapTransform(const Matrix4x4& aTransform,
909 const gfxRect& aSnapRect,
910 Matrix* aResidualTransform) {
911 if (aResidualTransform) {
912 *aResidualTransform = Matrix();
915 Matrix matrix2D;
916 if (aTransform.Is2D(&matrix2D)) {
917 return Matrix4x4::From2D(
918 SnapTransform(matrix2D, aSnapRect, aResidualTransform));
920 return aTransform;
923 Matrix gfxUtils::SnapTransform(const Matrix& aTransform,
924 const gfxRect& aSnapRect,
925 Matrix* aResidualTransform) {
926 if (aResidualTransform) {
927 *aResidualTransform = Matrix();
930 if (gfxSize(1.0, 1.0) <= aSnapRect.Size() &&
931 aTransform.PreservesAxisAlignedRectangles()) {
932 auto transformedTopLeft = IntPoint::Round(
933 aTransform.TransformPoint(ToPoint(aSnapRect.TopLeft())));
934 auto transformedTopRight = IntPoint::Round(
935 aTransform.TransformPoint(ToPoint(aSnapRect.TopRight())));
936 auto transformedBottomRight = IntPoint::Round(
937 aTransform.TransformPoint(ToPoint(aSnapRect.BottomRight())));
939 Matrix snappedMatrix = gfxUtils::TransformRectToRect(
940 aSnapRect, transformedTopLeft, transformedTopRight,
941 transformedBottomRight);
943 if (aResidualTransform && !snappedMatrix.IsSingular()) {
944 // set aResidualTransform so that aResidual * snappedMatrix == matrix2D.
945 // (i.e., appying snappedMatrix after aResidualTransform gives the
946 // ideal transform.
947 Matrix snappedMatrixInverse = snappedMatrix;
948 snappedMatrixInverse.Invert();
949 *aResidualTransform = aTransform * snappedMatrixInverse;
951 return snappedMatrix;
953 return aTransform;
956 /* static */
957 void gfxUtils::ClearThebesSurface(gfxASurface* aSurface) {
958 if (aSurface->CairoStatus()) {
959 return;
961 cairo_surface_t* surf = aSurface->CairoSurface();
962 if (cairo_surface_status(surf)) {
963 return;
965 cairo_t* ctx = cairo_create(surf);
966 cairo_set_source_rgba(ctx, 0.0, 0.0, 0.0, 0.0);
967 cairo_set_operator(ctx, CAIRO_OPERATOR_SOURCE);
968 IntRect bounds(nsIntPoint(0, 0), aSurface->GetSize());
969 cairo_rectangle(ctx, bounds.X(), bounds.Y(), bounds.Width(), bounds.Height());
970 cairo_fill(ctx);
971 cairo_destroy(ctx);
974 /* static */
975 already_AddRefed<DataSourceSurface>
976 gfxUtils::CopySurfaceToDataSourceSurfaceWithFormat(SourceSurface* aSurface,
977 SurfaceFormat aFormat) {
978 MOZ_ASSERT(aFormat != aSurface->GetFormat(),
979 "Unnecessary - and very expersive - surface format conversion");
981 Rect bounds(0, 0, aSurface->GetSize().width, aSurface->GetSize().height);
983 if (!aSurface->IsDataSourceSurface()) {
984 // If the surface is NOT of type DATA then its data is not mapped into main
985 // memory. Format conversion is probably faster on the GPU, and by doing it
986 // there we can avoid any expensive uploads/readbacks except for (possibly)
987 // a single readback due to the unavoidable GetDataSurface() call. Using
988 // CreateOffscreenContentDrawTarget ensures the conversion happens on the
989 // GPU.
990 RefPtr<DrawTarget> dt =
991 gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
992 aSurface->GetSize(), aFormat);
993 if (!dt) {
994 gfxWarning() << "gfxUtils::CopySurfaceToDataSourceSurfaceWithFormat "
995 "failed in CreateOffscreenContentDrawTarget";
996 return nullptr;
999 // Using DrawSurface() here rather than CopySurface() because CopySurface
1000 // is optimized for memcpy and therefore isn't good for format conversion.
1001 // Using OP_OVER since in our case it's equivalent to OP_SOURCE and
1002 // generally more optimized.
1003 dt->DrawSurface(aSurface, bounds, bounds, DrawSurfaceOptions(),
1004 DrawOptions(1.0f, CompositionOp::OP_OVER));
1005 RefPtr<SourceSurface> surface = dt->Snapshot();
1006 return surface->GetDataSurface();
1009 // If the surface IS of type DATA then it may or may not be in main memory
1010 // depending on whether or not it has been mapped yet. We have no way of
1011 // knowing, so we can't be sure if it's best to create a data wrapping
1012 // DrawTarget for the conversion or an offscreen content DrawTarget. We could
1013 // guess it's not mapped and create an offscreen content DrawTarget, but if
1014 // it is then we'll end up uploading the surface data, and most likely the
1015 // caller is going to be accessing the resulting surface data, resulting in a
1016 // readback (both very expensive operations). Alternatively we could guess
1017 // the data is mapped and create a data wrapping DrawTarget and, if the
1018 // surface is not in main memory, then we will incure a readback. The latter
1019 // of these two "wrong choices" is the least costly (a readback, vs an
1020 // upload and a readback), and more than likely the DATA surface that we've
1021 // been passed actually IS in main memory anyway. For these reasons it's most
1022 // likely best to create a data wrapping DrawTarget here to do the format
1023 // conversion.
1024 RefPtr<DataSourceSurface> dataSurface =
1025 Factory::CreateDataSourceSurface(aSurface->GetSize(), aFormat);
1026 DataSourceSurface::MappedSurface map;
1027 if (!dataSurface ||
1028 !dataSurface->Map(DataSourceSurface::MapType::READ_WRITE, &map)) {
1029 return nullptr;
1031 RefPtr<DrawTarget> dt = Factory::CreateDrawTargetForData(
1032 BackendType::CAIRO, map.mData, dataSurface->GetSize(), map.mStride,
1033 aFormat);
1034 if (!dt) {
1035 dataSurface->Unmap();
1036 return nullptr;
1038 // Using DrawSurface() here rather than CopySurface() because CopySurface
1039 // is optimized for memcpy and therefore isn't good for format conversion.
1040 // Using OP_OVER since in our case it's equivalent to OP_SOURCE and
1041 // generally more optimized.
1042 dt->DrawSurface(aSurface, bounds, bounds, DrawSurfaceOptions(),
1043 DrawOptions(1.0f, CompositionOp::OP_OVER));
1044 dataSurface->Unmap();
1045 return dataSurface.forget();
1048 const uint32_t gfxUtils::sNumFrameColors = 8;
1050 /* static */
1051 const gfx::DeviceColor& gfxUtils::GetColorForFrameNumber(
1052 uint64_t aFrameNumber) {
1053 static bool initialized = false;
1054 static gfx::DeviceColor colors[sNumFrameColors];
1056 if (!initialized) {
1057 // This isn't truly device color, but it is just for debug.
1058 uint32_t i = 0;
1059 colors[i++] = gfx::DeviceColor::FromABGR(0xffff0000);
1060 colors[i++] = gfx::DeviceColor::FromABGR(0xffcc00ff);
1061 colors[i++] = gfx::DeviceColor::FromABGR(0xff0066cc);
1062 colors[i++] = gfx::DeviceColor::FromABGR(0xff00ff00);
1063 colors[i++] = gfx::DeviceColor::FromABGR(0xff33ffff);
1064 colors[i++] = gfx::DeviceColor::FromABGR(0xffff0099);
1065 colors[i++] = gfx::DeviceColor::FromABGR(0xff0000ff);
1066 colors[i++] = gfx::DeviceColor::FromABGR(0xff999999);
1067 MOZ_ASSERT(i == sNumFrameColors);
1068 initialized = true;
1071 return colors[aFrameNumber % sNumFrameColors];
1074 // static
1075 nsresult gfxUtils::EncodeSourceSurfaceAsStream(SourceSurface* aSurface,
1076 const ImageType aImageType,
1077 const nsAString& aOutputOptions,
1078 nsIInputStream** aOutStream) {
1079 const IntSize size = aSurface->GetSize();
1080 if (size.IsEmpty()) {
1081 return NS_ERROR_FAILURE;
1084 RefPtr<DataSourceSurface> dataSurface;
1085 if (aSurface->GetFormat() != SurfaceFormat::B8G8R8A8) {
1086 // FIXME bug 995807 (B8G8R8X8), bug 831898 (R5G6B5)
1087 dataSurface = gfxUtils::CopySurfaceToDataSourceSurfaceWithFormat(
1088 aSurface, SurfaceFormat::B8G8R8A8);
1089 } else {
1090 dataSurface = aSurface->GetDataSurface();
1092 if (!dataSurface) {
1093 return NS_ERROR_FAILURE;
1096 DataSourceSurface::MappedSurface map;
1097 if (!dataSurface->Map(DataSourceSurface::MapType::READ, &map)) {
1098 return NS_ERROR_FAILURE;
1101 RefPtr<imgIEncoder> encoder = nullptr;
1103 switch (aImageType) {
1104 case ImageType::BMP:
1105 encoder = MakeRefPtr<nsBMPEncoder>();
1106 break;
1108 case ImageType::ICO:
1109 encoder = MakeRefPtr<nsICOEncoder>();
1110 break;
1112 case ImageType::JPEG:
1113 encoder = MakeRefPtr<nsJPEGEncoder>();
1114 break;
1116 case ImageType::PNG:
1117 encoder = MakeRefPtr<nsPNGEncoder>();
1118 break;
1120 default:
1121 break;
1124 MOZ_RELEASE_ASSERT(encoder != nullptr);
1126 nsresult rv = encoder->InitFromData(
1127 map.mData, BufferSizeFromStrideAndHeight(map.mStride, size.height),
1128 size.width, size.height, map.mStride, imgIEncoder::INPUT_FORMAT_HOSTARGB,
1129 aOutputOptions);
1130 dataSurface->Unmap();
1131 if (NS_FAILED(rv)) {
1132 return NS_ERROR_FAILURE;
1135 nsCOMPtr<nsIInputStream> imgStream(encoder);
1136 if (!imgStream) {
1137 return NS_ERROR_FAILURE;
1140 imgStream.forget(aOutStream);
1142 return NS_OK;
1145 // static
1146 Maybe<nsTArray<uint8_t>> gfxUtils::EncodeSourceSurfaceAsBytes(
1147 SourceSurface* aSurface, const ImageType aImageType,
1148 const nsAString& aOutputOptions) {
1149 nsCOMPtr<nsIInputStream> imgStream;
1150 nsresult rv = EncodeSourceSurfaceAsStream(
1151 aSurface, aImageType, aOutputOptions, getter_AddRefs(imgStream));
1152 if (NS_FAILED(rv)) {
1153 return Nothing();
1156 uint64_t bufSize64;
1157 rv = imgStream->Available(&bufSize64);
1158 if (NS_FAILED(rv) || bufSize64 > UINT32_MAX) {
1159 return Nothing();
1162 uint32_t bytesLeft = static_cast<uint32_t>(bufSize64);
1164 nsTArray<uint8_t> imgData;
1165 imgData.SetLength(bytesLeft);
1166 uint8_t* bytePtr = imgData.Elements();
1168 while (bytesLeft > 0) {
1169 uint32_t bytesRead = 0;
1170 rv = imgStream->Read(reinterpret_cast<char*>(bytePtr), bytesLeft,
1171 &bytesRead);
1172 if (NS_FAILED(rv) || bytesRead == 0) {
1173 return Nothing();
1176 bytePtr += bytesRead;
1177 bytesLeft -= bytesRead;
1180 #ifdef DEBUG
1182 // Currently, all implementers of imgIEncoder report their exact size through
1183 // nsIInputStream::Available(), but let's explicitly state that we rely on
1184 // that behavior for the algorithm above.
1186 char dummy = 0;
1187 uint32_t bytesRead = 0;
1188 rv = imgStream->Read(&dummy, 1, &bytesRead);
1189 MOZ_ASSERT(NS_SUCCEEDED(rv) && bytesRead == 0);
1191 #endif
1193 return Some(std::move(imgData));
1196 /* static */
1197 nsresult gfxUtils::EncodeSourceSurface(SourceSurface* aSurface,
1198 const ImageType aImageType,
1199 const nsAString& aOutputOptions,
1200 BinaryOrData aBinaryOrData, FILE* aFile,
1201 nsACString* aStrOut) {
1202 MOZ_ASSERT(aBinaryOrData == gfxUtils::eDataURIEncode || aFile || aStrOut,
1203 "Copying binary encoding to clipboard not currently supported");
1205 auto maybeImgData =
1206 EncodeSourceSurfaceAsBytes(aSurface, aImageType, aOutputOptions);
1207 if (!maybeImgData) {
1208 return NS_ERROR_FAILURE;
1211 nsTArray<uint8_t>& imgData = *maybeImgData;
1213 if (aBinaryOrData == gfxUtils::eBinaryEncode) {
1214 if (aFile) {
1215 Unused << fwrite(imgData.Elements(), 1, imgData.Length(), aFile);
1217 return NS_OK;
1220 nsCString stringBuf;
1221 nsACString& dataURI = aStrOut ? *aStrOut : stringBuf;
1222 dataURI.AppendLiteral("data:");
1224 switch (aImageType) {
1225 case ImageType::BMP:
1226 dataURI.AppendLiteral(IMAGE_BMP);
1227 break;
1229 case ImageType::ICO:
1230 dataURI.AppendLiteral(IMAGE_ICO_MS);
1231 break;
1232 case ImageType::JPEG:
1233 dataURI.AppendLiteral(IMAGE_JPEG);
1234 break;
1236 case ImageType::PNG:
1237 dataURI.AppendLiteral(IMAGE_PNG);
1238 break;
1240 default:
1241 break;
1244 dataURI.AppendLiteral(";base64,");
1245 nsresult rv = Base64EncodeAppend(reinterpret_cast<char*>(imgData.Elements()),
1246 imgData.Length(), dataURI);
1247 NS_ENSURE_SUCCESS(rv, rv);
1249 if (aFile) {
1250 #ifdef ANDROID
1251 if (aFile == stdout || aFile == stderr) {
1252 // ADB logcat cuts off long strings so we will break it down
1253 const char* cStr = dataURI.BeginReading();
1254 size_t len = strlen(cStr);
1255 while (true) {
1256 printf_stderr("IMG: %.140s\n", cStr);
1257 if (len <= 140) break;
1258 len -= 140;
1259 cStr += 140;
1262 #endif
1263 fprintf(aFile, "%s", dataURI.BeginReading());
1264 } else if (!aStrOut) {
1265 nsCOMPtr<nsIClipboardHelper> clipboard(
1266 do_GetService("@mozilla.org/widget/clipboardhelper;1", &rv));
1267 if (clipboard) {
1268 clipboard->CopyString(NS_ConvertASCIItoUTF16(dataURI), nullptr);
1271 return NS_OK;
1274 static nsCString EncodeSourceSurfaceAsPNGURI(SourceSurface* aSurface) {
1275 nsCString string;
1276 gfxUtils::EncodeSourceSurface(aSurface, ImageType::PNG, u""_ns,
1277 gfxUtils::eDataURIEncode, nullptr, &string);
1278 return string;
1281 // https://jdashg.github.io/misc/colors/from-coeffs.html
1282 const float kBT601NarrowYCbCrToRGB_RowMajor[16] = {
1283 1.16438f, 0.00000f, 1.59603f, -0.87420f, 1.16438f, -0.39176f,
1284 -0.81297f, 0.53167f, 1.16438f, 2.01723f, 0.00000f, -1.08563f,
1285 0.00000f, 0.00000f, 0.00000f, 1.00000f};
1286 const float kBT709NarrowYCbCrToRGB_RowMajor[16] = {
1287 1.16438f, 0.00000f, 1.79274f, -0.97295f, 1.16438f, -0.21325f,
1288 -0.53291f, 0.30148f, 1.16438f, 2.11240f, 0.00000f, -1.13340f,
1289 0.00000f, 0.00000f, 0.00000f, 1.00000f};
1290 const float kBT2020NarrowYCbCrToRGB_RowMajor[16] = {
1291 1.16438f, 0.00000f, 1.67867f, -0.91569f, 1.16438f, -0.18733f,
1292 -0.65042f, 0.34746f, 1.16438f, 2.14177f, 0.00000f, -1.14815f,
1293 0.00000f, 0.00000f, 0.00000f, 1.00000f};
1294 const float kIdentityNarrowYCbCrToRGB_RowMajor[16] = {
1295 0.00000f, 0.00000f, 1.00000f, 0.00000f, 1.00000f, 0.00000f,
1296 0.00000f, 0.00000f, 0.00000f, 1.00000f, 0.00000f, 0.00000f,
1297 0.00000f, 0.00000f, 0.00000f, 1.00000f};
1299 /* static */ const float* gfxUtils::YuvToRgbMatrix4x3RowMajor(
1300 gfx::YUVColorSpace aYUVColorSpace) {
1301 #define X(x) \
1302 { x[0], x[1], x[2], 0.0f, x[4], x[5], x[6], 0.0f, x[8], x[9], x[10], 0.0f }
1304 static const float rec601[12] = X(kBT601NarrowYCbCrToRGB_RowMajor);
1305 static const float rec709[12] = X(kBT709NarrowYCbCrToRGB_RowMajor);
1306 static const float rec2020[12] = X(kBT2020NarrowYCbCrToRGB_RowMajor);
1307 static const float identity[12] = X(kIdentityNarrowYCbCrToRGB_RowMajor);
1309 #undef X
1311 switch (aYUVColorSpace) {
1312 case gfx::YUVColorSpace::BT601:
1313 return rec601;
1314 case gfx::YUVColorSpace::BT709:
1315 return rec709;
1316 case gfx::YUVColorSpace::BT2020:
1317 return rec2020;
1318 case gfx::YUVColorSpace::Identity:
1319 return identity;
1320 default:
1321 MOZ_CRASH("Bad YUVColorSpace");
1325 /* static */ const float* gfxUtils::YuvToRgbMatrix3x3ColumnMajor(
1326 gfx::YUVColorSpace aYUVColorSpace) {
1327 #define X(x) \
1328 { x[0], x[4], x[8], x[1], x[5], x[9], x[2], x[6], x[10] }
1330 static const float rec601[9] = X(kBT601NarrowYCbCrToRGB_RowMajor);
1331 static const float rec709[9] = X(kBT709NarrowYCbCrToRGB_RowMajor);
1332 static const float rec2020[9] = X(kBT2020NarrowYCbCrToRGB_RowMajor);
1333 static const float identity[9] = X(kIdentityNarrowYCbCrToRGB_RowMajor);
1335 #undef X
1337 switch (aYUVColorSpace) {
1338 case gfx::YUVColorSpace::BT601:
1339 return rec601;
1340 case YUVColorSpace::BT709:
1341 return rec709;
1342 case YUVColorSpace::BT2020:
1343 return rec2020;
1344 case YUVColorSpace::Identity:
1345 return identity;
1346 default:
1347 MOZ_CRASH("Bad YUVColorSpace");
1351 /* static */ const float* gfxUtils::YuvToRgbMatrix4x4ColumnMajor(
1352 YUVColorSpace aYUVColorSpace) {
1353 #define X(x) \
1355 x[0], x[4], x[8], x[12], x[1], x[5], x[9], x[13], x[2], x[6], x[10], \
1356 x[14], x[3], x[7], x[11], x[15] \
1359 static const float rec601[16] = X(kBT601NarrowYCbCrToRGB_RowMajor);
1360 static const float rec709[16] = X(kBT709NarrowYCbCrToRGB_RowMajor);
1361 static const float rec2020[16] = X(kBT2020NarrowYCbCrToRGB_RowMajor);
1362 static const float identity[16] = X(kIdentityNarrowYCbCrToRGB_RowMajor);
1364 #undef X
1366 switch (aYUVColorSpace) {
1367 case YUVColorSpace::BT601:
1368 return rec601;
1369 case YUVColorSpace::BT709:
1370 return rec709;
1371 case YUVColorSpace::BT2020:
1372 return rec2020;
1373 case YUVColorSpace::Identity:
1374 return identity;
1375 default:
1376 MOZ_CRASH("Bad YUVColorSpace");
1380 // Translate from CICP values to the color spaces we support, or return
1381 // Nothing() if there is no appropriate match to let the caller choose
1382 // a default or generate an error.
1384 // See Rec. ITU-T H.273 (12/2016) for details on CICP
1385 /* static */ Maybe<gfx::YUVColorSpace> gfxUtils::CicpToColorSpace(
1386 const CICP::MatrixCoefficients aMatrixCoefficients,
1387 const CICP::ColourPrimaries aColourPrimaries, LazyLogModule& aLogger) {
1388 switch (aMatrixCoefficients) {
1389 case CICP::MatrixCoefficients::MC_BT2020_NCL:
1390 case CICP::MatrixCoefficients::MC_BT2020_CL:
1391 return Some(gfx::YUVColorSpace::BT2020);
1392 case CICP::MatrixCoefficients::MC_BT601:
1393 return Some(gfx::YUVColorSpace::BT601);
1394 case CICP::MatrixCoefficients::MC_BT709:
1395 return Some(gfx::YUVColorSpace::BT709);
1396 case CICP::MatrixCoefficients::MC_IDENTITY:
1397 return Some(gfx::YUVColorSpace::Identity);
1398 case CICP::MatrixCoefficients::MC_CHROMAT_NCL:
1399 case CICP::MatrixCoefficients::MC_CHROMAT_CL:
1400 case CICP::MatrixCoefficients::MC_UNSPECIFIED:
1401 switch (aColourPrimaries) {
1402 case CICP::ColourPrimaries::CP_BT601:
1403 return Some(gfx::YUVColorSpace::BT601);
1404 case CICP::ColourPrimaries::CP_BT709:
1405 return Some(gfx::YUVColorSpace::BT709);
1406 case CICP::ColourPrimaries::CP_BT2020:
1407 return Some(gfx::YUVColorSpace::BT2020);
1408 default:
1409 MOZ_LOG(aLogger, LogLevel::Debug,
1410 ("Couldn't infer color matrix from primaries: %hhu",
1411 aColourPrimaries));
1412 return {};
1414 default:
1415 MOZ_LOG(aLogger, LogLevel::Debug,
1416 ("Unsupported color matrix value: %hhu", aMatrixCoefficients));
1417 return {};
1421 // Translate from CICP values to the color primaries we support, or return
1422 // Nothing() if there is no appropriate match to let the caller choose
1423 // a default or generate an error.
1425 // See Rec. ITU-T H.273 (12/2016) for details on CICP
1426 /* static */ Maybe<gfx::ColorSpace2> gfxUtils::CicpToColorPrimaries(
1427 const CICP::ColourPrimaries aColourPrimaries, LazyLogModule& aLogger) {
1428 switch (aColourPrimaries) {
1429 case CICP::ColourPrimaries::CP_BT709:
1430 return Some(gfx::ColorSpace2::BT709);
1431 case CICP::ColourPrimaries::CP_BT2020:
1432 return Some(gfx::ColorSpace2::BT2020);
1433 default:
1434 MOZ_LOG(aLogger, LogLevel::Debug,
1435 ("Unsupported color primaries value: %hhu", aColourPrimaries));
1436 return {};
1440 // Translate from CICP values to the transfer functions we support, or return
1441 // Nothing() if there is no appropriate match.
1443 /* static */ Maybe<gfx::TransferFunction> gfxUtils::CicpToTransferFunction(
1444 const CICP::TransferCharacteristics aTransferCharacteristics) {
1445 switch (aTransferCharacteristics) {
1446 case CICP::TransferCharacteristics::TC_BT709:
1447 return Some(gfx::TransferFunction::BT709);
1449 case CICP::TransferCharacteristics::TC_SRGB:
1450 return Some(gfx::TransferFunction::SRGB);
1452 case CICP::TransferCharacteristics::TC_SMPTE2084:
1453 return Some(gfx::TransferFunction::PQ);
1455 case CICP::TransferCharacteristics::TC_HLG:
1456 return Some(gfx::TransferFunction::HLG);
1458 default:
1459 return {};
1463 /* static */
1464 void gfxUtils::WriteAsPNG(SourceSurface* aSurface, const nsAString& aFile) {
1465 WriteAsPNG(aSurface, NS_ConvertUTF16toUTF8(aFile).get());
1468 /* static */
1469 void gfxUtils::WriteAsPNG(SourceSurface* aSurface, const char* aFile) {
1470 FILE* file = fopen(aFile, "wb");
1472 if (!file) {
1473 // Maybe the directory doesn't exist; try creating it, then fopen again.
1474 nsresult rv = NS_ERROR_FAILURE;
1475 nsCOMPtr<nsIFile> comFile = do_CreateInstance("@mozilla.org/file/local;1");
1476 if (comFile) {
1477 NS_ConvertUTF8toUTF16 utf16path((nsDependentCString(aFile)));
1478 rv = comFile->InitWithPath(utf16path);
1479 if (NS_SUCCEEDED(rv)) {
1480 nsCOMPtr<nsIFile> dirPath;
1481 comFile->GetParent(getter_AddRefs(dirPath));
1482 if (dirPath) {
1483 rv = dirPath->Create(nsIFile::DIRECTORY_TYPE, 0777);
1484 if (NS_SUCCEEDED(rv) || rv == NS_ERROR_FILE_ALREADY_EXISTS) {
1485 file = fopen(aFile, "wb");
1490 if (!file) {
1491 NS_WARNING("Failed to open file to create PNG!");
1492 return;
1496 EncodeSourceSurface(aSurface, ImageType::PNG, u""_ns, eBinaryEncode, file);
1497 fclose(file);
1500 /* static */
1501 void gfxUtils::WriteAsPNG(DrawTarget* aDT, const nsAString& aFile) {
1502 WriteAsPNG(aDT, NS_ConvertUTF16toUTF8(aFile).get());
1505 /* static */
1506 void gfxUtils::WriteAsPNG(DrawTarget* aDT, const char* aFile) {
1507 RefPtr<SourceSurface> surface = aDT->Snapshot();
1508 if (surface) {
1509 WriteAsPNG(surface, aFile);
1510 } else {
1511 NS_WARNING("Failed to get surface!");
1515 /* static */
1516 void gfxUtils::DumpAsDataURI(SourceSurface* aSurface, FILE* aFile) {
1517 EncodeSourceSurface(aSurface, ImageType::PNG, u""_ns, eDataURIEncode, aFile);
1520 /* static */
1521 nsCString gfxUtils::GetAsDataURI(SourceSurface* aSurface) {
1522 return EncodeSourceSurfaceAsPNGURI(aSurface);
1525 /* static */
1526 void gfxUtils::DumpAsDataURI(DrawTarget* aDT, FILE* aFile) {
1527 RefPtr<SourceSurface> surface = aDT->Snapshot();
1528 if (surface) {
1529 DumpAsDataURI(surface, aFile);
1530 } else {
1531 NS_WARNING("Failed to get surface!");
1535 /* static */
1536 nsCString gfxUtils::GetAsLZ4Base64Str(DataSourceSurface* aSourceSurface) {
1537 DataSourceSurface::ScopedMap map(aSourceSurface, DataSourceSurface::READ);
1538 int32_t dataSize = aSourceSurface->GetSize().height * map.GetStride();
1539 auto compressedData = MakeUnique<char[]>(LZ4::maxCompressedSize(dataSize));
1540 if (compressedData) {
1541 int nDataSize =
1542 LZ4::compress((char*)map.GetData(), dataSize, compressedData.get());
1543 if (nDataSize > 0) {
1544 nsCString string;
1545 string.AppendPrintf("data:image/lz4bgra;base64,%i,%i,%i,",
1546 aSourceSurface->GetSize().width, map.GetStride(),
1547 aSourceSurface->GetSize().height);
1548 nsresult rv = Base64EncodeAppend(compressedData.get(), nDataSize, string);
1549 if (rv == NS_OK) {
1550 return string;
1554 return {};
1557 /* static */
1558 nsCString gfxUtils::GetAsDataURI(DrawTarget* aDT) {
1559 RefPtr<SourceSurface> surface = aDT->Snapshot();
1560 if (surface) {
1561 return EncodeSourceSurfaceAsPNGURI(surface);
1562 } else {
1563 NS_WARNING("Failed to get surface!");
1564 return nsCString("");
1568 /* static */
1569 void gfxUtils::CopyAsDataURI(SourceSurface* aSurface) {
1570 EncodeSourceSurface(aSurface, ImageType::PNG, u""_ns, eDataURIEncode,
1571 nullptr);
1574 /* static */
1575 void gfxUtils::CopyAsDataURI(DrawTarget* aDT) {
1576 RefPtr<SourceSurface> surface = aDT->Snapshot();
1577 if (surface) {
1578 CopyAsDataURI(surface);
1579 } else {
1580 NS_WARNING("Failed to get surface!");
1584 /* static */
1585 UniquePtr<uint8_t[]> gfxUtils::GetImageBuffer(gfx::DataSourceSurface* aSurface,
1586 bool aIsAlphaPremultiplied,
1587 int32_t* outFormat) {
1588 *outFormat = 0;
1590 DataSourceSurface::MappedSurface map;
1591 if (!aSurface->Map(DataSourceSurface::MapType::READ, &map)) return nullptr;
1593 uint32_t bufferSize =
1594 aSurface->GetSize().width * aSurface->GetSize().height * 4;
1595 auto imageBuffer = MakeUniqueFallible<uint8_t[]>(bufferSize);
1596 if (!imageBuffer) {
1597 aSurface->Unmap();
1598 return nullptr;
1600 memcpy(imageBuffer.get(), map.mData, bufferSize);
1602 aSurface->Unmap();
1604 int32_t format = imgIEncoder::INPUT_FORMAT_HOSTARGB;
1605 if (!aIsAlphaPremultiplied) {
1606 // We need to convert to INPUT_FORMAT_RGBA, otherwise
1607 // we are automatically considered premult, and unpremult'd.
1608 // Yes, it is THAT silly.
1609 // Except for different lossy conversions by color,
1610 // we could probably just change the label, and not change the data.
1611 gfxUtils::ConvertBGRAtoRGBA(imageBuffer.get(), bufferSize);
1612 format = imgIEncoder::INPUT_FORMAT_RGBA;
1615 *outFormat = format;
1616 return imageBuffer;
1619 /* static */
1620 UniquePtr<uint8_t[]> gfxUtils::GetImageBufferWithRandomNoise(
1621 gfx::DataSourceSurface* aSurface, bool aIsAlphaPremultiplied,
1622 nsICookieJarSettings* aCookieJarSettings, int32_t* outFormat) {
1623 UniquePtr<uint8_t[]> imageBuffer =
1624 GetImageBuffer(aSurface, aIsAlphaPremultiplied, outFormat);
1626 nsRFPService::RandomizePixels(
1627 aCookieJarSettings, imageBuffer.get(), aSurface->GetSize().width,
1628 aSurface->GetSize().height,
1629 aSurface->GetSize().width * aSurface->GetSize().height * 4,
1630 SurfaceFormat::A8R8G8B8_UINT32);
1632 return imageBuffer;
1635 /* static */
1636 nsresult gfxUtils::GetInputStream(gfx::DataSourceSurface* aSurface,
1637 bool aIsAlphaPremultiplied,
1638 const char* aMimeType,
1639 const nsAString& aEncoderOptions,
1640 nsIInputStream** outStream) {
1641 nsCString enccid("@mozilla.org/image/encoder;2?type=");
1642 enccid += aMimeType;
1643 nsCOMPtr<imgIEncoder> encoder = do_CreateInstance(enccid.get());
1644 if (!encoder) return NS_ERROR_FAILURE;
1646 int32_t format = 0;
1647 UniquePtr<uint8_t[]> imageBuffer =
1648 GetImageBuffer(aSurface, aIsAlphaPremultiplied, &format);
1649 if (!imageBuffer) return NS_ERROR_FAILURE;
1651 return dom::ImageEncoder::GetInputStream(
1652 aSurface->GetSize().width, aSurface->GetSize().height, imageBuffer.get(),
1653 format, encoder, aEncoderOptions, outStream);
1656 /* static */
1657 nsresult gfxUtils::GetInputStreamWithRandomNoise(
1658 gfx::DataSourceSurface* aSurface, bool aIsAlphaPremultiplied,
1659 const char* aMimeType, const nsAString& aEncoderOptions,
1660 nsICookieJarSettings* aCookieJarSettings, nsIInputStream** outStream) {
1661 nsCString enccid("@mozilla.org/image/encoder;2?type=");
1662 enccid += aMimeType;
1663 nsCOMPtr<imgIEncoder> encoder = do_CreateInstance(enccid.get());
1664 if (!encoder) {
1665 return NS_ERROR_FAILURE;
1668 int32_t format = 0;
1669 UniquePtr<uint8_t[]> imageBuffer =
1670 GetImageBuffer(aSurface, aIsAlphaPremultiplied, &format);
1671 if (!imageBuffer) {
1672 return NS_ERROR_FAILURE;
1675 nsRFPService::RandomizePixels(
1676 aCookieJarSettings, imageBuffer.get(), aSurface->GetSize().width,
1677 aSurface->GetSize().height,
1678 aSurface->GetSize().width * aSurface->GetSize().height * 4,
1679 SurfaceFormat::A8R8G8B8_UINT32);
1681 return dom::ImageEncoder::GetInputStream(
1682 aSurface->GetSize().width, aSurface->GetSize().height, imageBuffer.get(),
1683 format, encoder, aEncoderOptions, outStream);
1686 class GetFeatureStatusWorkerRunnable final
1687 : public dom::WorkerMainThreadRunnable {
1688 public:
1689 GetFeatureStatusWorkerRunnable(dom::WorkerPrivate* workerPrivate,
1690 const nsCOMPtr<nsIGfxInfo>& gfxInfo,
1691 int32_t feature, nsACString& failureId,
1692 int32_t* status)
1693 : WorkerMainThreadRunnable(workerPrivate, "GFX :: GetFeatureStatus"_ns),
1694 mGfxInfo(gfxInfo),
1695 mFeature(feature),
1696 mStatus(status),
1697 mFailureId(failureId),
1698 mNSResult(NS_OK) {}
1700 bool MainThreadRun() override {
1701 if (mGfxInfo) {
1702 mNSResult = mGfxInfo->GetFeatureStatus(mFeature, mFailureId, mStatus);
1704 return true;
1707 nsresult GetNSResult() const { return mNSResult; }
1709 protected:
1710 ~GetFeatureStatusWorkerRunnable() = default;
1712 private:
1713 nsCOMPtr<nsIGfxInfo> mGfxInfo;
1714 int32_t mFeature;
1715 int32_t* mStatus;
1716 nsACString& mFailureId;
1717 nsresult mNSResult;
1720 #define GFX_SHADER_CHECK_BUILD_VERSION_PREF "gfx-shader-check.build-version"
1721 #define GFX_SHADER_CHECK_PTR_SIZE_PREF "gfx-shader-check.ptr-size"
1722 #define GFX_SHADER_CHECK_DEVICE_ID_PREF "gfx-shader-check.device-id"
1723 #define GFX_SHADER_CHECK_DRIVER_VERSION_PREF "gfx-shader-check.driver-version"
1725 /* static */
1726 void gfxUtils::RemoveShaderCacheFromDiskIfNecessary() {
1727 if (!gfxVars::UseWebRenderProgramBinaryDisk()) {
1728 return;
1731 nsCOMPtr<nsIGfxInfo> gfxInfo = components::GfxInfo::Service();
1733 // Get current values
1734 nsCString buildID(mozilla::PlatformBuildID());
1735 int ptrSize = sizeof(void*);
1736 nsString deviceID, driverVersion;
1737 gfxInfo->GetAdapterDeviceID(deviceID);
1738 gfxInfo->GetAdapterDriverVersion(driverVersion);
1740 // Get pref stored values
1741 nsAutoCString buildIDChecked;
1742 Preferences::GetCString(GFX_SHADER_CHECK_BUILD_VERSION_PREF, buildIDChecked);
1743 int ptrSizeChecked = Preferences::GetInt(GFX_SHADER_CHECK_PTR_SIZE_PREF, 0);
1744 nsAutoString deviceIDChecked, driverVersionChecked;
1745 Preferences::GetString(GFX_SHADER_CHECK_DEVICE_ID_PREF, deviceIDChecked);
1746 Preferences::GetString(GFX_SHADER_CHECK_DRIVER_VERSION_PREF,
1747 driverVersionChecked);
1749 if (buildID == buildIDChecked && ptrSize == ptrSizeChecked &&
1750 deviceID == deviceIDChecked && driverVersion == driverVersionChecked) {
1751 return;
1754 nsAutoString path(gfx::gfxVars::ProfDirectory());
1756 if (!wr::remove_program_binary_disk_cache(&path)) {
1757 // Failed to remove program binary disk cache. The disk cache might have
1758 // invalid data. Disable program binary disk cache usage.
1759 gfxVars::SetUseWebRenderProgramBinaryDisk(false);
1760 return;
1763 Preferences::SetCString(GFX_SHADER_CHECK_BUILD_VERSION_PREF, buildID);
1764 Preferences::SetInt(GFX_SHADER_CHECK_PTR_SIZE_PREF, ptrSize);
1765 Preferences::SetString(GFX_SHADER_CHECK_DEVICE_ID_PREF, deviceID);
1766 Preferences::SetString(GFX_SHADER_CHECK_DRIVER_VERSION_PREF, driverVersion);
1769 /* static */
1770 bool gfxUtils::DumpDisplayList() {
1771 return StaticPrefs::layout_display_list_dump() ||
1772 (StaticPrefs::layout_display_list_dump_parent() &&
1773 XRE_IsParentProcess()) ||
1774 (StaticPrefs::layout_display_list_dump_content() &&
1775 XRE_IsContentProcess());
1778 FILE* gfxUtils::sDumpPaintFile = stderr;
1780 namespace mozilla {
1781 namespace gfx {
1783 DeviceColor ToDeviceColor(const sRGBColor& aColor) {
1784 // aColor is pass-by-value since to get return value optimization goodness we
1785 // need to return the same object from all return points in this function. We
1786 // could declare a local Color variable and use that, but we might as well
1787 // just use aColor.
1788 if (gfxPlatform::GetCMSMode() == CMSMode::All) {
1789 qcms_transform* transform = gfxPlatform::GetCMSRGBTransform();
1790 if (transform) {
1791 return gfxPlatform::TransformPixel(aColor, transform);
1792 // Use the original alpha to avoid unnecessary float->byte->float
1793 // conversion errors
1796 return DeviceColor(aColor.r, aColor.g, aColor.b, aColor.a);
1799 DeviceColor ToDeviceColor(nscolor aColor) {
1800 return ToDeviceColor(sRGBColor::FromABGR(aColor));
1803 DeviceColor ToDeviceColor(const StyleAbsoluteColor& aColor) {
1804 return ToDeviceColor(aColor.ToColor());
1807 sRGBColor ToSRGBColor(const StyleAbsoluteColor& aColor) {
1808 auto srgb = aColor.ToColorSpace(StyleColorSpace::Srgb);
1810 const auto ToComponent = [](float aF) -> float {
1811 float component = std::min(std::max(0.0f, aF), 1.0f);
1812 if (MOZ_UNLIKELY(!std::isfinite(component))) {
1813 return 0.0f;
1815 return component;
1817 return {ToComponent(srgb.components._0), ToComponent(srgb.components._1),
1818 ToComponent(srgb.components._2), ToComponent(srgb.alpha)};
1821 } // namespace gfx
1822 } // namespace mozilla