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