1 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
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/. */
5 
6 #include "gfxUtils.h"
7 
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 "imgIEncoder.h"
15 #include "libyuv.h"
16 #include "mozilla/Base64.h"
17 #include "mozilla/dom/ImageEncoder.h"
18 #include "mozilla/dom/WorkerPrivate.h"
19 #include "mozilla/dom/WorkerRunnable.h"
20 #include "mozilla/gfx/2D.h"
21 #include "mozilla/gfx/DataSurfaceHelpers.h"
22 #include "mozilla/gfx/Logging.h"
23 #include "mozilla/gfx/PathHelpers.h"
24 #include "mozilla/Maybe.h"
25 #include "mozilla/RefPtr.h"
26 #include "mozilla/UniquePtrExtensions.h"
27 #include "mozilla/Vector.h"
28 #include "nsComponentManagerUtils.h"
29 #include "nsIClipboardHelper.h"
30 #include "nsIFile.h"
31 #include "nsIGfxInfo.h"
32 #include "nsIPresShell.h"
33 #include "nsPresContext.h"
34 #include "nsRegion.h"
35 #include "nsServiceManagerUtils.h"
36 #include "GeckoProfiler.h"
37 #include "ImageContainer.h"
38 #include "ImageRegion.h"
39 #include "gfx2DGlue.h"
40 #include "gfxPrefs.h"
41 
42 #ifdef XP_WIN
43 #include "gfxWindowsPlatform.h"
44 #endif
45 
46 using namespace mozilla;
47 using namespace mozilla::image;
48 using namespace mozilla::layers;
49 using namespace mozilla::gfx;
50 
51 #include "DeprecatedPremultiplyTables.h"
52 
53 #undef compress
54 #include "mozilla/Compression.h"
55 
56 using namespace mozilla::Compression;
57 extern "C" {
58 
59 /**
60  * Dump a raw image to the default log.  This function is exported
61  * from libxul, so it can be called from any library in addition to
62  * (of course) from a debugger.
63  *
64  * Note: this helper currently assumes that all 2-bytepp images are
65  * r5g6b5, and that all 4-bytepp images are r8g8b8a8.
66  */
67 NS_EXPORT
mozilla_dump_image(void * bytes,int width,int height,int bytepp,int strideBytes)68 void mozilla_dump_image(void* bytes, int width, int height, int bytepp,
69                         int strideBytes)
70 {
71     if (0 == strideBytes) {
72         strideBytes = width * bytepp;
73     }
74     SurfaceFormat format;
75     // TODO more flexible; parse string?
76     switch (bytepp) {
77     case 2:
78         format = SurfaceFormat::R5G6B5_UINT16;
79         break;
80     case 4:
81     default:
82         format = SurfaceFormat::R8G8B8A8;
83         break;
84     }
85 
86     RefPtr<DataSourceSurface> surf =
87         Factory::CreateWrappingDataSourceSurface((uint8_t*)bytes, strideBytes,
88                                                  IntSize(width, height),
89                                                  format);
90     gfxUtils::DumpAsDataURI(surf);
91 }
92 
93 }
94 
PremultiplyValue(uint8_t a,uint8_t v)95 static uint8_t PremultiplyValue(uint8_t a, uint8_t v) {
96     return gfxUtils::sPremultiplyTable[a*256+v];
97 }
98 
UnpremultiplyValue(uint8_t a,uint8_t v)99 static uint8_t UnpremultiplyValue(uint8_t a, uint8_t v) {
100     return gfxUtils::sUnpremultiplyTable[a*256+v];
101 }
102 
103 static void
PremultiplyData(const uint8_t * srcData,size_t srcStride,uint8_t * destData,size_t destStride,size_t pixelWidth,size_t rowCount)104 PremultiplyData(const uint8_t* srcData,
105                 size_t srcStride,  // row-to-row stride in bytes
106                 uint8_t* destData,
107                 size_t destStride, // row-to-row stride in bytes
108                 size_t pixelWidth,
109                 size_t rowCount)
110 {
111     MOZ_ASSERT(srcData && destData);
112 
113     for (size_t y = 0; y < rowCount; ++y) {
114         const uint8_t* src  = srcData  + y * srcStride;
115         uint8_t* dest       = destData + y * destStride;
116 
117         for (size_t x = 0; x < pixelWidth; ++x) {
118 #ifdef IS_LITTLE_ENDIAN
119             uint8_t b = *src++;
120             uint8_t g = *src++;
121             uint8_t r = *src++;
122             uint8_t a = *src++;
123 
124             *dest++ = PremultiplyValue(a, b);
125             *dest++ = PremultiplyValue(a, g);
126             *dest++ = PremultiplyValue(a, r);
127             *dest++ = a;
128 #else
129             uint8_t a = *src++;
130             uint8_t r = *src++;
131             uint8_t g = *src++;
132             uint8_t b = *src++;
133 
134             *dest++ = a;
135             *dest++ = PremultiplyValue(a, r);
136             *dest++ = PremultiplyValue(a, g);
137             *dest++ = PremultiplyValue(a, b);
138 #endif
139         }
140     }
141 }
142 static void
UnpremultiplyData(const uint8_t * srcData,size_t srcStride,uint8_t * destData,size_t destStride,size_t pixelWidth,size_t rowCount)143 UnpremultiplyData(const uint8_t* srcData,
144                   size_t srcStride,  // row-to-row stride in bytes
145                   uint8_t* destData,
146                   size_t destStride, // row-to-row stride in bytes
147                   size_t pixelWidth,
148                   size_t rowCount)
149 {
150     MOZ_ASSERT(srcData && destData);
151 
152     for (size_t y = 0; y < rowCount; ++y) {
153         const uint8_t* src  = srcData  + y * srcStride;
154         uint8_t* dest       = destData + y * destStride;
155 
156         for (size_t x = 0; x < pixelWidth; ++x) {
157 #ifdef IS_LITTLE_ENDIAN
158             uint8_t b = *src++;
159             uint8_t g = *src++;
160             uint8_t r = *src++;
161             uint8_t a = *src++;
162 
163             *dest++ = UnpremultiplyValue(a, b);
164             *dest++ = UnpremultiplyValue(a, g);
165             *dest++ = UnpremultiplyValue(a, r);
166             *dest++ = a;
167 #else
168             uint8_t a = *src++;
169             uint8_t r = *src++;
170             uint8_t g = *src++;
171             uint8_t b = *src++;
172 
173             *dest++ = a;
174             *dest++ = UnpremultiplyValue(a, r);
175             *dest++ = UnpremultiplyValue(a, g);
176             *dest++ = UnpremultiplyValue(a, b);
177 #endif
178         }
179     }
180 }
181 
182 static bool
MapSrcDest(DataSourceSurface * srcSurf,DataSourceSurface * destSurf,DataSourceSurface::MappedSurface * out_srcMap,DataSourceSurface::MappedSurface * out_destMap)183 MapSrcDest(DataSourceSurface* srcSurf,
184            DataSourceSurface* destSurf,
185            DataSourceSurface::MappedSurface* out_srcMap,
186            DataSourceSurface::MappedSurface* out_destMap)
187 {
188     MOZ_ASSERT(srcSurf && destSurf);
189     MOZ_ASSERT(out_srcMap && out_destMap);
190 
191     if (srcSurf->GetFormat()  != SurfaceFormat::B8G8R8A8 ||
192         destSurf->GetFormat() != SurfaceFormat::B8G8R8A8)
193     {
194         MOZ_ASSERT(false, "Only operate on BGRA8 surfs.");
195         return false;
196     }
197 
198     if (srcSurf->GetSize().width  != destSurf->GetSize().width ||
199         srcSurf->GetSize().height != destSurf->GetSize().height)
200     {
201         MOZ_ASSERT(false, "Width and height must match.");
202         return false;
203     }
204 
205     if (srcSurf == destSurf) {
206         DataSourceSurface::MappedSurface map;
207         if (!srcSurf->Map(DataSourceSurface::MapType::READ_WRITE, &map)) {
208             NS_WARNING("Couldn't Map srcSurf/destSurf.");
209             return false;
210         }
211 
212         *out_srcMap = map;
213         *out_destMap = map;
214         return true;
215     }
216 
217     // Map src for reading.
218     DataSourceSurface::MappedSurface srcMap;
219     if (!srcSurf->Map(DataSourceSurface::MapType::READ, &srcMap)) {
220         NS_WARNING("Couldn't Map srcSurf.");
221         return false;
222     }
223 
224     // Map dest for writing.
225     DataSourceSurface::MappedSurface destMap;
226     if (!destSurf->Map(DataSourceSurface::MapType::WRITE, &destMap)) {
227         NS_WARNING("Couldn't Map aDest.");
228         srcSurf->Unmap();
229         return false;
230     }
231 
232     *out_srcMap = srcMap;
233     *out_destMap = destMap;
234     return true;
235 }
236 
237 static void
UnmapSrcDest(DataSourceSurface * srcSurf,DataSourceSurface * destSurf)238 UnmapSrcDest(DataSourceSurface* srcSurf,
239              DataSourceSurface* destSurf)
240 {
241     if (srcSurf == destSurf) {
242         srcSurf->Unmap();
243     } else {
244         srcSurf->Unmap();
245         destSurf->Unmap();
246     }
247 }
248 
249 bool
PremultiplyDataSurface(DataSourceSurface * srcSurf,DataSourceSurface * destSurf)250 gfxUtils::PremultiplyDataSurface(DataSourceSurface* srcSurf,
251                                  DataSourceSurface* destSurf)
252 {
253     MOZ_ASSERT(srcSurf && destSurf);
254 
255     DataSourceSurface::MappedSurface srcMap;
256     DataSourceSurface::MappedSurface destMap;
257     if (!MapSrcDest(srcSurf, destSurf, &srcMap, &destMap))
258         return false;
259 
260     PremultiplyData(srcMap.mData, srcMap.mStride,
261                     destMap.mData, destMap.mStride,
262                     srcSurf->GetSize().width,
263                     srcSurf->GetSize().height);
264 
265     UnmapSrcDest(srcSurf, destSurf);
266     return true;
267 }
268 
269 bool
UnpremultiplyDataSurface(DataSourceSurface * srcSurf,DataSourceSurface * destSurf)270 gfxUtils::UnpremultiplyDataSurface(DataSourceSurface* srcSurf,
271                                    DataSourceSurface* destSurf)
272 {
273     MOZ_ASSERT(srcSurf && destSurf);
274 
275     DataSourceSurface::MappedSurface srcMap;
276     DataSourceSurface::MappedSurface destMap;
277     if (!MapSrcDest(srcSurf, destSurf, &srcMap, &destMap))
278         return false;
279 
280     UnpremultiplyData(srcMap.mData, srcMap.mStride,
281                       destMap.mData, destMap.mStride,
282                       srcSurf->GetSize().width,
283                       srcSurf->GetSize().height);
284 
285     UnmapSrcDest(srcSurf, destSurf);
286     return true;
287 }
288 
289 static bool
MapSrcAndCreateMappedDest(DataSourceSurface * srcSurf,RefPtr<DataSourceSurface> * out_destSurf,DataSourceSurface::MappedSurface * out_srcMap,DataSourceSurface::MappedSurface * out_destMap)290 MapSrcAndCreateMappedDest(DataSourceSurface* srcSurf,
291                           RefPtr<DataSourceSurface>* out_destSurf,
292                           DataSourceSurface::MappedSurface* out_srcMap,
293                           DataSourceSurface::MappedSurface* out_destMap)
294 {
295     MOZ_ASSERT(srcSurf);
296     MOZ_ASSERT(out_destSurf && out_srcMap && out_destMap);
297 
298     if (srcSurf->GetFormat() != SurfaceFormat::B8G8R8A8) {
299         MOZ_ASSERT(false, "Only operate on BGRA8.");
300         return false;
301     }
302 
303     // Ok, map source for reading.
304     DataSourceSurface::MappedSurface srcMap;
305     if (!srcSurf->Map(DataSourceSurface::MapType::READ, &srcMap)) {
306         MOZ_ASSERT(false, "Couldn't Map srcSurf.");
307         return false;
308     }
309 
310     // Make our dest surface based on the src.
311     RefPtr<DataSourceSurface> destSurf =
312         Factory::CreateDataSourceSurfaceWithStride(srcSurf->GetSize(),
313                                                    srcSurf->GetFormat(),
314                                                    srcMap.mStride);
315     if (NS_WARN_IF(!destSurf)) {
316         return false;
317     }
318 
319     DataSourceSurface::MappedSurface destMap;
320     if (!destSurf->Map(DataSourceSurface::MapType::WRITE, &destMap)) {
321         MOZ_ASSERT(false, "Couldn't Map destSurf.");
322         srcSurf->Unmap();
323         return false;
324     }
325 
326     *out_destSurf = destSurf;
327     *out_srcMap = srcMap;
328     *out_destMap = destMap;
329     return true;
330 }
331 
332 already_AddRefed<DataSourceSurface>
CreatePremultipliedDataSurface(DataSourceSurface * srcSurf)333 gfxUtils::CreatePremultipliedDataSurface(DataSourceSurface* srcSurf)
334 {
335     RefPtr<DataSourceSurface> destSurf;
336     DataSourceSurface::MappedSurface srcMap;
337     DataSourceSurface::MappedSurface destMap;
338     if (!MapSrcAndCreateMappedDest(srcSurf, &destSurf, &srcMap, &destMap)) {
339         MOZ_ASSERT(false, "MapSrcAndCreateMappedDest failed.");
340         RefPtr<DataSourceSurface> surface(srcSurf);
341         return surface.forget();
342     }
343 
344     PremultiplyData(srcMap.mData, srcMap.mStride,
345                     destMap.mData, destMap.mStride,
346                     srcSurf->GetSize().width,
347                     srcSurf->GetSize().height);
348 
349     UnmapSrcDest(srcSurf, destSurf);
350     return destSurf.forget();
351 }
352 
353 already_AddRefed<DataSourceSurface>
CreateUnpremultipliedDataSurface(DataSourceSurface * srcSurf)354 gfxUtils::CreateUnpremultipliedDataSurface(DataSourceSurface* srcSurf)
355 {
356     RefPtr<DataSourceSurface> destSurf;
357     DataSourceSurface::MappedSurface srcMap;
358     DataSourceSurface::MappedSurface destMap;
359     if (!MapSrcAndCreateMappedDest(srcSurf, &destSurf, &srcMap, &destMap)) {
360         MOZ_ASSERT(false, "MapSrcAndCreateMappedDest failed.");
361         RefPtr<DataSourceSurface> surface(srcSurf);
362         return surface.forget();
363     }
364 
365     UnpremultiplyData(srcMap.mData, srcMap.mStride,
366                       destMap.mData, destMap.mStride,
367                       srcSurf->GetSize().width,
368                       srcSurf->GetSize().height);
369 
370     UnmapSrcDest(srcSurf, destSurf);
371     return destSurf.forget();
372 }
373 
374 void
ConvertBGRAtoRGBA(uint8_t * aData,uint32_t aLength)375 gfxUtils::ConvertBGRAtoRGBA(uint8_t* aData, uint32_t aLength)
376 {
377     MOZ_ASSERT((aLength % 4) == 0, "Loop below will pass srcEnd!");
378     libyuv::ABGRToARGB(aData, aLength, aData, aLength, aLength / 4, 1);
379 }
380 
381 #if !defined(MOZ_GFX_OPTIMIZE_MOBILE)
382 /**
383  * This returns the fastest operator to use for solid surfaces which have no
384  * alpha channel or their alpha channel is uniformly opaque.
385  * This differs per render mode.
386  */
387 static CompositionOp
OptimalFillOp()388 OptimalFillOp()
389 {
390 #ifdef XP_WIN
391     if (gfxWindowsPlatform::GetPlatform()->IsDirect2DBackend()) {
392         // D2D -really- hates operator source.
393         return CompositionOp::OP_OVER;
394     }
395 #endif
396     return CompositionOp::OP_SOURCE;
397 }
398 
399 // EXTEND_PAD won't help us here; we have to create a temporary surface to hold
400 // the subimage of pixels we're allowed to sample.
401 static already_AddRefed<gfxDrawable>
CreateSamplingRestrictedDrawable(gfxDrawable * aDrawable,gfxContext * aContext,const ImageRegion & aRegion,const SurfaceFormat aFormat)402 CreateSamplingRestrictedDrawable(gfxDrawable* aDrawable,
403                                  gfxContext* aContext,
404                                  const ImageRegion& aRegion,
405                                  const SurfaceFormat aFormat)
406 {
407     PROFILER_LABEL("gfxUtils", "CreateSamplingRestricedDrawable",
408       js::ProfileEntry::Category::GRAPHICS);
409 
410     DrawTarget* destDrawTarget = aContext->GetDrawTarget();
411     if (destDrawTarget->GetBackendType() == BackendType::DIRECT2D1_1) {
412       return nullptr;
413     }
414 
415     gfxRect clipExtents = aContext->GetClipExtents();
416 
417     // Inflate by one pixel because bilinear filtering will sample at most
418     // one pixel beyond the computed image pixel coordinate.
419     clipExtents.Inflate(1.0);
420 
421     gfxRect needed = aRegion.IntersectAndRestrict(clipExtents);
422     needed.RoundOut();
423 
424     // if 'needed' is empty, nothing will be drawn since aFill
425     // must be entirely outside the clip region, so it doesn't
426     // matter what we do here, but we should avoid trying to
427     // create a zero-size surface.
428     if (needed.IsEmpty())
429         return nullptr;
430 
431     IntSize size(int32_t(needed.Width()), int32_t(needed.Height()));
432 
433     RefPtr<DrawTarget> target =
434       gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(size, aFormat);
435     if (!target || !target->IsValid()) {
436       return nullptr;
437     }
438 
439     RefPtr<gfxContext> tmpCtx = gfxContext::CreateOrNull(target);
440     MOZ_ASSERT(tmpCtx); // already checked the target above
441 
442     tmpCtx->SetOp(OptimalFillOp());
443     aDrawable->Draw(tmpCtx, needed - needed.TopLeft(), ExtendMode::REPEAT,
444                     SamplingFilter::LINEAR,
445                     1.0, gfxMatrix::Translation(needed.TopLeft()));
446     RefPtr<SourceSurface> surface = target->Snapshot();
447 
448     RefPtr<gfxDrawable> drawable = new gfxSurfaceDrawable(surface, size, gfxMatrix::Translation(-needed.TopLeft()));
449     return drawable.forget();
450 }
451 #endif // !MOZ_GFX_OPTIMIZE_MOBILE
452 
453 /* These heuristics are based on Source/WebCore/platform/graphics/skia/ImageSkia.cpp:computeResamplingMode() */
454 #ifdef MOZ_GFX_OPTIMIZE_MOBILE
ReduceResamplingFilter(SamplingFilter aSamplingFilter,int aImgWidth,int aImgHeight,float aSourceWidth,float aSourceHeight)455 static SamplingFilter ReduceResamplingFilter(SamplingFilter aSamplingFilter,
456                                              int aImgWidth, int aImgHeight,
457                                              float aSourceWidth, float aSourceHeight)
458 {
459     // Images smaller than this in either direction are considered "small" and
460     // are not resampled ever (see below).
461     const int kSmallImageSizeThreshold = 8;
462 
463     // The amount an image can be stretched in a single direction before we
464     // say that it is being stretched so much that it must be a line or
465     // background that doesn't need resampling.
466     const float kLargeStretch = 3.0f;
467 
468     if (aImgWidth <= kSmallImageSizeThreshold
469         || aImgHeight <= kSmallImageSizeThreshold) {
470         // Never resample small images. These are often used for borders and
471         // rules (think 1x1 images used to make lines).
472         return SamplingFilter::POINT;
473     }
474 
475     if (aImgHeight * kLargeStretch <= aSourceHeight || aImgWidth * kLargeStretch <= aSourceWidth) {
476         // Large image tiling detected.
477 
478         // Don't resample if it is being tiled a lot in only one direction.
479         // This is trying to catch cases where somebody has created a border
480         // (which might be large) and then is stretching it to fill some part
481         // of the page.
482         if (fabs(aSourceWidth - aImgWidth)/aImgWidth < 0.5 || fabs(aSourceHeight - aImgHeight)/aImgHeight < 0.5)
483             return SamplingFilter::POINT;
484 
485         // The image is growing a lot and in more than one direction. Resampling
486         // is slow and doesn't give us very much when growing a lot.
487         return aSamplingFilter;
488     }
489 
490     /* Some notes on other heuristics:
491        The Skia backend also uses nearest for backgrounds that are stretched by
492        a large amount. I'm not sure this is common enough for us to worry about
493        now. It also uses nearest for backgrounds/avoids high quality for images
494        that are very slightly scaled.  I'm also not sure that very slightly
495        scaled backgrounds are common enough us to worry about.
496 
497        We don't currently have much support for doing high quality interpolation.
498        The only place this currently happens is on Quartz and we don't have as
499        much control over it as would be needed. Webkit avoids using high quality
500        resampling during load. It also avoids high quality if the transformation
501        is not just a scale and translation
502 
503        WebKit bug #40045 added code to avoid resampling different parts
504        of an image with different methods by using a resampling hint size.
505        It currently looks unused in WebKit but it's something to watch out for.
506     */
507 
508     return aSamplingFilter;
509 }
510 #else
ReduceResamplingFilter(SamplingFilter aSamplingFilter,int aImgWidth,int aImgHeight,int aSourceWidth,int aSourceHeight)511 static SamplingFilter ReduceResamplingFilter(SamplingFilter aSamplingFilter,
512                                              int aImgWidth, int aImgHeight,
513                                              int aSourceWidth, int aSourceHeight)
514 {
515     // Just pass the filter through unchanged
516     return aSamplingFilter;
517 }
518 #endif
519 
520 #ifdef MOZ_WIDGET_COCOA
521 // Only prescale a temporary surface if we're going to repeat it often.
522 // Scaling is expensive on OS X and without prescaling, we'd scale
523 // every tile of the repeated rect. However, using a temp surface also potentially uses
524 // more memory if the scaled image is large. So only prescale on a temp
525 // surface if we know we're going to repeat the image in either the X or Y axis
526 // multiple times.
527 static bool
ShouldUseTempSurface(Rect aImageRect,Rect aNeededRect)528 ShouldUseTempSurface(Rect aImageRect, Rect aNeededRect)
529 {
530   int repeatX = aNeededRect.width / aImageRect.width;
531   int repeatY = aNeededRect.height / aImageRect.height;
532   return (repeatX >= 5) || (repeatY >= 5);
533 }
534 
535 static bool
PrescaleAndTileDrawable(gfxDrawable * aDrawable,gfxContext * aContext,const ImageRegion & aRegion,Rect aImageRect,const SamplingFilter aSamplingFilter,const SurfaceFormat aFormat,gfxFloat aOpacity,ExtendMode aExtendMode)536 PrescaleAndTileDrawable(gfxDrawable* aDrawable,
537                         gfxContext* aContext,
538                         const ImageRegion& aRegion,
539                         Rect aImageRect,
540                         const SamplingFilter aSamplingFilter,
541                         const SurfaceFormat aFormat,
542                         gfxFloat aOpacity,
543                         ExtendMode aExtendMode)
544 {
545   gfxSize scaleFactor = aContext->CurrentMatrix().ScaleFactors(true);
546   gfxMatrix scaleMatrix = gfxMatrix::Scaling(scaleFactor.width, scaleFactor.height);
547   const float fuzzFactor = 0.01;
548 
549   // If we aren't scaling or translating, don't go down this path
550   if ((FuzzyEqual(scaleFactor.width, 1.0, fuzzFactor) &&
551       FuzzyEqual(scaleFactor.width, 1.0, fuzzFactor)) ||
552       aContext->CurrentMatrix().HasNonAxisAlignedTransform()) {
553     return false;
554   }
555 
556   gfxRect clipExtents = aContext->GetClipExtents();
557 
558   // Inflate by one pixel because bilinear filtering will sample at most
559   // one pixel beyond the computed image pixel coordinate.
560   clipExtents.Inflate(1.0);
561 
562   gfxRect needed = aRegion.IntersectAndRestrict(clipExtents);
563   Rect scaledNeededRect = ToMatrix(scaleMatrix).TransformBounds(ToRect(needed));
564   scaledNeededRect.RoundOut();
565   if (scaledNeededRect.IsEmpty()) {
566     return false;
567   }
568 
569   Rect scaledImageRect = ToMatrix(scaleMatrix).TransformBounds(aImageRect);
570   if (!ShouldUseTempSurface(scaledImageRect, scaledNeededRect)) {
571     return false;
572   }
573 
574   IntSize scaledImageSize((int32_t)scaledImageRect.width,
575                           (int32_t)scaledImageRect.height);
576   if (scaledImageSize.width != scaledImageRect.width ||
577       scaledImageSize.height != scaledImageRect.height) {
578     // If the scaled image isn't pixel aligned, we'll get artifacts
579     // so we have to take the slow path.
580     return false;
581   }
582 
583   RefPtr<DrawTarget> scaledDT =
584     gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(scaledImageSize, aFormat);
585   if (!scaledDT || !scaledDT->IsValid()) {
586     return false;
587   }
588 
589   RefPtr<gfxContext> tmpCtx = gfxContext::CreateOrNull(scaledDT);
590   MOZ_ASSERT(tmpCtx); // already checked the target above
591 
592   scaledDT->SetTransform(ToMatrix(scaleMatrix));
593   gfxRect gfxImageRect(aImageRect.x, aImageRect.y, aImageRect.width, aImageRect.height);
594 
595   // Since this is just the scaled image, we don't want to repeat anything yet.
596   aDrawable->Draw(tmpCtx, gfxImageRect, ExtendMode::CLAMP, aSamplingFilter, 1.0, gfxMatrix());
597 
598   RefPtr<SourceSurface> scaledImage = scaledDT->Snapshot();
599 
600   {
601     gfxContextMatrixAutoSaveRestore autoSR(aContext);
602     Matrix withoutScale = ToMatrix(aContext->CurrentMatrix());
603     DrawTarget* destDrawTarget = aContext->GetDrawTarget();
604 
605     // The translation still is in scaled units
606     withoutScale.PreScale(1.0 / scaleFactor.width, 1.0 / scaleFactor.height);
607     aContext->SetMatrix(ThebesMatrix(withoutScale));
608 
609     DrawOptions drawOptions(aOpacity, aContext->CurrentOp(),
610                             aContext->CurrentAntialiasMode());
611 
612     SurfacePattern scaledImagePattern(scaledImage, aExtendMode,
613                                       Matrix(), aSamplingFilter);
614     destDrawTarget->FillRect(scaledNeededRect, scaledImagePattern, drawOptions);
615   }
616   return true;
617 }
618 #endif // MOZ_WIDGET_COCOA
619 
620 /* static */ void
DrawPixelSnapped(gfxContext * aContext,gfxDrawable * aDrawable,const gfxSize & aImageSize,const ImageRegion & aRegion,const SurfaceFormat aFormat,SamplingFilter aSamplingFilter,uint32_t aImageFlags,gfxFloat aOpacity)621 gfxUtils::DrawPixelSnapped(gfxContext*         aContext,
622                            gfxDrawable*        aDrawable,
623                            const gfxSize&      aImageSize,
624                            const ImageRegion&  aRegion,
625                            const SurfaceFormat aFormat,
626                            SamplingFilter      aSamplingFilter,
627                            uint32_t            aImageFlags,
628                            gfxFloat            aOpacity)
629 {
630     PROFILER_LABEL("gfxUtils", "DrawPixelSnapped",
631       js::ProfileEntry::Category::GRAPHICS);
632 
633     gfxRect imageRect(gfxPoint(0, 0), aImageSize);
634     gfxRect region(aRegion.Rect());
635     ExtendMode extendMode = aRegion.GetExtendMode();
636 
637     RefPtr<gfxDrawable> drawable = aDrawable;
638 
639     aSamplingFilter =
640       ReduceResamplingFilter(aSamplingFilter,
641                              imageRect.Width(), imageRect.Height(),
642                              region.Width(), region.Height());
643 
644     // OK now, the hard part left is to account for the subimage sampling
645     // restriction. If all the transforms involved are just integer
646     // translations, then we assume no resampling will occur so there's
647     // nothing to do.
648     // XXX if only we had source-clipping in cairo!
649 
650     if (aContext->CurrentMatrix().HasNonIntegerTranslation()) {
651         if ((extendMode != ExtendMode::CLAMP) || !aRegion.RestrictionContains(imageRect)) {
652             if (drawable->DrawWithSamplingRect(aContext->GetDrawTarget(),
653                                                aContext->CurrentOp(),
654                                                aContext->CurrentAntialiasMode(),
655                                                aRegion.Rect(),
656                                                aRegion.Restriction(),
657                                                extendMode, aSamplingFilter,
658                                                aOpacity)) {
659               return;
660             }
661 
662 #ifdef MOZ_WIDGET_COCOA
663             if (PrescaleAndTileDrawable(aDrawable, aContext, aRegion,
664                                         ToRect(imageRect), aSamplingFilter,
665                                         aFormat, aOpacity, extendMode)) {
666               return;
667             }
668 #endif
669 
670             // On Mobile, we don't ever want to do this; it has the potential for
671             // allocating very large temporary surfaces, especially since we'll
672             // do full-page snapshots often (see bug 749426).
673 #if !defined(MOZ_GFX_OPTIMIZE_MOBILE)
674             RefPtr<gfxDrawable> restrictedDrawable =
675               CreateSamplingRestrictedDrawable(aDrawable, aContext,
676                                                aRegion, aFormat);
677             if (restrictedDrawable) {
678               drawable.swap(restrictedDrawable);
679 
680               // We no longer need to tile: Either we never needed to, or we already
681               // filled a surface with the tiled pattern; this surface can now be
682               // drawn without tiling.
683               extendMode = ExtendMode::CLAMP;
684             }
685 #endif
686         }
687     }
688 
689     drawable->Draw(aContext, aRegion.Rect(), extendMode, aSamplingFilter,
690                    aOpacity, gfxMatrix());
691 }
692 
693 /* static */ int
ImageFormatToDepth(gfxImageFormat aFormat)694 gfxUtils::ImageFormatToDepth(gfxImageFormat aFormat)
695 {
696     switch (aFormat) {
697         case SurfaceFormat::A8R8G8B8_UINT32:
698             return 32;
699         case SurfaceFormat::X8R8G8B8_UINT32:
700             return 24;
701         case SurfaceFormat::R5G6B5_UINT16:
702             return 16;
703         default:
704             break;
705     }
706     return 0;
707 }
708 
709 /*static*/ void
ClipToRegion(gfxContext * aContext,const nsIntRegion & aRegion)710 gfxUtils::ClipToRegion(gfxContext* aContext, const nsIntRegion& aRegion)
711 {
712   aContext->NewPath();
713   for (auto iter = aRegion.RectIter(); !iter.Done(); iter.Next()) {
714     const IntRect& r = iter.Get();
715     aContext->Rectangle(gfxRect(r.x, r.y, r.width, r.height));
716   }
717   aContext->Clip();
718 }
719 
720 /*static*/ void
ClipToRegion(DrawTarget * aTarget,const nsIntRegion & aRegion)721 gfxUtils::ClipToRegion(DrawTarget* aTarget, const nsIntRegion& aRegion)
722 {
723   uint32_t numRects = aRegion.GetNumRects();
724   // If there is only one rect, then the region bounds are equivalent to the
725   // contents. So just use push a single clip rect with the bounds.
726   if (numRects == 1) {
727     aTarget->PushClipRect(Rect(aRegion.GetBounds()));
728     return;
729   }
730 
731   // Check if the target's transform will preserve axis-alignment and
732   // pixel-alignment for each rect. For now, just handle the common case
733   // of integer translations.
734   Matrix transform = aTarget->GetTransform();
735   if (transform.IsIntegerTranslation()) {
736     IntPoint translation = RoundedToInt(transform.GetTranslation());
737     AutoTArray<IntRect, 16> rects;
738     rects.SetLength(numRects);
739     uint32_t i = 0;
740     // Build the list of transformed rects by adding in the translation.
741     for (auto iter = aRegion.RectIter(); !iter.Done(); iter.Next()) {
742       IntRect rect = iter.Get();
743       rect.MoveBy(translation);
744       rects[i++] = rect;
745     }
746     aTarget->PushDeviceSpaceClipRects(rects.Elements(), rects.Length());
747   } else {
748     // The transform does not produce axis-aligned rects or a rect was not
749     // pixel-aligned. So just build a path with all the rects and clip to it
750     // instead.
751     RefPtr<PathBuilder> pathBuilder = aTarget->CreatePathBuilder();
752     for (auto iter = aRegion.RectIter(); !iter.Done(); iter.Next()) {
753       AppendRectToPath(pathBuilder, Rect(iter.Get()));
754     }
755     RefPtr<Path> path = pathBuilder->Finish();
756     aTarget->PushClip(path);
757   }
758 }
759 
760 /*static*/ gfxFloat
ClampToScaleFactor(gfxFloat aVal)761 gfxUtils::ClampToScaleFactor(gfxFloat aVal)
762 {
763   // Arbitary scale factor limitation. We can increase this
764   // for better scaling performance at the cost of worse
765   // quality.
766   static const gfxFloat kScaleResolution = 2;
767 
768   // Negative scaling is just a flip and irrelevant to
769   // our resolution calculation.
770   if (aVal < 0.0) {
771     aVal = -aVal;
772   }
773 
774   bool inverse = false;
775   if (aVal < 1.0) {
776     inverse = true;
777     aVal = 1 / aVal;
778   }
779 
780   gfxFloat power = log(aVal)/log(kScaleResolution);
781 
782   // If power is within 1e-5 of an integer, round to nearest to
783   // prevent floating point errors, otherwise round up to the
784   // next integer value.
785   if (fabs(power - NS_round(power)) < 1e-5) {
786     power = NS_round(power);
787   } else if (inverse) {
788     power = floor(power);
789   } else {
790     power = ceil(power);
791   }
792 
793   gfxFloat scale = pow(kScaleResolution, power);
794 
795   if (inverse) {
796     scale = 1 / scale;
797   }
798 
799   return scale;
800 }
801 
802 gfxMatrix
TransformRectToRect(const gfxRect & aFrom,const gfxPoint & aToTopLeft,const gfxPoint & aToTopRight,const gfxPoint & aToBottomRight)803 gfxUtils::TransformRectToRect(const gfxRect& aFrom, const gfxPoint& aToTopLeft,
804                               const gfxPoint& aToTopRight, const gfxPoint& aToBottomRight)
805 {
806   gfxMatrix m;
807   if (aToTopRight.y == aToTopLeft.y && aToTopRight.x == aToBottomRight.x) {
808     // Not a rotation, so xy and yx are zero
809     m._21 = m._12 = 0.0;
810     m._11 = (aToBottomRight.x - aToTopLeft.x)/aFrom.width;
811     m._22 = (aToBottomRight.y - aToTopLeft.y)/aFrom.height;
812     m._31 = aToTopLeft.x - m._11*aFrom.x;
813     m._32 = aToTopLeft.y - m._22*aFrom.y;
814   } else {
815     NS_ASSERTION(aToTopRight.y == aToBottomRight.y && aToTopRight.x == aToTopLeft.x,
816                  "Destination rectangle not axis-aligned");
817     m._11 = m._22 = 0.0;
818     m._21 = (aToBottomRight.x - aToTopLeft.x)/aFrom.height;
819     m._12 = (aToBottomRight.y - aToTopLeft.y)/aFrom.width;
820     m._31 = aToTopLeft.x - m._21*aFrom.y;
821     m._32 = aToTopLeft.y - m._12*aFrom.x;
822   }
823   return m;
824 }
825 
826 Matrix
TransformRectToRect(const gfxRect & aFrom,const IntPoint & aToTopLeft,const IntPoint & aToTopRight,const IntPoint & aToBottomRight)827 gfxUtils::TransformRectToRect(const gfxRect& aFrom, const IntPoint& aToTopLeft,
828                               const IntPoint& aToTopRight, const IntPoint& aToBottomRight)
829 {
830   Matrix m;
831   if (aToTopRight.y == aToTopLeft.y && aToTopRight.x == aToBottomRight.x) {
832     // Not a rotation, so xy and yx are zero
833     m._12 = m._21 = 0.0;
834     m._11 = (aToBottomRight.x - aToTopLeft.x)/aFrom.width;
835     m._22 = (aToBottomRight.y - aToTopLeft.y)/aFrom.height;
836     m._31 = aToTopLeft.x - m._11*aFrom.x;
837     m._32 = aToTopLeft.y - m._22*aFrom.y;
838   } else {
839     NS_ASSERTION(aToTopRight.y == aToBottomRight.y && aToTopRight.x == aToTopLeft.x,
840                  "Destination rectangle not axis-aligned");
841     m._11 = m._22 = 0.0;
842     m._21 = (aToBottomRight.x - aToTopLeft.x)/aFrom.height;
843     m._12 = (aToBottomRight.y - aToTopLeft.y)/aFrom.width;
844     m._31 = aToTopLeft.x - m._21*aFrom.y;
845     m._32 = aToTopLeft.y - m._12*aFrom.x;
846   }
847   return m;
848 }
849 
850 /* This function is sort of shitty. We truncate doubles
851  * to ints then convert those ints back to doubles to make sure that
852  * they equal the doubles that we got in. */
853 bool
GfxRectToIntRect(const gfxRect & aIn,IntRect * aOut)854 gfxUtils::GfxRectToIntRect(const gfxRect& aIn, IntRect* aOut)
855 {
856   *aOut = IntRect(int32_t(aIn.X()), int32_t(aIn.Y()),
857   int32_t(aIn.Width()), int32_t(aIn.Height()));
858   return gfxRect(aOut->x, aOut->y, aOut->width, aOut->height).IsEqualEdges(aIn);
859 }
860 
ClearThebesSurface(gfxASurface * aSurface)861 /* static */ void gfxUtils::ClearThebesSurface(gfxASurface* aSurface)
862 {
863   if (aSurface->CairoStatus()) {
864     return;
865   }
866   cairo_surface_t* surf = aSurface->CairoSurface();
867   if (cairo_surface_status(surf)) {
868     return;
869   }
870   cairo_t* ctx = cairo_create(surf);
871   cairo_set_source_rgba(ctx, 0.0, 0.0, 0.0, 0.0);
872   cairo_set_operator(ctx, CAIRO_OPERATOR_SOURCE);
873   IntRect bounds(nsIntPoint(0, 0), aSurface->GetSize());
874   cairo_rectangle(ctx, bounds.x, bounds.y, bounds.width, bounds.height);
875   cairo_fill(ctx);
876   cairo_destroy(ctx);
877 }
878 
879 /* static */ already_AddRefed<DataSourceSurface>
CopySurfaceToDataSourceSurfaceWithFormat(SourceSurface * aSurface,SurfaceFormat aFormat)880 gfxUtils::CopySurfaceToDataSourceSurfaceWithFormat(SourceSurface* aSurface,
881                                                    SurfaceFormat aFormat)
882 {
883   MOZ_ASSERT(aFormat != aSurface->GetFormat(),
884              "Unnecessary - and very expersive - surface format conversion");
885 
886   Rect bounds(0, 0, aSurface->GetSize().width, aSurface->GetSize().height);
887 
888   if (aSurface->GetType() != SurfaceType::DATA) {
889     // If the surface is NOT of type DATA then its data is not mapped into main
890     // memory. Format conversion is probably faster on the GPU, and by doing it
891     // there we can avoid any expensive uploads/readbacks except for (possibly)
892     // a single readback due to the unavoidable GetDataSurface() call. Using
893     // CreateOffscreenContentDrawTarget ensures the conversion happens on the
894     // GPU.
895     RefPtr<DrawTarget> dt = gfxPlatform::GetPlatform()->
896       CreateOffscreenContentDrawTarget(aSurface->GetSize(), aFormat);
897     if (!dt) {
898       gfxWarning() << "gfxUtils::CopySurfaceToDataSourceSurfaceWithFormat failed in CreateOffscreenContentDrawTarget";
899       return nullptr;
900     }
901 
902     // Using DrawSurface() here rather than CopySurface() because CopySurface
903     // is optimized for memcpy and therefore isn't good for format conversion.
904     // Using OP_OVER since in our case it's equivalent to OP_SOURCE and
905     // generally more optimized.
906     dt->DrawSurface(aSurface, bounds, bounds, DrawSurfaceOptions(),
907                     DrawOptions(1.0f, CompositionOp::OP_OVER));
908     RefPtr<SourceSurface> surface = dt->Snapshot();
909     return surface->GetDataSurface();
910   }
911 
912   // If the surface IS of type DATA then it may or may not be in main memory
913   // depending on whether or not it has been mapped yet. We have no way of
914   // knowing, so we can't be sure if it's best to create a data wrapping
915   // DrawTarget for the conversion or an offscreen content DrawTarget. We could
916   // guess it's not mapped and create an offscreen content DrawTarget, but if
917   // it is then we'll end up uploading the surface data, and most likely the
918   // caller is going to be accessing the resulting surface data, resulting in a
919   // readback (both very expensive operations). Alternatively we could guess
920   // the data is mapped and create a data wrapping DrawTarget and, if the
921   // surface is not in main memory, then we will incure a readback. The latter
922   // of these two "wrong choices" is the least costly (a readback, vs an
923   // upload and a readback), and more than likely the DATA surface that we've
924   // been passed actually IS in main memory anyway. For these reasons it's most
925   // likely best to create a data wrapping DrawTarget here to do the format
926   // conversion.
927   RefPtr<DataSourceSurface> dataSurface =
928     Factory::CreateDataSourceSurface(aSurface->GetSize(), aFormat);
929   DataSourceSurface::MappedSurface map;
930   if (!dataSurface ||
931       !dataSurface->Map(DataSourceSurface::MapType::READ_WRITE, &map)) {
932     return nullptr;
933   }
934   RefPtr<DrawTarget> dt =
935     Factory::CreateDrawTargetForData(BackendType::CAIRO,
936                                      map.mData,
937                                      dataSurface->GetSize(),
938                                      map.mStride,
939                                      aFormat);
940   if (!dt) {
941     dataSurface->Unmap();
942     return nullptr;
943   }
944   // Using DrawSurface() here rather than CopySurface() because CopySurface
945   // is optimized for memcpy and therefore isn't good for format conversion.
946   // Using OP_OVER since in our case it's equivalent to OP_SOURCE and
947   // generally more optimized.
948   dt->DrawSurface(aSurface, bounds, bounds, DrawSurfaceOptions(),
949                   DrawOptions(1.0f, CompositionOp::OP_OVER));
950   dataSurface->Unmap();
951   return dataSurface.forget();
952 }
953 
954 const uint32_t gfxUtils::sNumFrameColors = 8;
955 
956 /* static */ const gfx::Color&
GetColorForFrameNumber(uint64_t aFrameNumber)957 gfxUtils::GetColorForFrameNumber(uint64_t aFrameNumber)
958 {
959     static bool initialized = false;
960     static gfx::Color colors[sNumFrameColors];
961 
962     if (!initialized) {
963         uint32_t i = 0;
964         colors[i++] = gfx::Color::FromABGR(0xffff0000);
965         colors[i++] = gfx::Color::FromABGR(0xffcc00ff);
966         colors[i++] = gfx::Color::FromABGR(0xff0066cc);
967         colors[i++] = gfx::Color::FromABGR(0xff00ff00);
968         colors[i++] = gfx::Color::FromABGR(0xff33ffff);
969         colors[i++] = gfx::Color::FromABGR(0xffff0099);
970         colors[i++] = gfx::Color::FromABGR(0xff0000ff);
971         colors[i++] = gfx::Color::FromABGR(0xff999999);
972         MOZ_ASSERT(i == sNumFrameColors);
973         initialized = true;
974     }
975 
976     return colors[aFrameNumber % sNumFrameColors];
977 }
978 
979 static nsresult
EncodeSourceSurfaceInternal(SourceSurface * aSurface,const nsACString & aMimeType,const nsAString & aOutputOptions,gfxUtils::BinaryOrData aBinaryOrData,FILE * aFile,nsCString * aStrOut)980 EncodeSourceSurfaceInternal(SourceSurface* aSurface,
981                            const nsACString& aMimeType,
982                            const nsAString& aOutputOptions,
983                            gfxUtils::BinaryOrData aBinaryOrData,
984                            FILE* aFile,
985                            nsCString* aStrOut)
986 {
987   MOZ_ASSERT(aBinaryOrData == gfxUtils::eDataURIEncode || aFile || aStrOut,
988              "Copying binary encoding to clipboard not currently supported");
989 
990   const IntSize size = aSurface->GetSize();
991   if (size.IsEmpty()) {
992     return NS_ERROR_INVALID_ARG;
993   }
994   const Size floatSize(size.width, size.height);
995 
996   RefPtr<DataSourceSurface> dataSurface;
997   if (aSurface->GetFormat() != SurfaceFormat::B8G8R8A8) {
998     // FIXME bug 995807 (B8G8R8X8), bug 831898 (R5G6B5)
999     dataSurface =
1000       gfxUtils::CopySurfaceToDataSourceSurfaceWithFormat(aSurface,
1001                                                          SurfaceFormat::B8G8R8A8);
1002   } else {
1003     dataSurface = aSurface->GetDataSurface();
1004   }
1005   if (!dataSurface) {
1006     return NS_ERROR_FAILURE;
1007   }
1008 
1009   DataSourceSurface::MappedSurface map;
1010   if (!dataSurface->Map(DataSourceSurface::MapType::READ, &map)) {
1011     return NS_ERROR_FAILURE;
1012   }
1013 
1014   nsAutoCString encoderCID(
1015     NS_LITERAL_CSTRING("@mozilla.org/image/encoder;2?type=") + aMimeType);
1016   nsCOMPtr<imgIEncoder> encoder = do_CreateInstance(encoderCID.get());
1017   if (!encoder) {
1018 #ifdef DEBUG
1019     int32_t w = std::min(size.width, 8);
1020     int32_t h = std::min(size.height, 8);
1021     printf("Could not create encoder. Top-left %dx%d pixels contain:\n", w, h);
1022     for (int32_t y = 0; y < h; ++y) {
1023       for (int32_t x = 0; x < w; ++x) {
1024         printf("%x ", reinterpret_cast<uint32_t*>(map.mData)[y*map.mStride+x]);
1025       }
1026     }
1027 #endif
1028     dataSurface->Unmap();
1029     return NS_ERROR_FAILURE;
1030   }
1031 
1032   nsresult rv = encoder->InitFromData(map.mData,
1033                                       BufferSizeFromStrideAndHeight(map.mStride, size.height),
1034                                       size.width,
1035                                       size.height,
1036                                       map.mStride,
1037                                       imgIEncoder::INPUT_FORMAT_HOSTARGB,
1038                                       aOutputOptions);
1039   dataSurface->Unmap();
1040   NS_ENSURE_SUCCESS(rv, rv);
1041 
1042   nsCOMPtr<nsIInputStream> imgStream;
1043   CallQueryInterface(encoder.get(), getter_AddRefs(imgStream));
1044   if (!imgStream) {
1045     return NS_ERROR_FAILURE;
1046   }
1047 
1048   uint64_t bufSize64;
1049   rv = imgStream->Available(&bufSize64);
1050   NS_ENSURE_SUCCESS(rv, rv);
1051 
1052   NS_ENSURE_TRUE(bufSize64 < UINT32_MAX - 16, NS_ERROR_FAILURE);
1053 
1054   uint32_t bufSize = (uint32_t)bufSize64;
1055 
1056   // ...leave a little extra room so we can call read again and make sure we
1057   // got everything. 16 bytes for better padding (maybe)
1058   bufSize += 16;
1059   uint32_t imgSize = 0;
1060   Vector<char> imgData;
1061   if (!imgData.initCapacity(bufSize)) {
1062     return NS_ERROR_OUT_OF_MEMORY;
1063   }
1064   uint32_t numReadThisTime = 0;
1065   while ((rv = imgStream->Read(imgData.begin() + imgSize,
1066                                bufSize - imgSize,
1067                                &numReadThisTime)) == NS_OK && numReadThisTime > 0)
1068   {
1069     // Update the length of the vector without overwriting the new data.
1070     if (!imgData.growByUninitialized(numReadThisTime)) {
1071       return NS_ERROR_OUT_OF_MEMORY;
1072     }
1073 
1074     imgSize += numReadThisTime;
1075     if (imgSize == bufSize) {
1076       // need a bigger buffer, just double
1077       bufSize *= 2;
1078       if (!imgData.resizeUninitialized(bufSize)) {
1079         return NS_ERROR_OUT_OF_MEMORY;
1080       }
1081     }
1082   }
1083   NS_ENSURE_SUCCESS(rv, rv);
1084   NS_ENSURE_TRUE(!imgData.empty(), NS_ERROR_FAILURE);
1085 
1086   if (aBinaryOrData == gfxUtils::eBinaryEncode) {
1087     if (aFile) {
1088       fwrite(imgData.begin(), 1, imgSize, aFile);
1089     }
1090     return NS_OK;
1091   }
1092 
1093   // base 64, result will be null-terminated
1094   nsCString encodedImg;
1095   rv = Base64Encode(Substring(imgData.begin(), imgSize), encodedImg);
1096   NS_ENSURE_SUCCESS(rv, rv);
1097 
1098   nsCString string("data:");
1099   string.Append(aMimeType);
1100   string.Append(";base64,");
1101   string.Append(encodedImg);
1102 
1103   if (aFile) {
1104 #ifdef ANDROID
1105     if (aFile == stdout || aFile == stderr) {
1106       // ADB logcat cuts off long strings so we will break it down
1107       const char* cStr = string.BeginReading();
1108       size_t len = strlen(cStr);
1109       while (true) {
1110         printf_stderr("IMG: %.140s\n", cStr);
1111         if (len <= 140)
1112           break;
1113         len -= 140;
1114         cStr += 140;
1115       }
1116     }
1117 #endif
1118     fprintf(aFile, "%s", string.BeginReading());
1119   } else if (aStrOut) {
1120     *aStrOut = string;
1121   } else {
1122     nsCOMPtr<nsIClipboardHelper> clipboard(do_GetService("@mozilla.org/widget/clipboardhelper;1", &rv));
1123     if (clipboard) {
1124       clipboard->CopyString(NS_ConvertASCIItoUTF16(string));
1125     }
1126   }
1127   return NS_OK;
1128 }
1129 
1130 static nsCString
EncodeSourceSurfaceAsPNGURI(SourceSurface * aSurface)1131 EncodeSourceSurfaceAsPNGURI(SourceSurface* aSurface)
1132 {
1133   nsCString string;
1134   EncodeSourceSurfaceInternal(aSurface, NS_LITERAL_CSTRING("image/png"),
1135                               EmptyString(), gfxUtils::eDataURIEncode,
1136                               nullptr, &string);
1137   return string;
1138 }
1139 
1140 /* static */ nsresult
EncodeSourceSurface(SourceSurface * aSurface,const nsACString & aMimeType,const nsAString & aOutputOptions,BinaryOrData aBinaryOrData,FILE * aFile)1141 gfxUtils::EncodeSourceSurface(SourceSurface* aSurface,
1142                               const nsACString& aMimeType,
1143                               const nsAString& aOutputOptions,
1144                               BinaryOrData aBinaryOrData,
1145                               FILE* aFile)
1146 {
1147   return EncodeSourceSurfaceInternal(aSurface, aMimeType, aOutputOptions,
1148                                      aBinaryOrData, aFile, nullptr);
1149 }
1150 
1151 /* From Rec601:
1152 [R]   [1.1643835616438356,  0.0,                 1.5960267857142858]      [ Y -  16]
1153 [G] = [1.1643835616438358, -0.3917622900949137, -0.8129676472377708]    x [Cb - 128]
1154 [B]   [1.1643835616438356,  2.017232142857143,   8.862867620416422e-17]   [Cr - 128]
1155 
1156 For [0,1] instead of [0,255], and to 5 places:
1157 [R]   [1.16438,  0.00000,  1.59603]   [ Y - 0.06275]
1158 [G] = [1.16438, -0.39176, -0.81297] x [Cb - 0.50196]
1159 [B]   [1.16438,  2.01723,  0.00000]   [Cr - 0.50196]
1160 
1161 From Rec709:
1162 [R]   [1.1643835616438356,  4.2781193979771426e-17, 1.7927410714285714]     [ Y -  16]
1163 [G] = [1.1643835616438358, -0.21324861427372963,   -0.532909328559444]    x [Cb - 128]
1164 [B]   [1.1643835616438356,  2.1124017857142854,     0.0]                    [Cr - 128]
1165 
1166 For [0,1] instead of [0,255], and to 5 places:
1167 [R]   [1.16438,  0.00000,  1.79274]   [ Y - 0.06275]
1168 [G] = [1.16438, -0.21325, -0.53291] x [Cb - 0.50196]
1169 [B]   [1.16438,  2.11240,  0.00000]   [Cr - 0.50196]
1170 */
1171 
1172 /* static */ float*
Get4x3YuvColorMatrix(YUVColorSpace aYUVColorSpace)1173 gfxUtils::Get4x3YuvColorMatrix(YUVColorSpace aYUVColorSpace)
1174 {
1175   static const float yuv_to_rgb_rec601[12] = { 1.16438f,  0.0f,      1.59603f, 0.0f,
1176                                                1.16438f, -0.39176f, -0.81297f, 0.0f,
1177                                                1.16438f,  2.01723f,  0.0f,     0.0f,
1178                                              };
1179 
1180   static const float yuv_to_rgb_rec709[12] = { 1.16438f,  0.0f,      1.79274f, 0.0f,
1181                                                1.16438f, -0.21325f, -0.53291f, 0.0f,
1182                                                1.16438f,  2.11240f,  0.0f,     0.0f,
1183                                              };
1184 
1185   if (aYUVColorSpace == YUVColorSpace::BT709) {
1186     return const_cast<float*>(yuv_to_rgb_rec709);
1187   } else {
1188     return const_cast<float*>(yuv_to_rgb_rec601);
1189   }
1190 }
1191 
1192 /* static */ float*
Get3x3YuvColorMatrix(YUVColorSpace aYUVColorSpace)1193 gfxUtils::Get3x3YuvColorMatrix(YUVColorSpace aYUVColorSpace)
1194 {
1195   static const float yuv_to_rgb_rec601[9] = {
1196     1.16438f, 1.16438f, 1.16438f, 0.0f, -0.39176f, 2.01723f, 1.59603f, -0.81297f, 0.0f,
1197   };
1198   static const float yuv_to_rgb_rec709[9] = {
1199     1.16438f, 1.16438f, 1.16438f, 0.0f, -0.21325f, 2.11240f, 1.79274f, -0.53291f, 0.0f,
1200   };
1201 
1202   if (aYUVColorSpace == YUVColorSpace::BT709) {
1203     return const_cast<float*>(yuv_to_rgb_rec709);
1204   } else {
1205     return const_cast<float*>(yuv_to_rgb_rec601);
1206   }
1207 }
1208 
1209 /* static */ void
WriteAsPNG(SourceSurface * aSurface,const nsAString & aFile)1210 gfxUtils::WriteAsPNG(SourceSurface* aSurface, const nsAString& aFile)
1211 {
1212   WriteAsPNG(aSurface, NS_ConvertUTF16toUTF8(aFile).get());
1213 }
1214 
1215 /* static */ void
WriteAsPNG(SourceSurface * aSurface,const char * aFile)1216 gfxUtils::WriteAsPNG(SourceSurface* aSurface, const char* aFile)
1217 {
1218   FILE* file = fopen(aFile, "wb");
1219 
1220   if (!file) {
1221     // Maybe the directory doesn't exist; try creating it, then fopen again.
1222     nsresult rv = NS_ERROR_FAILURE;
1223     nsCOMPtr<nsIFile> comFile = do_CreateInstance("@mozilla.org/file/local;1");
1224     if (comFile) {
1225       NS_ConvertUTF8toUTF16 utf16path((nsDependentCString(aFile)));
1226       rv = comFile->InitWithPath(utf16path);
1227       if (NS_SUCCEEDED(rv)) {
1228         nsCOMPtr<nsIFile> dirPath;
1229         comFile->GetParent(getter_AddRefs(dirPath));
1230         if (dirPath) {
1231           rv = dirPath->Create(nsIFile::DIRECTORY_TYPE, 0777);
1232           if (NS_SUCCEEDED(rv) || rv == NS_ERROR_FILE_ALREADY_EXISTS) {
1233             file = fopen(aFile, "wb");
1234           }
1235         }
1236       }
1237     }
1238     if (!file) {
1239       NS_WARNING("Failed to open file to create PNG!");
1240       return;
1241     }
1242   }
1243 
1244   EncodeSourceSurface(aSurface, NS_LITERAL_CSTRING("image/png"),
1245                       EmptyString(), eBinaryEncode, file);
1246   fclose(file);
1247 }
1248 
1249 /* static */ void
WriteAsPNG(DrawTarget * aDT,const nsAString & aFile)1250 gfxUtils::WriteAsPNG(DrawTarget* aDT, const nsAString& aFile)
1251 {
1252   WriteAsPNG(aDT, NS_ConvertUTF16toUTF8(aFile).get());
1253 }
1254 
1255 /* static */ void
WriteAsPNG(DrawTarget * aDT,const char * aFile)1256 gfxUtils::WriteAsPNG(DrawTarget* aDT, const char* aFile)
1257 {
1258   RefPtr<SourceSurface> surface = aDT->Snapshot();
1259   if (surface) {
1260     WriteAsPNG(surface, aFile);
1261   } else {
1262     NS_WARNING("Failed to get surface!");
1263   }
1264 }
1265 
1266 /* static */ void
WriteAsPNG(nsIPresShell * aShell,const char * aFile)1267 gfxUtils::WriteAsPNG(nsIPresShell* aShell, const char* aFile)
1268 {
1269   int32_t width = 1000, height = 1000;
1270   nsRect r(0, 0, aShell->GetPresContext()->DevPixelsToAppUnits(width),
1271            aShell->GetPresContext()->DevPixelsToAppUnits(height));
1272 
1273   RefPtr<mozilla::gfx::DrawTarget> dt = gfxPlatform::GetPlatform()->
1274     CreateOffscreenContentDrawTarget(IntSize(width, height),
1275                                      SurfaceFormat::B8G8R8A8);
1276   NS_ENSURE_TRUE(dt && dt->IsValid(), /*void*/);
1277 
1278   RefPtr<gfxContext> context = gfxContext::CreateOrNull(dt);
1279   MOZ_ASSERT(context); // already checked the draw target above
1280   aShell->RenderDocument(r, 0, NS_RGB(255, 255, 0), context);
1281   WriteAsPNG(dt.get(), aFile);
1282 }
1283 
1284 /* static */ void
DumpAsDataURI(SourceSurface * aSurface,FILE * aFile)1285 gfxUtils::DumpAsDataURI(SourceSurface* aSurface, FILE* aFile)
1286 {
1287   EncodeSourceSurface(aSurface, NS_LITERAL_CSTRING("image/png"),
1288                       EmptyString(), eDataURIEncode, aFile);
1289 }
1290 
1291 /* static */ nsCString
GetAsDataURI(SourceSurface * aSurface)1292 gfxUtils::GetAsDataURI(SourceSurface* aSurface)
1293 {
1294   return EncodeSourceSurfaceAsPNGURI(aSurface);
1295 }
1296 
1297 /* static */ void
DumpAsDataURI(DrawTarget * aDT,FILE * aFile)1298 gfxUtils::DumpAsDataURI(DrawTarget* aDT, FILE* aFile)
1299 {
1300   RefPtr<SourceSurface> surface = aDT->Snapshot();
1301   if (surface) {
1302     DumpAsDataURI(surface, aFile);
1303   } else {
1304     NS_WARNING("Failed to get surface!");
1305   }
1306 }
1307 
1308 /* static */ nsCString
GetAsLZ4Base64Str(DataSourceSurface * aSourceSurface)1309 gfxUtils::GetAsLZ4Base64Str(DataSourceSurface* aSourceSurface)
1310 {
1311   int32_t dataSize = aSourceSurface->GetSize().height * aSourceSurface->Stride();
1312   auto compressedData = MakeUnique<char[]>(LZ4::maxCompressedSize(dataSize));
1313   if (compressedData) {
1314     int nDataSize = LZ4::compress((char*)aSourceSurface->GetData(),
1315                                   dataSize,
1316                                   compressedData.get());
1317     if (nDataSize > 0) {
1318       nsCString encodedImg;
1319       nsresult rv = Base64Encode(Substring(compressedData.get(), nDataSize), encodedImg);
1320       if (rv == NS_OK) {
1321         nsCString string("");
1322         string.AppendPrintf("data:image/lz4bgra;base64,%i,%i,%i,",
1323                              aSourceSurface->GetSize().width,
1324                              aSourceSurface->Stride(),
1325                              aSourceSurface->GetSize().height);
1326         string.Append(encodedImg);
1327         return string;
1328       }
1329     }
1330   }
1331   return nsCString("");
1332 }
1333 
1334 /* static */ nsCString
GetAsDataURI(DrawTarget * aDT)1335 gfxUtils::GetAsDataURI(DrawTarget* aDT)
1336 {
1337   RefPtr<SourceSurface> surface = aDT->Snapshot();
1338   if (surface) {
1339     return EncodeSourceSurfaceAsPNGURI(surface);
1340   } else {
1341     NS_WARNING("Failed to get surface!");
1342     return nsCString("");
1343   }
1344 }
1345 
1346 /* static */ void
CopyAsDataURI(SourceSurface * aSurface)1347 gfxUtils::CopyAsDataURI(SourceSurface* aSurface)
1348 {
1349   EncodeSourceSurface(aSurface, NS_LITERAL_CSTRING("image/png"),
1350                       EmptyString(), eDataURIEncode, nullptr);
1351 }
1352 
1353 /* static */ void
CopyAsDataURI(DrawTarget * aDT)1354 gfxUtils::CopyAsDataURI(DrawTarget* aDT)
1355 {
1356   RefPtr<SourceSurface> surface = aDT->Snapshot();
1357   if (surface) {
1358     CopyAsDataURI(surface);
1359   } else {
1360     NS_WARNING("Failed to get surface!");
1361   }
1362 }
1363 
1364 /* static */ UniquePtr<uint8_t[]>
GetImageBuffer(gfx::DataSourceSurface * aSurface,bool aIsAlphaPremultiplied,int32_t * outFormat)1365 gfxUtils::GetImageBuffer(gfx::DataSourceSurface* aSurface,
1366                          bool aIsAlphaPremultiplied,
1367                          int32_t* outFormat)
1368 {
1369     *outFormat = 0;
1370 
1371     DataSourceSurface::MappedSurface map;
1372     if (!aSurface->Map(DataSourceSurface::MapType::READ, &map))
1373         return nullptr;
1374 
1375     uint32_t bufferSize = aSurface->GetSize().width * aSurface->GetSize().height * 4;
1376     auto imageBuffer = MakeUniqueFallible<uint8_t[]>(bufferSize);
1377     if (!imageBuffer) {
1378         aSurface->Unmap();
1379         return nullptr;
1380     }
1381     memcpy(imageBuffer.get(), map.mData, bufferSize);
1382 
1383     aSurface->Unmap();
1384 
1385     int32_t format = imgIEncoder::INPUT_FORMAT_HOSTARGB;
1386     if (!aIsAlphaPremultiplied) {
1387         // We need to convert to INPUT_FORMAT_RGBA, otherwise
1388         // we are automatically considered premult, and unpremult'd.
1389         // Yes, it is THAT silly.
1390         // Except for different lossy conversions by color,
1391         // we could probably just change the label, and not change the data.
1392         gfxUtils::ConvertBGRAtoRGBA(imageBuffer.get(), bufferSize);
1393         format = imgIEncoder::INPUT_FORMAT_RGBA;
1394     }
1395 
1396     *outFormat = format;
1397     return imageBuffer;
1398 }
1399 
1400 /* static */ nsresult
GetInputStream(gfx::DataSourceSurface * aSurface,bool aIsAlphaPremultiplied,const char * aMimeType,const char16_t * aEncoderOptions,nsIInputStream ** outStream)1401 gfxUtils::GetInputStream(gfx::DataSourceSurface* aSurface,
1402                          bool aIsAlphaPremultiplied,
1403                          const char* aMimeType,
1404                          const char16_t* aEncoderOptions,
1405                          nsIInputStream** outStream)
1406 {
1407     nsCString enccid("@mozilla.org/image/encoder;2?type=");
1408     enccid += aMimeType;
1409     nsCOMPtr<imgIEncoder> encoder = do_CreateInstance(enccid.get());
1410     if (!encoder)
1411         return NS_ERROR_FAILURE;
1412 
1413     int32_t format = 0;
1414     UniquePtr<uint8_t[]> imageBuffer = GetImageBuffer(aSurface, aIsAlphaPremultiplied, &format);
1415     if (!imageBuffer)
1416         return NS_ERROR_FAILURE;
1417 
1418     return dom::ImageEncoder::GetInputStream(aSurface->GetSize().width,
1419                                              aSurface->GetSize().height,
1420                                              imageBuffer.get(), format,
1421                                              encoder, aEncoderOptions, outStream);
1422 }
1423 
1424 class GetFeatureStatusRunnable final : public dom::workers::WorkerMainThreadRunnable
1425 {
1426 public:
GetFeatureStatusRunnable(dom::workers::WorkerPrivate * workerPrivate,const nsCOMPtr<nsIGfxInfo> & gfxInfo,int32_t feature,nsACString & failureId,int32_t * status)1427     GetFeatureStatusRunnable(dom::workers::WorkerPrivate* workerPrivate,
1428                              const nsCOMPtr<nsIGfxInfo>& gfxInfo,
1429                              int32_t feature,
1430                              nsACString& failureId,
1431                              int32_t* status)
1432       : WorkerMainThreadRunnable(workerPrivate,
1433                                  NS_LITERAL_CSTRING("GFX :: GetFeatureStatus"))
1434       , mGfxInfo(gfxInfo)
1435       , mFeature(feature)
1436       , mStatus(status)
1437       , mFailureId(failureId)
1438       , mNSResult(NS_OK)
1439     {
1440     }
1441 
MainThreadRun()1442     bool MainThreadRun() override
1443     {
1444       if (mGfxInfo) {
1445         mNSResult = mGfxInfo->GetFeatureStatus(mFeature, mFailureId, mStatus);
1446       }
1447       return true;
1448     }
1449 
GetNSResult() const1450     nsresult GetNSResult() const
1451     {
1452       return mNSResult;
1453     }
1454 
1455 protected:
~GetFeatureStatusRunnable()1456     ~GetFeatureStatusRunnable() {}
1457 
1458 private:
1459     nsCOMPtr<nsIGfxInfo> mGfxInfo;
1460     int32_t mFeature;
1461     int32_t* mStatus;
1462     nsACString& mFailureId;
1463     nsresult mNSResult;
1464 };
1465 
1466 /* static */ nsresult
ThreadSafeGetFeatureStatus(const nsCOMPtr<nsIGfxInfo> & gfxInfo,int32_t feature,nsACString & failureId,int32_t * status)1467 gfxUtils::ThreadSafeGetFeatureStatus(const nsCOMPtr<nsIGfxInfo>& gfxInfo,
1468                                      int32_t feature, nsACString& failureId,
1469                                      int32_t* status)
1470 {
1471   if (!NS_IsMainThread()) {
1472     dom::workers::WorkerPrivate* workerPrivate =
1473       dom::workers::GetCurrentThreadWorkerPrivate();
1474 
1475     RefPtr<GetFeatureStatusRunnable> runnable =
1476       new GetFeatureStatusRunnable(workerPrivate, gfxInfo, feature, failureId,
1477                                    status);
1478 
1479     ErrorResult rv;
1480     runnable->Dispatch(rv);
1481     if (rv.Failed()) {
1482         // XXXbz This is totally broken, since we're supposed to just abort
1483         // everything up the callstack but the callers basically eat the
1484         // exception.  Ah, well.
1485         return rv.StealNSResult();
1486     }
1487 
1488     return runnable->GetNSResult();
1489   }
1490 
1491   return gfxInfo->GetFeatureStatus(feature, failureId, status);
1492 }
1493 
1494 /* static */ bool
IsFeatureBlacklisted(nsCOMPtr<nsIGfxInfo> gfxInfo,int32_t feature,nsACString * const out_blacklistId)1495 gfxUtils::IsFeatureBlacklisted(nsCOMPtr<nsIGfxInfo> gfxInfo, int32_t feature,
1496                                nsACString* const out_blacklistId)
1497 {
1498   if (!gfxInfo) {
1499     gfxInfo = services::GetGfxInfo();
1500   }
1501 
1502   int32_t status;
1503   if (!NS_SUCCEEDED(gfxUtils::ThreadSafeGetFeatureStatus(gfxInfo, feature,
1504                                                          *out_blacklistId, &status)))
1505   {
1506     out_blacklistId->AssignLiteral("");
1507     return true;
1508   }
1509 
1510   return status != nsIGfxInfo::FEATURE_STATUS_OK;
1511 }
1512 
1513 /* static */ bool
DumpDisplayList()1514 gfxUtils::DumpDisplayList() {
1515   return gfxPrefs::LayoutDumpDisplayList() ||
1516          (gfxPrefs::LayoutDumpDisplayListContent() && XRE_IsContentProcess());
1517 }
1518 
1519 FILE *gfxUtils::sDumpPaintFile = stderr;
1520 
1521 namespace mozilla {
1522 namespace gfx {
1523 
ToDeviceColor(Color aColor)1524 Color ToDeviceColor(Color aColor)
1525 {
1526   // aColor is pass-by-value since to get return value optimization goodness we
1527   // need to return the same object from all return points in this function. We
1528   // could declare a local Color variable and use that, but we might as well
1529   // just use aColor.
1530   if (gfxPlatform::GetCMSMode() == eCMSMode_All) {
1531     qcms_transform *transform = gfxPlatform::GetCMSRGBTransform();
1532     if (transform) {
1533       gfxPlatform::TransformPixel(aColor, aColor, transform);
1534       // Use the original alpha to avoid unnecessary float->byte->float
1535       // conversion errors
1536     }
1537   }
1538   return aColor;
1539 }
1540 
ToDeviceColor(nscolor aColor)1541 Color ToDeviceColor(nscolor aColor)
1542 {
1543   return ToDeviceColor(Color::FromABGR(aColor));
1544 }
1545 
1546 } // namespace gfx
1547 } // namespace mozilla
1548