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