Bug 1765187 Part 1: Track color primaries independently from coefficients. r=media...
[gecko.git] / gfx / thebes / gfxUtils.cpp
blob921bb0407eb6cf1bdbf2373a95ffecea09b3d719
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/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"
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 "ImageContainer.h"
55 #include "ImageRegion.h"
56 #include "gfx2DGlue.h"
58 #ifdef XP_WIN
59 # include "gfxWindowsPlatform.h"
60 #endif
62 using namespace mozilla;
63 using namespace mozilla::image;
64 using namespace mozilla::layers;
65 using namespace mozilla::gfx;
67 #undef compress
68 #include "mozilla/Compression.h"
70 using namespace mozilla::Compression;
71 extern "C" {
73 /**
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.
81 NS_EXPORT
82 void mozilla_dump_image(void* bytes, int width, int height, int bytepp,
83 int strideBytes) {
84 if (0 == strideBytes) {
85 strideBytes = width * bytepp;
87 SurfaceFormat format;
88 // TODO more flexible; parse string?
89 switch (bytepp) {
90 case 2:
91 format = SurfaceFormat::R5G6B5_UINT16;
92 break;
93 case 4:
94 default:
95 format = SurfaceFormat::R8G8B8A8;
96 break;
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.");
113 return false;
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.");
120 return false;
123 *out_srcMap = map;
124 *out_destMap = map;
125 return true;
128 // Map src for reading.
129 DataSourceSurface::MappedSurface srcMap;
130 if (!srcSurf->Map(DataSourceSurface::MapType::READ, &srcMap)) {
131 NS_WARNING("Couldn't Map srcSurf.");
132 return false;
135 // Map dest for writing.
136 DataSourceSurface::MappedSurface destMap;
137 if (!destSurf->Map(DataSourceSurface::MapType::WRITE, &destMap)) {
138 NS_WARNING("Couldn't Map aDest.");
139 srcSurf->Unmap();
140 return false;
143 *out_srcMap = srcMap;
144 *out_destMap = destMap;
145 return true;
148 static void UnmapSrcDest(DataSourceSurface* srcSurf,
149 DataSourceSurface* destSurf) {
150 if (srcSurf == destSurf) {
151 srcSurf->Unmap();
152 } else {
153 srcSurf->Unmap();
154 destSurf->Unmap();
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(),
168 srcSurf->GetSize());
170 UnmapSrcDest(srcSurf, destSurf);
171 return true;
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(),
184 srcSurf->GetSize());
186 UnmapSrcDest(srcSurf, destSurf);
187 return true;
190 static bool MapSrcAndCreateMappedDest(
191 DataSourceSurface* srcSurf, RefPtr<DataSourceSurface>* out_destSurf,
192 DataSourceSurface::MappedSurface* out_srcMap,
193 DataSourceSurface::MappedSurface* out_destMap) {
194 MOZ_ASSERT(srcSurf);
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.");
201 return false;
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)) {
209 return false;
212 DataSourceSurface::MappedSurface destMap;
213 if (!destSurf->Map(DataSourceSurface::MapType::WRITE, &destMap)) {
214 MOZ_ASSERT(false, "Couldn't Map destSurf.");
215 srcSurf->Unmap();
216 return false;
219 *out_destSurf = destSurf;
220 *out_srcMap = srcMap;
221 *out_destMap = destMap;
222 return true;
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(),
238 srcSurf->GetSize());
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(),
257 srcSurf->GetSize());
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() {
276 # ifdef XP_WIN
277 if (gfxWindowsPlatform::GetPlatform()->IsDirect2DBackend()) {
278 // D2D -really- hates operator source.
279 return CompositionOp::OP_OVER;
281 # endif
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
295 // rid of.
296 if (destDrawTarget->GetBackendType() == BackendType::DIRECT2D1_1 || (true)) {
297 return nullptr;
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);
307 needed.RoundOut();
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,
319 aFormat);
320 if (!target || !target->IsValid()) {
321 return nullptr;
324 RefPtr<gfxContext> tmpCtx = gfxContext::CreateOrNull(target);
325 MOZ_ASSERT(tmpCtx); // already checked the target above
327 if (aUseOptimalFillOp) {
328 tmpCtx->SetOp(OptimalFillOp());
330 aDrawable->Draw(tmpCtx, needed - needed.TopLeft(), ExtendMode::REPEAT,
331 SamplingFilter::LINEAR, 1.0,
332 gfxMatrix::Translation(needed.TopLeft()));
333 RefPtr<SourceSurface> surface = target->Snapshot();
335 RefPtr<gfxDrawable> drawable = new gfxSurfaceDrawable(
336 surface, size, gfxMatrix::Translation(-needed.TopLeft()));
337 return drawable.forget();
339 #endif // !MOZ_GFX_OPTIMIZE_MOBILE
341 /* These heuristics are based on
342 * Source/WebCore/platform/graphics/skia/ImageSkia.cpp:computeResamplingMode()
344 #ifdef MOZ_GFX_OPTIMIZE_MOBILE
345 static SamplingFilter ReduceResamplingFilter(SamplingFilter aSamplingFilter,
346 int aImgWidth, int aImgHeight,
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 RefPtr<gfxContext> tmpCtx = gfxContext::CreateOrNull(scaledDT);
478 MOZ_ASSERT(tmpCtx); // already checked the target above
480 scaledDT->SetTransform(scaleMatrix);
481 gfxRect gfxImageRect(aImageRect.x, aImageRect.y, aImageRect.width,
482 aImageRect.height);
484 // Since this is just the scaled image, we don't want to repeat anything yet.
485 aDrawable->Draw(tmpCtx, gfxImageRect, ExtendMode::CLAMP, aSamplingFilter, 1.0,
486 gfxMatrix());
488 RefPtr<SourceSurface> scaledImage = scaledDT->Snapshot();
491 gfxContextMatrixAutoSaveRestore autoSR(aContext);
492 Matrix withoutScale = aContext->CurrentMatrix();
493 DrawTarget* destDrawTarget = aContext->GetDrawTarget();
495 // The translation still is in scaled units
496 withoutScale.PreScale(1.0f / scaleFactor.xScale, 1.0f / scaleFactor.yScale);
497 aContext->SetMatrix(withoutScale);
499 DrawOptions drawOptions(aOpacity, aContext->CurrentOp(),
500 aContext->CurrentAntialiasMode());
502 SurfacePattern scaledImagePattern(scaledImage, aExtendMode, Matrix(),
503 aSamplingFilter);
504 destDrawTarget->FillRect(scaledNeededRect, scaledImagePattern, drawOptions);
506 return true;
508 #endif // MOZ_WIDGET_COCOA
510 /* static */
511 void gfxUtils::DrawPixelSnapped(gfxContext* aContext, gfxDrawable* aDrawable,
512 const gfxSize& aImageSize,
513 const ImageRegion& aRegion,
514 const SurfaceFormat aFormat,
515 SamplingFilter aSamplingFilter,
516 uint32_t aImageFlags, gfxFloat aOpacity,
517 bool aUseOptimalFillOp) {
518 AUTO_PROFILER_LABEL("gfxUtils::DrawPixelSnapped", GRAPHICS);
520 gfxRect imageRect(gfxPoint(0, 0), aImageSize);
521 gfxRect region(aRegion.Rect());
522 ExtendMode extendMode = aRegion.GetExtendMode();
524 RefPtr<gfxDrawable> drawable = aDrawable;
526 aSamplingFilter = ReduceResamplingFilter(aSamplingFilter, imageRect.Width(),
527 imageRect.Height(), region.Width(),
528 region.Height());
530 // OK now, the hard part left is to account for the subimage sampling
531 // restriction. If all the transforms involved are just integer
532 // translations, then we assume no resampling will occur so there's
533 // nothing to do.
534 // XXX if only we had source-clipping in cairo!
536 if (aContext->CurrentMatrix().HasNonIntegerTranslation()) {
537 if ((extendMode != ExtendMode::CLAMP) ||
538 !aRegion.RestrictionContains(imageRect)) {
539 if (drawable->DrawWithSamplingRect(
540 aContext->GetDrawTarget(), aContext->CurrentOp(),
541 aContext->CurrentAntialiasMode(), aRegion.Rect(),
542 aRegion.Restriction(), extendMode, aSamplingFilter, aOpacity)) {
543 return;
546 #ifdef MOZ_WIDGET_COCOA
547 if (PrescaleAndTileDrawable(aDrawable, aContext, aRegion,
548 ToRect(imageRect), aSamplingFilter, aFormat,
549 aOpacity, extendMode)) {
550 return;
552 #endif
554 // On Mobile, we don't ever want to do this; it has the potential for
555 // allocating very large temporary surfaces, especially since we'll
556 // do full-page snapshots often (see bug 749426).
557 #if !defined(MOZ_GFX_OPTIMIZE_MOBILE)
558 RefPtr<gfxDrawable> restrictedDrawable = CreateSamplingRestrictedDrawable(
559 aDrawable, aContext, aRegion, aFormat, aUseOptimalFillOp);
560 if (restrictedDrawable) {
561 drawable.swap(restrictedDrawable);
563 // We no longer need to tile: Either we never needed to, or we already
564 // filled a surface with the tiled pattern; this surface can now be
565 // drawn without tiling.
566 extendMode = ExtendMode::CLAMP;
568 #endif
572 drawable->Draw(aContext, aRegion.Rect(), extendMode, aSamplingFilter,
573 aOpacity, gfxMatrix());
576 /* static */
577 int gfxUtils::ImageFormatToDepth(gfxImageFormat aFormat) {
578 switch (aFormat) {
579 case SurfaceFormat::A8R8G8B8_UINT32:
580 return 32;
581 case SurfaceFormat::X8R8G8B8_UINT32:
582 return 24;
583 case SurfaceFormat::R5G6B5_UINT16:
584 return 16;
585 default:
586 break;
588 return 0;
591 /*static*/
592 void gfxUtils::ClipToRegion(gfxContext* aContext, const nsIntRegion& aRegion) {
593 aContext->NewPath();
594 for (auto iter = aRegion.RectIter(); !iter.Done(); iter.Next()) {
595 const IntRect& r = iter.Get();
596 aContext->Rectangle(gfxRect(r.X(), r.Y(), r.Width(), r.Height()));
598 aContext->Clip();
601 /*static*/
602 void gfxUtils::ClipToRegion(DrawTarget* aTarget, const nsIntRegion& aRegion) {
603 uint32_t numRects = aRegion.GetNumRects();
604 // If there is only one rect, then the region bounds are equivalent to the
605 // contents. So just use push a single clip rect with the bounds.
606 if (numRects == 1) {
607 aTarget->PushClipRect(Rect(aRegion.GetBounds()));
608 return;
611 // Check if the target's transform will preserve axis-alignment and
612 // pixel-alignment for each rect. For now, just handle the common case
613 // of integer translations.
614 Matrix transform = aTarget->GetTransform();
615 if (transform.IsIntegerTranslation()) {
616 IntPoint translation = RoundedToInt(transform.GetTranslation());
617 AutoTArray<IntRect, 16> rects;
618 rects.SetLength(numRects);
619 uint32_t i = 0;
620 // Build the list of transformed rects by adding in the translation.
621 for (auto iter = aRegion.RectIter(); !iter.Done(); iter.Next()) {
622 IntRect rect = iter.Get();
623 rect.MoveBy(translation);
624 rects[i++] = rect;
626 aTarget->PushDeviceSpaceClipRects(rects.Elements(), rects.Length());
627 } else {
628 // The transform does not produce axis-aligned rects or a rect was not
629 // pixel-aligned. So just build a path with all the rects and clip to it
630 // instead.
631 RefPtr<PathBuilder> pathBuilder = aTarget->CreatePathBuilder();
632 for (auto iter = aRegion.RectIter(); !iter.Done(); iter.Next()) {
633 AppendRectToPath(pathBuilder, Rect(iter.Get()));
635 RefPtr<Path> path = pathBuilder->Finish();
636 aTarget->PushClip(path);
640 /*static*/
641 float gfxUtils::ClampToScaleFactor(float aVal, bool aRoundDown) {
642 // Arbitary scale factor limitation. We can increase this
643 // for better scaling performance at the cost of worse
644 // quality.
645 static const float kScaleResolution = 2;
647 // Negative scaling is just a flip and irrelevant to
648 // our resolution calculation.
649 if (aVal < 0.0) {
650 aVal = -aVal;
653 bool inverse = false;
654 if (aVal < 1.0) {
655 inverse = true;
656 aVal = 1 / aVal;
659 float power = logf(aVal) / logf(kScaleResolution);
661 // If power is within 1e-5 of an integer, round to nearest to
662 // prevent floating point errors, otherwise round up to the
663 // next integer value.
664 if (fabs(power - NS_round(power)) < 1e-5) {
665 power = NS_round(power);
666 // Use floor when we are either inverted or rounding down, but
667 // not both.
668 } else if (inverse != aRoundDown) {
669 power = floor(power);
670 // Otherwise, ceil when we are not inverted and not rounding
671 // down, or we are inverted and rounding down.
672 } else {
673 power = ceil(power);
676 float scale = powf(kScaleResolution, power);
678 if (inverse) {
679 scale = 1 / scale;
682 return scale;
685 gfxMatrix gfxUtils::TransformRectToRect(const gfxRect& aFrom,
686 const gfxPoint& aToTopLeft,
687 const gfxPoint& aToTopRight,
688 const gfxPoint& aToBottomRight) {
689 gfxMatrix m;
690 if (aToTopRight.y == aToTopLeft.y && aToTopRight.x == aToBottomRight.x) {
691 // Not a rotation, so xy and yx are zero
692 m._21 = m._12 = 0.0;
693 m._11 = (aToBottomRight.x - aToTopLeft.x) / aFrom.Width();
694 m._22 = (aToBottomRight.y - aToTopLeft.y) / aFrom.Height();
695 m._31 = aToTopLeft.x - m._11 * aFrom.X();
696 m._32 = aToTopLeft.y - m._22 * aFrom.Y();
697 } else {
698 NS_ASSERTION(
699 aToTopRight.y == aToBottomRight.y && aToTopRight.x == aToTopLeft.x,
700 "Destination rectangle not axis-aligned");
701 m._11 = m._22 = 0.0;
702 m._21 = (aToBottomRight.x - aToTopLeft.x) / aFrom.Height();
703 m._12 = (aToBottomRight.y - aToTopLeft.y) / aFrom.Width();
704 m._31 = aToTopLeft.x - m._21 * aFrom.Y();
705 m._32 = aToTopLeft.y - m._12 * aFrom.X();
707 return m;
710 Matrix gfxUtils::TransformRectToRect(const gfxRect& aFrom,
711 const IntPoint& aToTopLeft,
712 const IntPoint& aToTopRight,
713 const IntPoint& aToBottomRight) {
714 Matrix m;
715 if (aToTopRight.y == aToTopLeft.y && aToTopRight.x == aToBottomRight.x) {
716 // Not a rotation, so xy and yx are zero
717 m._12 = m._21 = 0.0;
718 m._11 = (aToBottomRight.x - aToTopLeft.x) / aFrom.Width();
719 m._22 = (aToBottomRight.y - aToTopLeft.y) / aFrom.Height();
720 m._31 = aToTopLeft.x - m._11 * aFrom.X();
721 m._32 = aToTopLeft.y - m._22 * aFrom.Y();
722 } else {
723 NS_ASSERTION(
724 aToTopRight.y == aToBottomRight.y && aToTopRight.x == aToTopLeft.x,
725 "Destination rectangle not axis-aligned");
726 m._11 = m._22 = 0.0;
727 m._21 = (aToBottomRight.x - aToTopLeft.x) / aFrom.Height();
728 m._12 = (aToBottomRight.y - aToTopLeft.y) / aFrom.Width();
729 m._31 = aToTopLeft.x - m._21 * aFrom.Y();
730 m._32 = aToTopLeft.y - m._12 * aFrom.X();
732 return m;
735 /* This function is sort of shitty. We truncate doubles
736 * to ints then convert those ints back to doubles to make sure that
737 * they equal the doubles that we got in. */
738 bool gfxUtils::GfxRectToIntRect(const gfxRect& aIn, IntRect* aOut) {
739 *aOut = IntRect(int32_t(aIn.X()), int32_t(aIn.Y()), int32_t(aIn.Width()),
740 int32_t(aIn.Height()));
741 return gfxRect(aOut->X(), aOut->Y(), aOut->Width(), aOut->Height())
742 .IsEqualEdges(aIn);
745 /* Clamp r to CAIRO_COORD_MIN .. CAIRO_COORD_MAX
746 * these are to be device coordinates.
748 * Cairo is currently using 24.8 fixed point,
749 * so -2^24 .. 2^24-1 is our valid
751 /*static*/
752 void gfxUtils::ConditionRect(gfxRect& aRect) {
753 #define CAIRO_COORD_MAX (16777215.0)
754 #define CAIRO_COORD_MIN (-16777216.0)
755 // if either x or y is way out of bounds;
756 // note that we don't handle negative w/h here
757 if (aRect.X() > CAIRO_COORD_MAX) {
758 aRect.SetRectX(CAIRO_COORD_MAX, 0.0);
761 if (aRect.Y() > CAIRO_COORD_MAX) {
762 aRect.SetRectY(CAIRO_COORD_MAX, 0.0);
765 if (aRect.X() < CAIRO_COORD_MIN) {
766 aRect.SetWidth(aRect.XMost() - CAIRO_COORD_MIN);
767 if (aRect.Width() < 0.0) {
768 aRect.SetWidth(0.0);
770 aRect.MoveToX(CAIRO_COORD_MIN);
773 if (aRect.Y() < CAIRO_COORD_MIN) {
774 aRect.SetHeight(aRect.YMost() - CAIRO_COORD_MIN);
775 if (aRect.Height() < 0.0) {
776 aRect.SetHeight(0.0);
778 aRect.MoveToY(CAIRO_COORD_MIN);
781 if (aRect.XMost() > CAIRO_COORD_MAX) {
782 aRect.SetRightEdge(CAIRO_COORD_MAX);
785 if (aRect.YMost() > CAIRO_COORD_MAX) {
786 aRect.SetBottomEdge(CAIRO_COORD_MAX);
788 #undef CAIRO_COORD_MAX
789 #undef CAIRO_COORD_MIN
792 /*static*/
793 gfxQuad gfxUtils::TransformToQuad(const gfxRect& aRect,
794 const mozilla::gfx::Matrix4x4& aMatrix) {
795 gfxPoint points[4];
797 points[0] = aMatrix.TransformPoint(aRect.TopLeft());
798 points[1] = aMatrix.TransformPoint(aRect.TopRight());
799 points[2] = aMatrix.TransformPoint(aRect.BottomRight());
800 points[3] = aMatrix.TransformPoint(aRect.BottomLeft());
802 // Could this ever result in lines that intersect? I don't think so.
803 return gfxQuad(points[0], points[1], points[2], points[3]);
806 Matrix4x4 gfxUtils::SnapTransformTranslation(const Matrix4x4& aTransform,
807 Matrix* aResidualTransform) {
808 if (aResidualTransform) {
809 *aResidualTransform = Matrix();
812 Matrix matrix2D;
813 if (aTransform.CanDraw2D(&matrix2D) && !matrix2D.HasNonTranslation() &&
814 matrix2D.HasNonIntegerTranslation()) {
815 return Matrix4x4::From2D(
816 SnapTransformTranslation(matrix2D, aResidualTransform));
819 return SnapTransformTranslation3D(aTransform, aResidualTransform);
822 Matrix gfxUtils::SnapTransformTranslation(const Matrix& aTransform,
823 Matrix* aResidualTransform) {
824 if (aResidualTransform) {
825 *aResidualTransform = Matrix();
828 if (!aTransform.HasNonTranslation() &&
829 aTransform.HasNonIntegerTranslation()) {
830 auto snappedTranslation = IntPoint::Round(aTransform.GetTranslation());
831 Matrix snappedMatrix =
832 Matrix::Translation(snappedTranslation.x, snappedTranslation.y);
833 if (aResidualTransform) {
834 // set aResidualTransform so that aResidual * snappedMatrix == matrix2D.
835 // (I.e., appying snappedMatrix after aResidualTransform gives the
836 // ideal transform.)
837 *aResidualTransform =
838 Matrix::Translation(aTransform._31 - snappedTranslation.x,
839 aTransform._32 - snappedTranslation.y);
841 return snappedMatrix;
844 return aTransform;
847 Matrix4x4 gfxUtils::SnapTransformTranslation3D(const Matrix4x4& aTransform,
848 Matrix* aResidualTransform) {
849 if (aTransform.IsSingular() || aTransform.HasPerspectiveComponent() ||
850 aTransform.HasNonTranslation() ||
851 !aTransform.HasNonIntegerTranslation()) {
852 // For a singular transform, there is no reversed matrix, so we
853 // don't snap it.
854 // For a perspective transform, the content is transformed in
855 // non-linear, so we don't snap it too.
856 return aTransform;
859 // Snap for 3D Transforms
861 Point3D transformedOrigin = aTransform.TransformPoint(Point3D());
863 // Compute the transformed snap by rounding the values of
864 // transformed origin.
865 auto transformedSnapXY =
866 IntPoint::Round(transformedOrigin.x, transformedOrigin.y);
867 Matrix4x4 inverse = aTransform;
868 inverse.Invert();
869 // see Matrix4x4::ProjectPoint()
870 Float transformedSnapZ =
871 inverse._33 == 0 ? 0
872 : (-(transformedSnapXY.x * inverse._13 +
873 transformedSnapXY.y * inverse._23 + inverse._43) /
874 inverse._33);
875 Point3D transformedSnap =
876 Point3D(transformedSnapXY.x, transformedSnapXY.y, transformedSnapZ);
877 if (transformedOrigin == transformedSnap) {
878 return aTransform;
881 // Compute the snap from the transformed snap.
882 Point3D snap = inverse.TransformPoint(transformedSnap);
883 if (snap.z > 0.001 || snap.z < -0.001) {
884 // Allow some level of accumulated computation error.
885 MOZ_ASSERT(inverse._33 == 0.0);
886 return aTransform;
889 // The difference between the origin and snap is the residual transform.
890 if (aResidualTransform) {
891 // The residual transform is to translate the snap to the origin
892 // of the content buffer.
893 *aResidualTransform = Matrix::Translation(-snap.x, -snap.y);
896 // Translate transformed origin to transformed snap since the
897 // residual transform would trnslate the snap to the origin.
898 Point3D transformedShift = transformedSnap - transformedOrigin;
899 Matrix4x4 result = aTransform;
900 result.PostTranslate(transformedShift.x, transformedShift.y,
901 transformedShift.z);
903 // For non-2d transform, residual translation could be more than
904 // 0.5 pixels for every axis.
906 return result;
909 Matrix4x4 gfxUtils::SnapTransform(const Matrix4x4& aTransform,
910 const gfxRect& aSnapRect,
911 Matrix* aResidualTransform) {
912 if (aResidualTransform) {
913 *aResidualTransform = Matrix();
916 Matrix matrix2D;
917 if (aTransform.Is2D(&matrix2D)) {
918 return Matrix4x4::From2D(
919 SnapTransform(matrix2D, aSnapRect, aResidualTransform));
921 return aTransform;
924 Matrix gfxUtils::SnapTransform(const Matrix& aTransform,
925 const gfxRect& aSnapRect,
926 Matrix* aResidualTransform) {
927 if (aResidualTransform) {
928 *aResidualTransform = Matrix();
931 if (gfxSize(1.0, 1.0) <= aSnapRect.Size() &&
932 aTransform.PreservesAxisAlignedRectangles()) {
933 auto transformedTopLeft = IntPoint::Round(
934 aTransform.TransformPoint(ToPoint(aSnapRect.TopLeft())));
935 auto transformedTopRight = IntPoint::Round(
936 aTransform.TransformPoint(ToPoint(aSnapRect.TopRight())));
937 auto transformedBottomRight = IntPoint::Round(
938 aTransform.TransformPoint(ToPoint(aSnapRect.BottomRight())));
940 Matrix snappedMatrix = gfxUtils::TransformRectToRect(
941 aSnapRect, transformedTopLeft, transformedTopRight,
942 transformedBottomRight);
944 if (aResidualTransform && !snappedMatrix.IsSingular()) {
945 // set aResidualTransform so that aResidual * snappedMatrix == matrix2D.
946 // (i.e., appying snappedMatrix after aResidualTransform gives the
947 // ideal transform.
948 Matrix snappedMatrixInverse = snappedMatrix;
949 snappedMatrixInverse.Invert();
950 *aResidualTransform = aTransform * snappedMatrixInverse;
952 return snappedMatrix;
954 return aTransform;
957 /* static */
958 void gfxUtils::ClearThebesSurface(gfxASurface* aSurface) {
959 if (aSurface->CairoStatus()) {
960 return;
962 cairo_surface_t* surf = aSurface->CairoSurface();
963 if (cairo_surface_status(surf)) {
964 return;
966 cairo_t* ctx = cairo_create(surf);
967 cairo_set_source_rgba(ctx, 0.0, 0.0, 0.0, 0.0);
968 cairo_set_operator(ctx, CAIRO_OPERATOR_SOURCE);
969 IntRect bounds(nsIntPoint(0, 0), aSurface->GetSize());
970 cairo_rectangle(ctx, bounds.X(), bounds.Y(), bounds.Width(), bounds.Height());
971 cairo_fill(ctx);
972 cairo_destroy(ctx);
975 /* static */
976 already_AddRefed<DataSourceSurface>
977 gfxUtils::CopySurfaceToDataSourceSurfaceWithFormat(SourceSurface* aSurface,
978 SurfaceFormat aFormat) {
979 MOZ_ASSERT(aFormat != aSurface->GetFormat(),
980 "Unnecessary - and very expersive - surface format conversion");
982 Rect bounds(0, 0, aSurface->GetSize().width, aSurface->GetSize().height);
984 if (!aSurface->IsDataSourceSurface()) {
985 // If the surface is NOT of type DATA then its data is not mapped into main
986 // memory. Format conversion is probably faster on the GPU, and by doing it
987 // there we can avoid any expensive uploads/readbacks except for (possibly)
988 // a single readback due to the unavoidable GetDataSurface() call. Using
989 // CreateOffscreenContentDrawTarget ensures the conversion happens on the
990 // GPU.
991 RefPtr<DrawTarget> dt =
992 gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
993 aSurface->GetSize(), aFormat);
994 if (!dt) {
995 gfxWarning() << "gfxUtils::CopySurfaceToDataSourceSurfaceWithFormat "
996 "failed in CreateOffscreenContentDrawTarget";
997 return nullptr;
1000 // Using DrawSurface() here rather than CopySurface() because CopySurface
1001 // is optimized for memcpy and therefore isn't good for format conversion.
1002 // Using OP_OVER since in our case it's equivalent to OP_SOURCE and
1003 // generally more optimized.
1004 dt->DrawSurface(aSurface, bounds, bounds, DrawSurfaceOptions(),
1005 DrawOptions(1.0f, CompositionOp::OP_OVER));
1006 RefPtr<SourceSurface> surface = dt->Snapshot();
1007 return surface->GetDataSurface();
1010 // If the surface IS of type DATA then it may or may not be in main memory
1011 // depending on whether or not it has been mapped yet. We have no way of
1012 // knowing, so we can't be sure if it's best to create a data wrapping
1013 // DrawTarget for the conversion or an offscreen content DrawTarget. We could
1014 // guess it's not mapped and create an offscreen content DrawTarget, but if
1015 // it is then we'll end up uploading the surface data, and most likely the
1016 // caller is going to be accessing the resulting surface data, resulting in a
1017 // readback (both very expensive operations). Alternatively we could guess
1018 // the data is mapped and create a data wrapping DrawTarget and, if the
1019 // surface is not in main memory, then we will incure a readback. The latter
1020 // of these two "wrong choices" is the least costly (a readback, vs an
1021 // upload and a readback), and more than likely the DATA surface that we've
1022 // been passed actually IS in main memory anyway. For these reasons it's most
1023 // likely best to create a data wrapping DrawTarget here to do the format
1024 // conversion.
1025 RefPtr<DataSourceSurface> dataSurface =
1026 Factory::CreateDataSourceSurface(aSurface->GetSize(), aFormat);
1027 DataSourceSurface::MappedSurface map;
1028 if (!dataSurface ||
1029 !dataSurface->Map(DataSourceSurface::MapType::READ_WRITE, &map)) {
1030 return nullptr;
1032 RefPtr<DrawTarget> dt = Factory::CreateDrawTargetForData(
1033 BackendType::CAIRO, map.mData, dataSurface->GetSize(), map.mStride,
1034 aFormat);
1035 if (!dt) {
1036 dataSurface->Unmap();
1037 return nullptr;
1039 // Using DrawSurface() here rather than CopySurface() because CopySurface
1040 // is optimized for memcpy and therefore isn't good for format conversion.
1041 // Using OP_OVER since in our case it's equivalent to OP_SOURCE and
1042 // generally more optimized.
1043 dt->DrawSurface(aSurface, bounds, bounds, DrawSurfaceOptions(),
1044 DrawOptions(1.0f, CompositionOp::OP_OVER));
1045 dataSurface->Unmap();
1046 return dataSurface.forget();
1049 const uint32_t gfxUtils::sNumFrameColors = 8;
1051 /* static */
1052 const gfx::DeviceColor& gfxUtils::GetColorForFrameNumber(
1053 uint64_t aFrameNumber) {
1054 static bool initialized = false;
1055 static gfx::DeviceColor colors[sNumFrameColors];
1057 if (!initialized) {
1058 // This isn't truly device color, but it is just for debug.
1059 uint32_t i = 0;
1060 colors[i++] = gfx::DeviceColor::FromABGR(0xffff0000);
1061 colors[i++] = gfx::DeviceColor::FromABGR(0xffcc00ff);
1062 colors[i++] = gfx::DeviceColor::FromABGR(0xff0066cc);
1063 colors[i++] = gfx::DeviceColor::FromABGR(0xff00ff00);
1064 colors[i++] = gfx::DeviceColor::FromABGR(0xff33ffff);
1065 colors[i++] = gfx::DeviceColor::FromABGR(0xffff0099);
1066 colors[i++] = gfx::DeviceColor::FromABGR(0xff0000ff);
1067 colors[i++] = gfx::DeviceColor::FromABGR(0xff999999);
1068 MOZ_ASSERT(i == sNumFrameColors);
1069 initialized = true;
1072 return colors[aFrameNumber % sNumFrameColors];
1075 /* static */
1076 nsresult gfxUtils::EncodeSourceSurface(SourceSurface* aSurface,
1077 const ImageType aImageType,
1078 const nsAString& aOutputOptions,
1079 BinaryOrData aBinaryOrData, FILE* aFile,
1080 nsACString* aStrOut) {
1081 MOZ_ASSERT(aBinaryOrData == gfxUtils::eDataURIEncode || aFile || aStrOut,
1082 "Copying binary encoding to clipboard not currently supported");
1084 const IntSize size = aSurface->GetSize();
1085 if (size.IsEmpty()) {
1086 return NS_ERROR_INVALID_ARG;
1088 const Size floatSize(size.width, size.height);
1090 RefPtr<DataSourceSurface> dataSurface;
1091 if (aSurface->GetFormat() != SurfaceFormat::B8G8R8A8) {
1092 // FIXME bug 995807 (B8G8R8X8), bug 831898 (R5G6B5)
1093 dataSurface = gfxUtils::CopySurfaceToDataSourceSurfaceWithFormat(
1094 aSurface, SurfaceFormat::B8G8R8A8);
1095 } else {
1096 dataSurface = aSurface->GetDataSurface();
1098 if (!dataSurface) {
1099 return NS_ERROR_FAILURE;
1102 DataSourceSurface::MappedSurface map;
1103 if (!dataSurface->Map(DataSourceSurface::MapType::READ, &map)) {
1104 return NS_ERROR_FAILURE;
1107 RefPtr<imgIEncoder> encoder = nullptr;
1109 switch (aImageType) {
1110 case ImageType::BMP:
1111 encoder = MakeRefPtr<nsBMPEncoder>();
1112 break;
1114 case ImageType::ICO:
1115 encoder = MakeRefPtr<nsICOEncoder>();
1116 break;
1118 case ImageType::JPEG:
1119 encoder = MakeRefPtr<nsJPEGEncoder>();
1120 break;
1122 case ImageType::PNG:
1123 encoder = MakeRefPtr<nsPNGEncoder>();
1124 break;
1126 default:
1127 break;
1130 MOZ_RELEASE_ASSERT(encoder != nullptr);
1132 nsresult rv = encoder->InitFromData(
1133 map.mData, BufferSizeFromStrideAndHeight(map.mStride, size.height),
1134 size.width, size.height, map.mStride, imgIEncoder::INPUT_FORMAT_HOSTARGB,
1135 aOutputOptions);
1136 dataSurface->Unmap();
1137 NS_ENSURE_SUCCESS(rv, rv);
1139 nsCOMPtr<nsIInputStream> imgStream(encoder);
1140 if (!imgStream) {
1141 return NS_ERROR_FAILURE;
1144 uint64_t bufSize64;
1145 rv = imgStream->Available(&bufSize64);
1146 NS_ENSURE_SUCCESS(rv, rv);
1148 NS_ENSURE_TRUE(bufSize64 < UINT32_MAX - 16, NS_ERROR_FAILURE);
1150 uint32_t bufSize = (uint32_t)bufSize64;
1152 // ...leave a little extra room so we can call read again and make sure we
1153 // got everything. 16 bytes for better padding (maybe)
1154 bufSize += 16;
1155 uint32_t imgSize = 0;
1156 Vector<char> imgData;
1157 if (!imgData.initCapacity(bufSize)) {
1158 return NS_ERROR_OUT_OF_MEMORY;
1160 uint32_t numReadThisTime = 0;
1161 while ((rv = imgStream->Read(imgData.begin() + imgSize, bufSize - imgSize,
1162 &numReadThisTime)) == NS_OK &&
1163 numReadThisTime > 0) {
1164 // Update the length of the vector without overwriting the new data.
1165 if (!imgData.growByUninitialized(numReadThisTime)) {
1166 return NS_ERROR_OUT_OF_MEMORY;
1169 imgSize += numReadThisTime;
1170 if (imgSize == bufSize) {
1171 // need a bigger buffer, just double
1172 bufSize *= 2;
1173 if (!imgData.resizeUninitialized(bufSize)) {
1174 return NS_ERROR_OUT_OF_MEMORY;
1178 NS_ENSURE_SUCCESS(rv, rv);
1179 NS_ENSURE_TRUE(!imgData.empty(), NS_ERROR_FAILURE);
1181 if (aBinaryOrData == gfxUtils::eBinaryEncode) {
1182 if (aFile) {
1183 Unused << fwrite(imgData.begin(), 1, imgSize, aFile);
1185 return NS_OK;
1188 nsCString stringBuf;
1189 nsACString& dataURI = aStrOut ? *aStrOut : stringBuf;
1190 dataURI.AppendLiteral("data:");
1192 switch (aImageType) {
1193 case ImageType::BMP:
1194 dataURI.AppendLiteral(IMAGE_BMP);
1195 break;
1197 case ImageType::ICO:
1198 dataURI.AppendLiteral(IMAGE_ICO_MS);
1199 break;
1200 case ImageType::JPEG:
1201 dataURI.AppendLiteral(IMAGE_JPEG);
1202 break;
1204 case ImageType::PNG:
1205 dataURI.AppendLiteral(IMAGE_PNG);
1206 break;
1208 default:
1209 break;
1212 dataURI.AppendLiteral(";base64,");
1213 rv = Base64EncodeAppend(imgData.begin(), imgSize, dataURI);
1214 NS_ENSURE_SUCCESS(rv, rv);
1216 if (aFile) {
1217 #ifdef ANDROID
1218 if (aFile == stdout || aFile == stderr) {
1219 // ADB logcat cuts off long strings so we will break it down
1220 const char* cStr = dataURI.BeginReading();
1221 size_t len = strlen(cStr);
1222 while (true) {
1223 printf_stderr("IMG: %.140s\n", cStr);
1224 if (len <= 140) break;
1225 len -= 140;
1226 cStr += 140;
1229 #endif
1230 fprintf(aFile, "%s", dataURI.BeginReading());
1231 } else if (!aStrOut) {
1232 nsCOMPtr<nsIClipboardHelper> clipboard(
1233 do_GetService("@mozilla.org/widget/clipboardhelper;1", &rv));
1234 if (clipboard) {
1235 clipboard->CopyString(NS_ConvertASCIItoUTF16(dataURI));
1238 return NS_OK;
1241 static nsCString EncodeSourceSurfaceAsPNGURI(SourceSurface* aSurface) {
1242 nsCString string;
1243 gfxUtils::EncodeSourceSurface(aSurface, ImageType::PNG, u""_ns,
1244 gfxUtils::eDataURIEncode, nullptr, &string);
1245 return string;
1248 // https://jdashg.github.io/misc/colors/from-coeffs.html
1249 const float kBT601NarrowYCbCrToRGB_RowMajor[16] = {
1250 1.16438f, 0.00000f, 1.59603f, -0.87420f, 1.16438f, -0.39176f,
1251 -0.81297f, 0.53167f, 1.16438f, 2.01723f, 0.00000f, -1.08563f,
1252 0.00000f, 0.00000f, 0.00000f, 1.00000f};
1253 const float kBT709NarrowYCbCrToRGB_RowMajor[16] = {
1254 1.16438f, 0.00000f, 1.79274f, -0.97295f, 1.16438f, -0.21325f,
1255 -0.53291f, 0.30148f, 1.16438f, 2.11240f, 0.00000f, -1.13340f,
1256 0.00000f, 0.00000f, 0.00000f, 1.00000f};
1257 const float kBT2020NarrowYCbCrToRGB_RowMajor[16] = {
1258 1.16438f, 0.00000f, 1.67867f, -0.91569f, 1.16438f, -0.18733f,
1259 -0.65042f, 0.34746f, 1.16438f, 2.14177f, 0.00000f, -1.14815f,
1260 0.00000f, 0.00000f, 0.00000f, 1.00000f};
1261 const float kIdentityNarrowYCbCrToRGB_RowMajor[16] = {
1262 0.00000f, 0.00000f, 1.00000f, 0.00000f, 1.00000f, 0.00000f,
1263 0.00000f, 0.00000f, 0.00000f, 1.00000f, 0.00000f, 0.00000f,
1264 0.00000f, 0.00000f, 0.00000f, 1.00000f};
1266 /* static */ const float* gfxUtils::YuvToRgbMatrix4x3RowMajor(
1267 gfx::YUVColorSpace aYUVColorSpace) {
1268 #define X(x) \
1269 { x[0], x[1], x[2], 0.0f, x[4], x[5], x[6], 0.0f, x[8], x[9], x[10], 0.0f }
1271 static const float rec601[12] = X(kBT601NarrowYCbCrToRGB_RowMajor);
1272 static const float rec709[12] = X(kBT709NarrowYCbCrToRGB_RowMajor);
1273 static const float rec2020[12] = X(kBT2020NarrowYCbCrToRGB_RowMajor);
1274 static const float identity[12] = X(kIdentityNarrowYCbCrToRGB_RowMajor);
1276 #undef X
1278 switch (aYUVColorSpace) {
1279 case gfx::YUVColorSpace::BT601:
1280 return rec601;
1281 case gfx::YUVColorSpace::BT709:
1282 return rec709;
1283 case gfx::YUVColorSpace::BT2020:
1284 return rec2020;
1285 case gfx::YUVColorSpace::Identity:
1286 return identity;
1287 default:
1288 MOZ_CRASH("Bad YUVColorSpace");
1292 /* static */ const float* gfxUtils::YuvToRgbMatrix3x3ColumnMajor(
1293 gfx::YUVColorSpace aYUVColorSpace) {
1294 #define X(x) \
1295 { x[0], x[4], x[8], x[1], x[5], x[9], x[2], x[6], x[10] }
1297 static const float rec601[9] = X(kBT601NarrowYCbCrToRGB_RowMajor);
1298 static const float rec709[9] = X(kBT709NarrowYCbCrToRGB_RowMajor);
1299 static const float rec2020[9] = X(kBT2020NarrowYCbCrToRGB_RowMajor);
1300 static const float identity[9] = X(kIdentityNarrowYCbCrToRGB_RowMajor);
1302 #undef X
1304 switch (aYUVColorSpace) {
1305 case gfx::YUVColorSpace::BT601:
1306 return rec601;
1307 case YUVColorSpace::BT709:
1308 return rec709;
1309 case YUVColorSpace::BT2020:
1310 return rec2020;
1311 case YUVColorSpace::Identity:
1312 return identity;
1313 default:
1314 MOZ_CRASH("Bad YUVColorSpace");
1318 /* static */ const float* gfxUtils::YuvToRgbMatrix4x4ColumnMajor(
1319 YUVColorSpace aYUVColorSpace) {
1320 #define X(x) \
1322 x[0], x[4], x[8], x[12], x[1], x[5], x[9], x[13], x[2], x[6], x[10], \
1323 x[14], x[3], x[7], x[11], x[15] \
1326 static const float rec601[16] = X(kBT601NarrowYCbCrToRGB_RowMajor);
1327 static const float rec709[16] = X(kBT709NarrowYCbCrToRGB_RowMajor);
1328 static const float rec2020[16] = X(kBT2020NarrowYCbCrToRGB_RowMajor);
1329 static const float identity[16] = X(kIdentityNarrowYCbCrToRGB_RowMajor);
1331 #undef X
1333 switch (aYUVColorSpace) {
1334 case YUVColorSpace::BT601:
1335 return rec601;
1336 case YUVColorSpace::BT709:
1337 return rec709;
1338 case YUVColorSpace::BT2020:
1339 return rec2020;
1340 case YUVColorSpace::Identity:
1341 return identity;
1342 default:
1343 MOZ_CRASH("Bad YUVColorSpace");
1347 // Translate from CICP values to the color spaces we support, or return
1348 // Nothing() if there is no appropriate match to let the caller choose
1349 // a default or generate an error.
1351 // See Rec. ITU-T H.273 (12/2016) for details on CICP
1352 /* static */ Maybe<gfx::YUVColorSpace> gfxUtils::CicpToColorSpace(
1353 const CICP::MatrixCoefficients aMatrixCoefficients,
1354 const CICP::ColourPrimaries aColourPrimaries, LazyLogModule& aLogger) {
1355 switch (aMatrixCoefficients) {
1356 case CICP::MatrixCoefficients::MC_BT2020_NCL:
1357 case CICP::MatrixCoefficients::MC_BT2020_CL:
1358 return Some(gfx::YUVColorSpace::BT2020);
1359 case CICP::MatrixCoefficients::MC_BT601:
1360 return Some(gfx::YUVColorSpace::BT601);
1361 case CICP::MatrixCoefficients::MC_BT709:
1362 return Some(gfx::YUVColorSpace::BT709);
1363 case CICP::MatrixCoefficients::MC_IDENTITY:
1364 return Some(gfx::YUVColorSpace::Identity);
1365 case CICP::MatrixCoefficients::MC_CHROMAT_NCL:
1366 case CICP::MatrixCoefficients::MC_CHROMAT_CL:
1367 case CICP::MatrixCoefficients::MC_UNSPECIFIED:
1368 switch (aColourPrimaries) {
1369 case CICP::ColourPrimaries::CP_BT601:
1370 return Some(gfx::YUVColorSpace::BT601);
1371 case CICP::ColourPrimaries::CP_BT709:
1372 return Some(gfx::YUVColorSpace::BT709);
1373 case CICP::ColourPrimaries::CP_BT2020:
1374 return Some(gfx::YUVColorSpace::BT2020);
1375 default:
1376 MOZ_LOG(aLogger, LogLevel::Debug,
1377 ("Couldn't infer color matrix from primaries: %hhu",
1378 aColourPrimaries));
1379 return {};
1381 default:
1382 MOZ_LOG(aLogger, LogLevel::Debug,
1383 ("Unsupported color matrix value: %hhu", aMatrixCoefficients));
1384 return {};
1388 // Translate from CICP values to the color primaries we support, or return
1389 // Nothing() if there is no appropriate match to let the caller choose
1390 // a default or generate an error.
1392 // See Rec. ITU-T H.273 (12/2016) for details on CICP
1393 /* static */ Maybe<gfx::ColorSpace2> gfxUtils::CicpToColorPrimaries(
1394 const CICP::ColourPrimaries aColourPrimaries, LazyLogModule& aLogger) {
1395 switch (aColourPrimaries) {
1396 case CICP::ColourPrimaries::CP_BT709:
1397 return Some(gfx::ColorSpace2::BT709);
1398 case CICP::ColourPrimaries::CP_BT2020:
1399 return Some(gfx::ColorSpace2::BT2020);
1400 default:
1401 MOZ_LOG(aLogger, LogLevel::Debug,
1402 ("Unsupported color primaries value: %hhu", aColourPrimaries));
1403 return {};
1407 // Translate from CICP values to the transfer functions we support, or return
1408 // Nothing() if there is no appropriate match.
1410 /* static */ Maybe<gfx::TransferFunction> gfxUtils::CicpToTransferFunction(
1411 const CICP::TransferCharacteristics aTransferCharacteristics) {
1412 switch (aTransferCharacteristics) {
1413 case CICP::TransferCharacteristics::TC_BT709:
1414 return Some(gfx::TransferFunction::BT709);
1416 case CICP::TransferCharacteristics::TC_SRGB:
1417 return Some(gfx::TransferFunction::SRGB);
1419 case CICP::TransferCharacteristics::TC_SMPTE2084:
1420 return Some(gfx::TransferFunction::PQ);
1422 case CICP::TransferCharacteristics::TC_HLG:
1423 return Some(gfx::TransferFunction::HLG);
1425 default:
1426 return {};
1430 /* static */
1431 void gfxUtils::WriteAsPNG(SourceSurface* aSurface, const nsAString& aFile) {
1432 WriteAsPNG(aSurface, NS_ConvertUTF16toUTF8(aFile).get());
1435 /* static */
1436 void gfxUtils::WriteAsPNG(SourceSurface* aSurface, const char* aFile) {
1437 FILE* file = fopen(aFile, "wb");
1439 if (!file) {
1440 // Maybe the directory doesn't exist; try creating it, then fopen again.
1441 nsresult rv = NS_ERROR_FAILURE;
1442 nsCOMPtr<nsIFile> comFile = do_CreateInstance("@mozilla.org/file/local;1");
1443 if (comFile) {
1444 NS_ConvertUTF8toUTF16 utf16path((nsDependentCString(aFile)));
1445 rv = comFile->InitWithPath(utf16path);
1446 if (NS_SUCCEEDED(rv)) {
1447 nsCOMPtr<nsIFile> dirPath;
1448 comFile->GetParent(getter_AddRefs(dirPath));
1449 if (dirPath) {
1450 rv = dirPath->Create(nsIFile::DIRECTORY_TYPE, 0777);
1451 if (NS_SUCCEEDED(rv) || rv == NS_ERROR_FILE_ALREADY_EXISTS) {
1452 file = fopen(aFile, "wb");
1457 if (!file) {
1458 NS_WARNING("Failed to open file to create PNG!");
1459 return;
1463 EncodeSourceSurface(aSurface, ImageType::PNG, u""_ns, eBinaryEncode, file);
1464 fclose(file);
1467 /* static */
1468 void gfxUtils::WriteAsPNG(DrawTarget* aDT, const nsAString& aFile) {
1469 WriteAsPNG(aDT, NS_ConvertUTF16toUTF8(aFile).get());
1472 /* static */
1473 void gfxUtils::WriteAsPNG(DrawTarget* aDT, const char* aFile) {
1474 RefPtr<SourceSurface> surface = aDT->Snapshot();
1475 if (surface) {
1476 WriteAsPNG(surface, aFile);
1477 } else {
1478 NS_WARNING("Failed to get surface!");
1482 /* static */
1483 void gfxUtils::DumpAsDataURI(SourceSurface* aSurface, FILE* aFile) {
1484 EncodeSourceSurface(aSurface, ImageType::PNG, u""_ns, eDataURIEncode, aFile);
1487 /* static */
1488 nsCString gfxUtils::GetAsDataURI(SourceSurface* aSurface) {
1489 return EncodeSourceSurfaceAsPNGURI(aSurface);
1492 /* static */
1493 void gfxUtils::DumpAsDataURI(DrawTarget* aDT, FILE* aFile) {
1494 RefPtr<SourceSurface> surface = aDT->Snapshot();
1495 if (surface) {
1496 DumpAsDataURI(surface, aFile);
1497 } else {
1498 NS_WARNING("Failed to get surface!");
1502 /* static */
1503 nsCString gfxUtils::GetAsLZ4Base64Str(DataSourceSurface* aSourceSurface) {
1504 DataSourceSurface::ScopedMap map(aSourceSurface, DataSourceSurface::READ);
1505 int32_t dataSize = aSourceSurface->GetSize().height * map.GetStride();
1506 auto compressedData = MakeUnique<char[]>(LZ4::maxCompressedSize(dataSize));
1507 if (compressedData) {
1508 int nDataSize =
1509 LZ4::compress((char*)map.GetData(), dataSize, compressedData.get());
1510 if (nDataSize > 0) {
1511 nsCString string;
1512 string.AppendPrintf("data:image/lz4bgra;base64,%i,%i,%i,",
1513 aSourceSurface->GetSize().width, map.GetStride(),
1514 aSourceSurface->GetSize().height);
1515 nsresult rv = Base64EncodeAppend(compressedData.get(), nDataSize, string);
1516 if (rv == NS_OK) {
1517 return string;
1521 return {};
1524 /* static */
1525 nsCString gfxUtils::GetAsDataURI(DrawTarget* aDT) {
1526 RefPtr<SourceSurface> surface = aDT->Snapshot();
1527 if (surface) {
1528 return EncodeSourceSurfaceAsPNGURI(surface);
1529 } else {
1530 NS_WARNING("Failed to get surface!");
1531 return nsCString("");
1535 /* static */
1536 void gfxUtils::CopyAsDataURI(SourceSurface* aSurface) {
1537 EncodeSourceSurface(aSurface, ImageType::PNG, u""_ns, eDataURIEncode,
1538 nullptr);
1541 /* static */
1542 void gfxUtils::CopyAsDataURI(DrawTarget* aDT) {
1543 RefPtr<SourceSurface> surface = aDT->Snapshot();
1544 if (surface) {
1545 CopyAsDataURI(surface);
1546 } else {
1547 NS_WARNING("Failed to get surface!");
1551 /* static */ UniquePtr<uint8_t[]> gfxUtils::GetImageBuffer(
1552 gfx::DataSourceSurface* aSurface, bool aIsAlphaPremultiplied,
1553 int32_t* outFormat) {
1554 *outFormat = 0;
1556 DataSourceSurface::MappedSurface map;
1557 if (!aSurface->Map(DataSourceSurface::MapType::READ, &map)) return nullptr;
1559 uint32_t bufferSize =
1560 aSurface->GetSize().width * aSurface->GetSize().height * 4;
1561 auto imageBuffer = MakeUniqueFallible<uint8_t[]>(bufferSize);
1562 if (!imageBuffer) {
1563 aSurface->Unmap();
1564 return nullptr;
1566 memcpy(imageBuffer.get(), map.mData, bufferSize);
1568 aSurface->Unmap();
1570 int32_t format = imgIEncoder::INPUT_FORMAT_HOSTARGB;
1571 if (!aIsAlphaPremultiplied) {
1572 // We need to convert to INPUT_FORMAT_RGBA, otherwise
1573 // we are automatically considered premult, and unpremult'd.
1574 // Yes, it is THAT silly.
1575 // Except for different lossy conversions by color,
1576 // we could probably just change the label, and not change the data.
1577 gfxUtils::ConvertBGRAtoRGBA(imageBuffer.get(), bufferSize);
1578 format = imgIEncoder::INPUT_FORMAT_RGBA;
1581 *outFormat = format;
1582 return imageBuffer;
1585 /* static */
1586 nsresult gfxUtils::GetInputStream(gfx::DataSourceSurface* aSurface,
1587 bool aIsAlphaPremultiplied,
1588 const char* aMimeType,
1589 const nsAString& aEncoderOptions,
1590 nsIInputStream** outStream) {
1591 nsCString enccid("@mozilla.org/image/encoder;2?type=");
1592 enccid += aMimeType;
1593 nsCOMPtr<imgIEncoder> encoder = do_CreateInstance(enccid.get());
1594 if (!encoder) return NS_ERROR_FAILURE;
1596 int32_t format = 0;
1597 UniquePtr<uint8_t[]> imageBuffer =
1598 GetImageBuffer(aSurface, aIsAlphaPremultiplied, &format);
1599 if (!imageBuffer) return NS_ERROR_FAILURE;
1601 return dom::ImageEncoder::GetInputStream(
1602 aSurface->GetSize().width, aSurface->GetSize().height, imageBuffer.get(),
1603 format, encoder, aEncoderOptions, outStream);
1606 class GetFeatureStatusWorkerRunnable final
1607 : public dom::WorkerMainThreadRunnable {
1608 public:
1609 GetFeatureStatusWorkerRunnable(dom::WorkerPrivate* workerPrivate,
1610 const nsCOMPtr<nsIGfxInfo>& gfxInfo,
1611 int32_t feature, nsACString& failureId,
1612 int32_t* status)
1613 : WorkerMainThreadRunnable(workerPrivate, "GFX :: GetFeatureStatus"_ns),
1614 mGfxInfo(gfxInfo),
1615 mFeature(feature),
1616 mStatus(status),
1617 mFailureId(failureId),
1618 mNSResult(NS_OK) {}
1620 bool MainThreadRun() override {
1621 if (mGfxInfo) {
1622 mNSResult = mGfxInfo->GetFeatureStatus(mFeature, mFailureId, mStatus);
1624 return true;
1627 nsresult GetNSResult() const { return mNSResult; }
1629 protected:
1630 ~GetFeatureStatusWorkerRunnable() = default;
1632 private:
1633 nsCOMPtr<nsIGfxInfo> mGfxInfo;
1634 int32_t mFeature;
1635 int32_t* mStatus;
1636 nsACString& mFailureId;
1637 nsresult mNSResult;
1640 #define GFX_SHADER_CHECK_BUILD_VERSION_PREF "gfx-shader-check.build-version"
1641 #define GFX_SHADER_CHECK_PTR_SIZE_PREF "gfx-shader-check.ptr-size"
1642 #define GFX_SHADER_CHECK_DEVICE_ID_PREF "gfx-shader-check.device-id"
1643 #define GFX_SHADER_CHECK_DRIVER_VERSION_PREF "gfx-shader-check.driver-version"
1645 /* static */
1646 void gfxUtils::RemoveShaderCacheFromDiskIfNecessary() {
1647 if (!gfxVars::UseWebRenderProgramBinaryDisk()) {
1648 return;
1651 nsCOMPtr<nsIGfxInfo> gfxInfo = components::GfxInfo::Service();
1653 // Get current values
1654 nsCString buildID(mozilla::PlatformBuildID());
1655 int ptrSize = sizeof(void*);
1656 nsString deviceID, driverVersion;
1657 gfxInfo->GetAdapterDeviceID(deviceID);
1658 gfxInfo->GetAdapterDriverVersion(driverVersion);
1660 // Get pref stored values
1661 nsAutoCString buildIDChecked;
1662 Preferences::GetCString(GFX_SHADER_CHECK_BUILD_VERSION_PREF, buildIDChecked);
1663 int ptrSizeChecked = Preferences::GetInt(GFX_SHADER_CHECK_PTR_SIZE_PREF, 0);
1664 nsAutoString deviceIDChecked, driverVersionChecked;
1665 Preferences::GetString(GFX_SHADER_CHECK_DEVICE_ID_PREF, deviceIDChecked);
1666 Preferences::GetString(GFX_SHADER_CHECK_DRIVER_VERSION_PREF,
1667 driverVersionChecked);
1669 if (buildID == buildIDChecked && ptrSize == ptrSizeChecked &&
1670 deviceID == deviceIDChecked && driverVersion == driverVersionChecked) {
1671 return;
1674 nsAutoString path(gfx::gfxVars::ProfDirectory());
1676 if (!wr::remove_program_binary_disk_cache(&path)) {
1677 // Failed to remove program binary disk cache. The disk cache might have
1678 // invalid data. Disable program binary disk cache usage.
1679 gfxVars::SetUseWebRenderProgramBinaryDisk(false);
1680 return;
1683 Preferences::SetCString(GFX_SHADER_CHECK_BUILD_VERSION_PREF, buildID);
1684 Preferences::SetInt(GFX_SHADER_CHECK_PTR_SIZE_PREF, ptrSize);
1685 Preferences::SetString(GFX_SHADER_CHECK_DEVICE_ID_PREF, deviceID);
1686 Preferences::SetString(GFX_SHADER_CHECK_DRIVER_VERSION_PREF, driverVersion);
1689 /* static */
1690 bool gfxUtils::DumpDisplayList() {
1691 return StaticPrefs::layout_display_list_dump() ||
1692 (StaticPrefs::layout_display_list_dump_parent() &&
1693 XRE_IsParentProcess()) ||
1694 (StaticPrefs::layout_display_list_dump_content() &&
1695 XRE_IsContentProcess());
1698 FILE* gfxUtils::sDumpPaintFile = stderr;
1700 namespace mozilla {
1701 namespace gfx {
1703 DeviceColor ToDeviceColor(const sRGBColor& aColor) {
1704 // aColor is pass-by-value since to get return value optimization goodness we
1705 // need to return the same object from all return points in this function. We
1706 // could declare a local Color variable and use that, but we might as well
1707 // just use aColor.
1708 if (gfxPlatform::GetCMSMode() == CMSMode::All) {
1709 qcms_transform* transform = gfxPlatform::GetCMSRGBTransform();
1710 if (transform) {
1711 return gfxPlatform::TransformPixel(aColor, transform);
1712 // Use the original alpha to avoid unnecessary float->byte->float
1713 // conversion errors
1716 return DeviceColor(aColor.r, aColor.g, aColor.b, aColor.a);
1719 DeviceColor ToDeviceColor(nscolor aColor) {
1720 return ToDeviceColor(sRGBColor::FromABGR(aColor));
1723 DeviceColor ToDeviceColor(const StyleRGBA& aColor) {
1724 return ToDeviceColor(aColor.ToColor());
1727 sRGBColor ToSRGBColor(const StyleAnimatedRGBA& aColor) {
1728 const auto ToComponent = [](float aF) -> float {
1729 float component = std::min(std::max(0.0f, aF), 1.0f);
1730 if (MOZ_UNLIKELY(!std::isfinite(component))) {
1731 return 0.0f;
1733 return component;
1735 return {ToComponent(aColor.red), ToComponent(aColor.green),
1736 ToComponent(aColor.blue), ToComponent(aColor.alpha)};
1739 DeviceColor ToDeviceColor(const StyleAnimatedRGBA& aColor) {
1740 return ToDeviceColor(ToSRGBColor(aColor));
1743 } // namespace gfx
1744 } // namespace mozilla