Bumping manifests a=b2g-bump
[gecko.git] / gfx / thebes / gfxUtils.cpp
blob4cd1990137fc64c9dab057de1ce48c671911096f
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/. */
6 #include "gfxUtils.h"
8 #include "cairo.h"
9 #include "gfxContext.h"
10 #include "gfxImageSurface.h"
11 #include "gfxPlatform.h"
12 #include "gfxDrawable.h"
13 #include "imgIEncoder.h"
14 #include "mozilla/Base64.h"
15 #include "mozilla/gfx/2D.h"
16 #include "mozilla/gfx/DataSurfaceHelpers.h"
17 #include "mozilla/Maybe.h"
18 #include "mozilla/RefPtr.h"
19 #include "mozilla/Vector.h"
20 #include "nsComponentManagerUtils.h"
21 #include "nsIClipboardHelper.h"
22 #include "nsIFile.h"
23 #include "nsIPresShell.h"
24 #include "nsPresContext.h"
25 #include "nsRegion.h"
26 #include "nsServiceManagerUtils.h"
27 #include "yuv_convert.h"
28 #include "ycbcr_to_rgb565.h"
29 #include "GeckoProfiler.h"
30 #include "ImageContainer.h"
31 #include "ImageRegion.h"
32 #include "gfx2DGlue.h"
33 #include "gfxPrefs.h"
35 #ifdef XP_WIN
36 #include "gfxWindowsPlatform.h"
37 #endif
39 using namespace mozilla;
40 using namespace mozilla::image;
41 using namespace mozilla::layers;
42 using namespace mozilla::gfx;
44 #include "DeprecatedPremultiplyTables.h"
46 #undef compress
47 #include "mozilla/Compression.h"
49 using namespace mozilla::Compression;
50 extern "C" {
52 /**
53 * Dump a raw image to the default log. This function is exported
54 * from libxul, so it can be called from any library in addition to
55 * (of course) from a debugger.
57 * Note: this helper currently assumes that all 2-bytepp images are
58 * r5g6b5, and that all 4-bytepp images are r8g8b8a8.
60 NS_EXPORT
61 void mozilla_dump_image(void* bytes, int width, int height, int bytepp,
62 int strideBytes)
64 if (0 == strideBytes) {
65 strideBytes = width * bytepp;
67 SurfaceFormat format;
68 // TODO more flexible; parse string?
69 switch (bytepp) {
70 case 2:
71 format = SurfaceFormat::R5G6B5;
72 break;
73 case 4:
74 default:
75 format = SurfaceFormat::R8G8B8A8;
76 break;
79 RefPtr<DataSourceSurface> surf =
80 Factory::CreateWrappingDataSourceSurface((uint8_t*)bytes, strideBytes,
81 gfx::IntSize(width, height),
82 format);
83 gfxUtils::DumpAsDataURI(surf);
88 static const uint8_t PremultiplyValue(uint8_t a, uint8_t v) {
89 return gfxUtils::sPremultiplyTable[a*256+v];
92 static const uint8_t UnpremultiplyValue(uint8_t a, uint8_t v) {
93 return gfxUtils::sUnpremultiplyTable[a*256+v];
96 static void
97 PremultiplyData(const uint8_t* srcData,
98 size_t srcStride, // row-to-row stride in bytes
99 uint8_t* destData,
100 size_t destStride, // row-to-row stride in bytes
101 size_t pixelWidth,
102 size_t rowCount)
104 MOZ_ASSERT(srcData && destData);
106 for (size_t y = 0; y < rowCount; ++y) {
107 const uint8_t* src = srcData + y * srcStride;
108 uint8_t* dest = destData + y * destStride;
110 for (size_t x = 0; x < pixelWidth; ++x) {
111 #ifdef IS_LITTLE_ENDIAN
112 uint8_t b = *src++;
113 uint8_t g = *src++;
114 uint8_t r = *src++;
115 uint8_t a = *src++;
117 *dest++ = PremultiplyValue(a, b);
118 *dest++ = PremultiplyValue(a, g);
119 *dest++ = PremultiplyValue(a, r);
120 *dest++ = a;
121 #else
122 uint8_t a = *src++;
123 uint8_t r = *src++;
124 uint8_t g = *src++;
125 uint8_t b = *src++;
127 *dest++ = a;
128 *dest++ = PremultiplyValue(a, r);
129 *dest++ = PremultiplyValue(a, g);
130 *dest++ = PremultiplyValue(a, b);
131 #endif
135 static void
136 UnpremultiplyData(const uint8_t* srcData,
137 size_t srcStride, // row-to-row stride in bytes
138 uint8_t* destData,
139 size_t destStride, // row-to-row stride in bytes
140 size_t pixelWidth,
141 size_t rowCount)
143 MOZ_ASSERT(srcData && destData);
145 for (size_t y = 0; y < rowCount; ++y) {
146 const uint8_t* src = srcData + y * srcStride;
147 uint8_t* dest = destData + y * destStride;
149 for (size_t x = 0; x < pixelWidth; ++x) {
150 #ifdef IS_LITTLE_ENDIAN
151 uint8_t b = *src++;
152 uint8_t g = *src++;
153 uint8_t r = *src++;
154 uint8_t a = *src++;
156 *dest++ = UnpremultiplyValue(a, b);
157 *dest++ = UnpremultiplyValue(a, g);
158 *dest++ = UnpremultiplyValue(a, r);
159 *dest++ = a;
160 #else
161 uint8_t a = *src++;
162 uint8_t r = *src++;
163 uint8_t g = *src++;
164 uint8_t b = *src++;
166 *dest++ = a;
167 *dest++ = UnpremultiplyValue(a, r);
168 *dest++ = UnpremultiplyValue(a, g);
169 *dest++ = UnpremultiplyValue(a, b);
170 #endif
175 static bool
176 MapSrcDest(DataSourceSurface* srcSurf,
177 DataSourceSurface* destSurf,
178 DataSourceSurface::MappedSurface* out_srcMap,
179 DataSourceSurface::MappedSurface* out_destMap)
181 MOZ_ASSERT(srcSurf && destSurf);
182 MOZ_ASSERT(out_srcMap && out_destMap);
184 if (srcSurf->GetFormat() != SurfaceFormat::B8G8R8A8 ||
185 destSurf->GetFormat() != SurfaceFormat::B8G8R8A8)
187 MOZ_ASSERT(false, "Only operate on BGRA8 surfs.");
188 return false;
191 if (srcSurf->GetSize().width != destSurf->GetSize().width ||
192 srcSurf->GetSize().height != destSurf->GetSize().height)
194 MOZ_ASSERT(false, "Width and height must match.");
195 return false;
198 if (srcSurf == destSurf) {
199 DataSourceSurface::MappedSurface map;
200 if (!srcSurf->Map(DataSourceSurface::MapType::READ_WRITE, &map)) {
201 NS_WARNING("Couldn't Map srcSurf/destSurf.");
202 return false;
205 *out_srcMap = map;
206 *out_destMap = map;
207 return true;
210 // Map src for reading.
211 DataSourceSurface::MappedSurface srcMap;
212 if (!srcSurf->Map(DataSourceSurface::MapType::READ, &srcMap)) {
213 NS_WARNING("Couldn't Map srcSurf.");
214 return false;
217 // Map dest for writing.
218 DataSourceSurface::MappedSurface destMap;
219 if (!destSurf->Map(DataSourceSurface::MapType::WRITE, &destMap)) {
220 NS_WARNING("Couldn't Map aDest.");
221 srcSurf->Unmap();
222 return false;
225 *out_srcMap = srcMap;
226 *out_destMap = destMap;
227 return true;
230 static void
231 UnmapSrcDest(DataSourceSurface* srcSurf,
232 DataSourceSurface* destSurf)
234 if (srcSurf == destSurf) {
235 srcSurf->Unmap();
236 } else {
237 srcSurf->Unmap();
238 destSurf->Unmap();
242 bool
243 gfxUtils::PremultiplyDataSurface(DataSourceSurface* srcSurf,
244 DataSourceSurface* destSurf)
246 MOZ_ASSERT(srcSurf && destSurf);
248 DataSourceSurface::MappedSurface srcMap;
249 DataSourceSurface::MappedSurface destMap;
250 if (!MapSrcDest(srcSurf, destSurf, &srcMap, &destMap))
251 return false;
253 PremultiplyData(srcMap.mData, srcMap.mStride,
254 destMap.mData, destMap.mStride,
255 srcSurf->GetSize().width,
256 srcSurf->GetSize().height);
258 UnmapSrcDest(srcSurf, destSurf);
259 return true;
262 bool
263 gfxUtils::UnpremultiplyDataSurface(DataSourceSurface* srcSurf,
264 DataSourceSurface* destSurf)
266 MOZ_ASSERT(srcSurf && destSurf);
268 DataSourceSurface::MappedSurface srcMap;
269 DataSourceSurface::MappedSurface destMap;
270 if (!MapSrcDest(srcSurf, destSurf, &srcMap, &destMap))
271 return false;
273 UnpremultiplyData(srcMap.mData, srcMap.mStride,
274 destMap.mData, destMap.mStride,
275 srcSurf->GetSize().width,
276 srcSurf->GetSize().height);
278 UnmapSrcDest(srcSurf, destSurf);
279 return true;
282 static bool
283 MapSrcAndCreateMappedDest(DataSourceSurface* srcSurf,
284 RefPtr<DataSourceSurface>* out_destSurf,
285 DataSourceSurface::MappedSurface* out_srcMap,
286 DataSourceSurface::MappedSurface* out_destMap)
288 MOZ_ASSERT(srcSurf);
289 MOZ_ASSERT(out_destSurf && out_srcMap && out_destMap);
291 if (srcSurf->GetFormat() != SurfaceFormat::B8G8R8A8) {
292 MOZ_ASSERT(false, "Only operate on BGRA8.");
293 return false;
296 // Ok, map source for reading.
297 DataSourceSurface::MappedSurface srcMap;
298 if (!srcSurf->Map(DataSourceSurface::MapType::READ, &srcMap)) {
299 MOZ_ASSERT(false, "Couldn't Map srcSurf.");
300 return false;
303 // Make our dest surface based on the src.
304 RefPtr<DataSourceSurface> destSurf =
305 Factory::CreateDataSourceSurfaceWithStride(srcSurf->GetSize(),
306 srcSurf->GetFormat(),
307 srcMap.mStride);
308 if (NS_WARN_IF(!destSurf)) {
309 return false;
312 DataSourceSurface::MappedSurface destMap;
313 if (!destSurf->Map(DataSourceSurface::MapType::WRITE, &destMap)) {
314 MOZ_ASSERT(false, "Couldn't Map destSurf.");
315 srcSurf->Unmap();
316 return false;
319 *out_destSurf = destSurf;
320 *out_srcMap = srcMap;
321 *out_destMap = destMap;
322 return true;
325 TemporaryRef<DataSourceSurface>
326 gfxUtils::CreatePremultipliedDataSurface(DataSourceSurface* srcSurf)
328 RefPtr<DataSourceSurface> destSurf;
329 DataSourceSurface::MappedSurface srcMap;
330 DataSourceSurface::MappedSurface destMap;
331 if (!MapSrcAndCreateMappedDest(srcSurf, &destSurf, &srcMap, &destMap)) {
332 MOZ_ASSERT(false, "MapSrcAndCreateMappedDest failed.");
333 return srcSurf;
336 PremultiplyData(srcMap.mData, srcMap.mStride,
337 destMap.mData, destMap.mStride,
338 srcSurf->GetSize().width,
339 srcSurf->GetSize().height);
341 UnmapSrcDest(srcSurf, destSurf);
342 return destSurf;
345 TemporaryRef<DataSourceSurface>
346 gfxUtils::CreateUnpremultipliedDataSurface(DataSourceSurface* srcSurf)
348 RefPtr<DataSourceSurface> destSurf;
349 DataSourceSurface::MappedSurface srcMap;
350 DataSourceSurface::MappedSurface destMap;
351 if (!MapSrcAndCreateMappedDest(srcSurf, &destSurf, &srcMap, &destMap)) {
352 MOZ_ASSERT(false, "MapSrcAndCreateMappedDest failed.");
353 return srcSurf;
356 UnpremultiplyData(srcMap.mData, srcMap.mStride,
357 destMap.mData, destMap.mStride,
358 srcSurf->GetSize().width,
359 srcSurf->GetSize().height);
361 UnmapSrcDest(srcSurf, destSurf);
362 return destSurf;
365 void
366 gfxUtils::ConvertBGRAtoRGBA(uint8_t* aData, uint32_t aLength)
368 MOZ_ASSERT((aLength % 4) == 0, "Loop below will pass srcEnd!");
370 uint8_t *src = aData;
371 uint8_t *srcEnd = src + aLength;
373 uint8_t buffer[4];
374 for (; src != srcEnd; src += 4) {
375 buffer[0] = src[2];
376 buffer[1] = src[1];
377 buffer[2] = src[0];
379 src[0] = buffer[0];
380 src[1] = buffer[1];
381 src[2] = buffer[2];
385 static bool
386 IsSafeImageTransformComponent(gfxFloat aValue)
388 return aValue >= -32768 && aValue <= 32767;
391 #ifndef MOZ_GFX_OPTIMIZE_MOBILE
393 * This returns the fastest operator to use for solid surfaces which have no
394 * alpha channel or their alpha channel is uniformly opaque.
395 * This differs per render mode.
397 static gfxContext::GraphicsOperator
398 OptimalFillOperator()
400 #ifdef XP_WIN
401 if (gfxWindowsPlatform::GetPlatform()->GetRenderMode() ==
402 gfxWindowsPlatform::RENDER_DIRECT2D) {
403 // D2D -really- hates operator source.
404 return gfxContext::OPERATOR_OVER;
405 } else {
406 #endif
407 return gfxContext::OPERATOR_SOURCE;
408 #ifdef XP_WIN
410 #endif
413 // EXTEND_PAD won't help us here; we have to create a temporary surface to hold
414 // the subimage of pixels we're allowed to sample.
415 static already_AddRefed<gfxDrawable>
416 CreateSamplingRestrictedDrawable(gfxDrawable* aDrawable,
417 gfxContext* aContext,
418 const ImageRegion& aRegion,
419 const SurfaceFormat aFormat)
421 PROFILER_LABEL("gfxUtils", "CreateSamplingRestricedDrawable",
422 js::ProfileEntry::Category::GRAPHICS);
424 gfxRect clipExtents = aContext->GetClipExtents();
426 // Inflate by one pixel because bilinear filtering will sample at most
427 // one pixel beyond the computed image pixel coordinate.
428 clipExtents.Inflate(1.0);
430 gfxRect needed = aRegion.IntersectAndRestrict(clipExtents);
431 needed.RoundOut();
433 // if 'needed' is empty, nothing will be drawn since aFill
434 // must be entirely outside the clip region, so it doesn't
435 // matter what we do here, but we should avoid trying to
436 // create a zero-size surface.
437 if (needed.IsEmpty())
438 return nullptr;
440 gfxIntSize size(int32_t(needed.Width()), int32_t(needed.Height()));
442 RefPtr<DrawTarget> target =
443 gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(ToIntSize(size),
444 aFormat);
445 if (!target) {
446 return nullptr;
449 nsRefPtr<gfxContext> tmpCtx = new gfxContext(target);
450 tmpCtx->SetOperator(OptimalFillOperator());
451 aDrawable->Draw(tmpCtx, needed - needed.TopLeft(), true,
452 GraphicsFilter::FILTER_FAST, 1.0, gfxMatrix::Translation(needed.TopLeft()));
453 RefPtr<SourceSurface> surface = target->Snapshot();
455 nsRefPtr<gfxDrawable> drawable = new gfxSurfaceDrawable(surface, size, gfxMatrix::Translation(-needed.TopLeft()));
456 return drawable.forget();
458 #endif // !MOZ_GFX_OPTIMIZE_MOBILE
460 // working around cairo/pixman bug (bug 364968)
461 // Our device-space-to-image-space transform may not be acceptable to pixman.
462 struct MOZ_STACK_CLASS AutoCairoPixmanBugWorkaround
464 AutoCairoPixmanBugWorkaround(gfxContext* aContext,
465 const gfxMatrix& aDeviceSpaceToImageSpace,
466 const gfxRect& aFill,
467 const gfxASurface* aSurface)
468 : mContext(aContext), mSucceeded(true), mPushedGroup(false)
470 // Quartz's limits for matrix are much larger than pixman
471 if (!aSurface || aSurface->GetType() == gfxSurfaceType::Quartz)
472 return;
474 if (!IsSafeImageTransformComponent(aDeviceSpaceToImageSpace._11) ||
475 !IsSafeImageTransformComponent(aDeviceSpaceToImageSpace._21) ||
476 !IsSafeImageTransformComponent(aDeviceSpaceToImageSpace._12) ||
477 !IsSafeImageTransformComponent(aDeviceSpaceToImageSpace._22)) {
478 NS_WARNING("Scaling up too much, bailing out");
479 mSucceeded = false;
480 return;
483 if (IsSafeImageTransformComponent(aDeviceSpaceToImageSpace._31) &&
484 IsSafeImageTransformComponent(aDeviceSpaceToImageSpace._32))
485 return;
487 // We'll push a group, which will hopefully reduce our transform's
488 // translation so it's in bounds.
489 gfxMatrix currentMatrix = mContext->CurrentMatrix();
490 mContext->Save();
492 // Clip the rounded-out-to-device-pixels bounds of the
493 // transformed fill area. This is the area for the group we
494 // want to push.
495 mContext->SetMatrix(gfxMatrix());
496 gfxRect bounds = currentMatrix.TransformBounds(aFill);
497 bounds.RoundOut();
498 mContext->Clip(bounds);
499 mContext->SetMatrix(currentMatrix);
500 mContext->PushGroup(gfxContentType::COLOR_ALPHA);
501 mContext->SetOperator(gfxContext::OPERATOR_OVER);
503 mPushedGroup = true;
506 ~AutoCairoPixmanBugWorkaround()
508 if (mPushedGroup) {
509 mContext->PopGroupToSource();
510 mContext->Paint();
511 mContext->Restore();
515 bool PushedGroup() { return mPushedGroup; }
516 bool Succeeded() { return mSucceeded; }
518 private:
519 gfxContext* mContext;
520 bool mSucceeded;
521 bool mPushedGroup;
524 static gfxMatrix
525 DeviceToImageTransform(gfxContext* aContext)
527 gfxFloat deviceX, deviceY;
528 nsRefPtr<gfxASurface> currentTarget =
529 aContext->CurrentSurface(&deviceX, &deviceY);
530 gfxMatrix deviceToUser = aContext->CurrentMatrix();
531 if (!deviceToUser.Invert()) {
532 return gfxMatrix(0, 0, 0, 0, 0, 0); // singular
534 deviceToUser.Translate(-gfxPoint(-deviceX, -deviceY));
535 return deviceToUser;
538 /* These heuristics are based on Source/WebCore/platform/graphics/skia/ImageSkia.cpp:computeResamplingMode() */
539 #ifdef MOZ_GFX_OPTIMIZE_MOBILE
540 static GraphicsFilter ReduceResamplingFilter(GraphicsFilter aFilter,
541 int aImgWidth, int aImgHeight,
542 float aSourceWidth, float aSourceHeight)
544 // Images smaller than this in either direction are considered "small" and
545 // are not resampled ever (see below).
546 const int kSmallImageSizeThreshold = 8;
548 // The amount an image can be stretched in a single direction before we
549 // say that it is being stretched so much that it must be a line or
550 // background that doesn't need resampling.
551 const float kLargeStretch = 3.0f;
553 if (aImgWidth <= kSmallImageSizeThreshold
554 || aImgHeight <= kSmallImageSizeThreshold) {
555 // Never resample small images. These are often used for borders and
556 // rules (think 1x1 images used to make lines).
557 return GraphicsFilter::FILTER_NEAREST;
560 if (aImgHeight * kLargeStretch <= aSourceHeight || aImgWidth * kLargeStretch <= aSourceWidth) {
561 // Large image tiling detected.
563 // Don't resample if it is being tiled a lot in only one direction.
564 // This is trying to catch cases where somebody has created a border
565 // (which might be large) and then is stretching it to fill some part
566 // of the page.
567 if (fabs(aSourceWidth - aImgWidth)/aImgWidth < 0.5 || fabs(aSourceHeight - aImgHeight)/aImgHeight < 0.5)
568 return GraphicsFilter::FILTER_NEAREST;
570 // The image is growing a lot and in more than one direction. Resampling
571 // is slow and doesn't give us very much when growing a lot.
572 return aFilter;
575 /* Some notes on other heuristics:
576 The Skia backend also uses nearest for backgrounds that are stretched by
577 a large amount. I'm not sure this is common enough for us to worry about
578 now. It also uses nearest for backgrounds/avoids high quality for images
579 that are very slightly scaled. I'm also not sure that very slightly
580 scaled backgrounds are common enough us to worry about.
582 We don't currently have much support for doing high quality interpolation.
583 The only place this currently happens is on Quartz and we don't have as
584 much control over it as would be needed. Webkit avoids using high quality
585 resampling during load. It also avoids high quality if the transformation
586 is not just a scale and translation
588 WebKit bug #40045 added code to avoid resampling different parts
589 of an image with different methods by using a resampling hint size.
590 It currently looks unused in WebKit but it's something to watch out for.
593 return aFilter;
595 #else
596 static GraphicsFilter ReduceResamplingFilter(GraphicsFilter aFilter,
597 int aImgWidth, int aImgHeight,
598 int aSourceWidth, int aSourceHeight)
600 // Just pass the filter through unchanged
601 return aFilter;
603 #endif
605 /* static */ void
606 gfxUtils::DrawPixelSnapped(gfxContext* aContext,
607 gfxDrawable* aDrawable,
608 const gfxSize& aImageSize,
609 const ImageRegion& aRegion,
610 const SurfaceFormat aFormat,
611 GraphicsFilter aFilter,
612 uint32_t aImageFlags,
613 gfxFloat aOpacity)
615 PROFILER_LABEL("gfxUtils", "DrawPixelSnapped",
616 js::ProfileEntry::Category::GRAPHICS);
618 gfxRect imageRect(gfxPoint(0, 0), aImageSize);
619 gfxRect region(aRegion.Rect());
621 bool doTile = !imageRect.Contains(region) &&
622 !(aImageFlags & imgIContainer::FLAG_CLAMP);
624 nsRefPtr<gfxASurface> currentTarget = aContext->CurrentSurface();
625 gfxMatrix deviceSpaceToImageSpace = DeviceToImageTransform(aContext);
627 AutoCairoPixmanBugWorkaround workaround(aContext, deviceSpaceToImageSpace,
628 region, currentTarget);
629 if (!workaround.Succeeded())
630 return;
632 nsRefPtr<gfxDrawable> drawable = aDrawable;
634 aFilter = ReduceResamplingFilter(aFilter,
635 imageRect.Width(), imageRect.Height(),
636 region.Width(), region.Height());
638 // OK now, the hard part left is to account for the subimage sampling
639 // restriction. If all the transforms involved are just integer
640 // translations, then we assume no resampling will occur so there's
641 // nothing to do.
642 // XXX if only we had source-clipping in cairo!
643 if (aContext->CurrentMatrix().HasNonIntegerTranslation()) {
644 if (doTile || !aRegion.RestrictionContains(imageRect)) {
645 if (drawable->DrawWithSamplingRect(aContext, aRegion.Rect(), aRegion.Restriction(),
646 doTile, aFilter, aOpacity)) {
647 return;
650 // On Mobile, we don't ever want to do this; it has the potential for
651 // allocating very large temporary surfaces, especially since we'll
652 // do full-page snapshots often (see bug 749426).
653 #ifndef MOZ_GFX_OPTIMIZE_MOBILE
654 nsRefPtr<gfxDrawable> restrictedDrawable =
655 CreateSamplingRestrictedDrawable(aDrawable, aContext,
656 aRegion, aFormat);
657 if (restrictedDrawable) {
658 drawable.swap(restrictedDrawable);
661 // We no longer need to tile: Either we never needed to, or we already
662 // filled a surface with the tiled pattern; this surface can now be
663 // drawn without tiling.
664 doTile = false;
665 #endif
669 drawable->Draw(aContext, aRegion.Rect(), doTile, aFilter, aOpacity);
672 /* static */ int
673 gfxUtils::ImageFormatToDepth(gfxImageFormat aFormat)
675 switch (aFormat) {
676 case gfxImageFormat::ARGB32:
677 return 32;
678 case gfxImageFormat::RGB24:
679 return 24;
680 case gfxImageFormat::RGB16_565:
681 return 16;
682 default:
683 break;
685 return 0;
688 static void
689 PathFromRegionInternal(gfxContext* aContext, const nsIntRegion& aRegion)
691 aContext->NewPath();
692 nsIntRegionRectIterator iter(aRegion);
693 const nsIntRect* r;
694 while ((r = iter.Next()) != nullptr) {
695 aContext->Rectangle(gfxRect(r->x, r->y, r->width, r->height));
699 static void
700 ClipToRegionInternal(gfxContext* aContext, const nsIntRegion& aRegion)
702 PathFromRegionInternal(aContext, aRegion);
703 aContext->Clip();
706 static TemporaryRef<Path>
707 PathFromRegionInternal(DrawTarget* aTarget, const nsIntRegion& aRegion)
709 RefPtr<PathBuilder> pb = aTarget->CreatePathBuilder();
710 nsIntRegionRectIterator iter(aRegion);
712 const nsIntRect* r;
713 while ((r = iter.Next()) != nullptr) {
714 pb->MoveTo(Point(r->x, r->y));
715 pb->LineTo(Point(r->XMost(), r->y));
716 pb->LineTo(Point(r->XMost(), r->YMost()));
717 pb->LineTo(Point(r->x, r->YMost()));
718 pb->Close();
720 RefPtr<Path> path = pb->Finish();
721 return path;
724 static void
725 ClipToRegionInternal(DrawTarget* aTarget, const nsIntRegion& aRegion)
727 if (!aRegion.IsComplex()) {
728 nsIntRect rect = aRegion.GetBounds();
729 aTarget->PushClipRect(Rect(rect.x, rect.y, rect.width, rect.height));
730 return;
733 RefPtr<Path> path = PathFromRegionInternal(aTarget, aRegion);
734 aTarget->PushClip(path);
737 /*static*/ void
738 gfxUtils::ClipToRegion(gfxContext* aContext, const nsIntRegion& aRegion)
740 ClipToRegionInternal(aContext, aRegion);
743 /*static*/ void
744 gfxUtils::ClipToRegion(DrawTarget* aTarget, const nsIntRegion& aRegion)
746 ClipToRegionInternal(aTarget, aRegion);
749 /*static*/ gfxFloat
750 gfxUtils::ClampToScaleFactor(gfxFloat aVal)
752 // Arbitary scale factor limitation. We can increase this
753 // for better scaling performance at the cost of worse
754 // quality.
755 static const gfxFloat kScaleResolution = 2;
757 // Negative scaling is just a flip and irrelevant to
758 // our resolution calculation.
759 if (aVal < 0.0) {
760 aVal = -aVal;
763 bool inverse = false;
764 if (aVal < 1.0) {
765 inverse = true;
766 aVal = 1 / aVal;
769 gfxFloat power = log(aVal)/log(kScaleResolution);
771 // If power is within 1e-5 of an integer, round to nearest to
772 // prevent floating point errors, otherwise round up to the
773 // next integer value.
774 if (fabs(power - NS_round(power)) < 1e-5) {
775 power = NS_round(power);
776 } else if (inverse) {
777 power = floor(power);
778 } else {
779 power = ceil(power);
782 gfxFloat scale = pow(kScaleResolution, power);
784 if (inverse) {
785 scale = 1 / scale;
788 return scale;
792 /*static*/ void
793 gfxUtils::PathFromRegion(gfxContext* aContext, const nsIntRegion& aRegion)
795 PathFromRegionInternal(aContext, aRegion);
798 gfxMatrix
799 gfxUtils::TransformRectToRect(const gfxRect& aFrom, const gfxPoint& aToTopLeft,
800 const gfxPoint& aToTopRight, const gfxPoint& aToBottomRight)
802 gfxMatrix m;
803 if (aToTopRight.y == aToTopLeft.y && aToTopRight.x == aToBottomRight.x) {
804 // Not a rotation, so xy and yx are zero
805 m._21 = m._12 = 0.0;
806 m._11 = (aToBottomRight.x - aToTopLeft.x)/aFrom.width;
807 m._22 = (aToBottomRight.y - aToTopLeft.y)/aFrom.height;
808 m._31 = aToTopLeft.x - m._11*aFrom.x;
809 m._32 = aToTopLeft.y - m._22*aFrom.y;
810 } else {
811 NS_ASSERTION(aToTopRight.y == aToBottomRight.y && aToTopRight.x == aToTopLeft.x,
812 "Destination rectangle not axis-aligned");
813 m._11 = m._22 = 0.0;
814 m._21 = (aToBottomRight.x - aToTopLeft.x)/aFrom.height;
815 m._12 = (aToBottomRight.y - aToTopLeft.y)/aFrom.width;
816 m._31 = aToTopLeft.x - m._21*aFrom.y;
817 m._32 = aToTopLeft.y - m._12*aFrom.x;
819 return m;
822 Matrix
823 gfxUtils::TransformRectToRect(const gfxRect& aFrom, const IntPoint& aToTopLeft,
824 const IntPoint& aToTopRight, const IntPoint& aToBottomRight)
826 Matrix m;
827 if (aToTopRight.y == aToTopLeft.y && aToTopRight.x == aToBottomRight.x) {
828 // Not a rotation, so xy and yx are zero
829 m._12 = m._21 = 0.0;
830 m._11 = (aToBottomRight.x - aToTopLeft.x)/aFrom.width;
831 m._22 = (aToBottomRight.y - aToTopLeft.y)/aFrom.height;
832 m._31 = aToTopLeft.x - m._11*aFrom.x;
833 m._32 = aToTopLeft.y - m._22*aFrom.y;
834 } else {
835 NS_ASSERTION(aToTopRight.y == aToBottomRight.y && aToTopRight.x == aToTopLeft.x,
836 "Destination rectangle not axis-aligned");
837 m._11 = m._22 = 0.0;
838 m._21 = (aToBottomRight.x - aToTopLeft.x)/aFrom.height;
839 m._12 = (aToBottomRight.y - aToTopLeft.y)/aFrom.width;
840 m._31 = aToTopLeft.x - m._21*aFrom.y;
841 m._32 = aToTopLeft.y - m._12*aFrom.x;
843 return m;
846 /* This function is sort of shitty. We truncate doubles
847 * to ints then convert those ints back to doubles to make sure that
848 * they equal the doubles that we got in. */
849 bool
850 gfxUtils::GfxRectToIntRect(const gfxRect& aIn, nsIntRect* aOut)
852 *aOut = nsIntRect(int32_t(aIn.X()), int32_t(aIn.Y()),
853 int32_t(aIn.Width()), int32_t(aIn.Height()));
854 return gfxRect(aOut->x, aOut->y, aOut->width, aOut->height).IsEqualEdges(aIn);
857 void
858 gfxUtils::GetYCbCrToRGBDestFormatAndSize(const PlanarYCbCrData& aData,
859 gfxImageFormat& aSuggestedFormat,
860 gfxIntSize& aSuggestedSize)
862 YUVType yuvtype =
863 TypeFromSize(aData.mYSize.width,
864 aData.mYSize.height,
865 aData.mCbCrSize.width,
866 aData.mCbCrSize.height);
868 // 'prescale' is true if the scaling is to be done as part of the
869 // YCbCr to RGB conversion rather than on the RGB data when rendered.
870 bool prescale = aSuggestedSize.width > 0 && aSuggestedSize.height > 0 &&
871 ToIntSize(aSuggestedSize) != aData.mPicSize;
873 if (aSuggestedFormat == gfxImageFormat::RGB16_565) {
874 #if defined(HAVE_YCBCR_TO_RGB565)
875 if (prescale &&
876 !IsScaleYCbCrToRGB565Fast(aData.mPicX,
877 aData.mPicY,
878 aData.mPicSize.width,
879 aData.mPicSize.height,
880 aSuggestedSize.width,
881 aSuggestedSize.height,
882 yuvtype,
883 FILTER_BILINEAR) &&
884 IsConvertYCbCrToRGB565Fast(aData.mPicX,
885 aData.mPicY,
886 aData.mPicSize.width,
887 aData.mPicSize.height,
888 yuvtype)) {
889 prescale = false;
891 #else
892 // yuv2rgb16 function not available
893 aSuggestedFormat = gfxImageFormat::RGB24;
894 #endif
896 else if (aSuggestedFormat != gfxImageFormat::RGB24) {
897 // No other formats are currently supported.
898 aSuggestedFormat = gfxImageFormat::RGB24;
900 if (aSuggestedFormat == gfxImageFormat::RGB24) {
901 /* ScaleYCbCrToRGB32 does not support a picture offset, nor 4:4:4 data.
902 See bugs 639415 and 640073. */
903 if (aData.mPicX != 0 || aData.mPicY != 0 || yuvtype == YV24)
904 prescale = false;
906 if (!prescale) {
907 ToIntSize(aSuggestedSize) = aData.mPicSize;
911 void
912 gfxUtils::ConvertYCbCrToRGB(const PlanarYCbCrData& aData,
913 const gfxImageFormat& aDestFormat,
914 const gfxIntSize& aDestSize,
915 unsigned char* aDestBuffer,
916 int32_t aStride)
918 // ConvertYCbCrToRGB et al. assume the chroma planes are rounded up if the
919 // luma plane is odd sized.
920 MOZ_ASSERT((aData.mCbCrSize.width == aData.mYSize.width ||
921 aData.mCbCrSize.width == (aData.mYSize.width + 1) >> 1) &&
922 (aData.mCbCrSize.height == aData.mYSize.height ||
923 aData.mCbCrSize.height == (aData.mYSize.height + 1) >> 1));
924 YUVType yuvtype =
925 TypeFromSize(aData.mYSize.width,
926 aData.mYSize.height,
927 aData.mCbCrSize.width,
928 aData.mCbCrSize.height);
930 // Convert from YCbCr to RGB now, scaling the image if needed.
931 if (ToIntSize(aDestSize) != aData.mPicSize) {
932 #if defined(HAVE_YCBCR_TO_RGB565)
933 if (aDestFormat == gfxImageFormat::RGB16_565) {
934 ScaleYCbCrToRGB565(aData.mYChannel,
935 aData.mCbChannel,
936 aData.mCrChannel,
937 aDestBuffer,
938 aData.mPicX,
939 aData.mPicY,
940 aData.mPicSize.width,
941 aData.mPicSize.height,
942 aDestSize.width,
943 aDestSize.height,
944 aData.mYStride,
945 aData.mCbCrStride,
946 aStride,
947 yuvtype,
948 FILTER_BILINEAR);
949 } else
950 #endif
951 ScaleYCbCrToRGB32(aData.mYChannel,
952 aData.mCbChannel,
953 aData.mCrChannel,
954 aDestBuffer,
955 aData.mPicSize.width,
956 aData.mPicSize.height,
957 aDestSize.width,
958 aDestSize.height,
959 aData.mYStride,
960 aData.mCbCrStride,
961 aStride,
962 yuvtype,
963 ROTATE_0,
964 FILTER_BILINEAR);
965 } else { // no prescale
966 #if defined(HAVE_YCBCR_TO_RGB565)
967 if (aDestFormat == gfxImageFormat::RGB16_565) {
968 ConvertYCbCrToRGB565(aData.mYChannel,
969 aData.mCbChannel,
970 aData.mCrChannel,
971 aDestBuffer,
972 aData.mPicX,
973 aData.mPicY,
974 aData.mPicSize.width,
975 aData.mPicSize.height,
976 aData.mYStride,
977 aData.mCbCrStride,
978 aStride,
979 yuvtype);
980 } else // aDestFormat != gfxImageFormat::RGB16_565
981 #endif
982 ConvertYCbCrToRGB32(aData.mYChannel,
983 aData.mCbChannel,
984 aData.mCrChannel,
985 aDestBuffer,
986 aData.mPicX,
987 aData.mPicY,
988 aData.mPicSize.width,
989 aData.mPicSize.height,
990 aData.mYStride,
991 aData.mCbCrStride,
992 aStride,
993 yuvtype);
997 /* static */ void gfxUtils::ClearThebesSurface(gfxASurface* aSurface,
998 nsIntRect* aRect,
999 const gfxRGBA& aColor)
1001 if (aSurface->CairoStatus()) {
1002 return;
1004 cairo_surface_t* surf = aSurface->CairoSurface();
1005 if (cairo_surface_status(surf)) {
1006 return;
1008 cairo_t* ctx = cairo_create(surf);
1009 cairo_set_source_rgba(ctx, aColor.r, aColor.g, aColor.b, aColor.a);
1010 cairo_set_operator(ctx, CAIRO_OPERATOR_SOURCE);
1011 nsIntRect bounds;
1012 if (aRect) {
1013 bounds = *aRect;
1014 } else {
1015 bounds = nsIntRect(nsIntPoint(0, 0), aSurface->GetSize());
1017 cairo_rectangle(ctx, bounds.x, bounds.y, bounds.width, bounds.height);
1018 cairo_fill(ctx);
1019 cairo_destroy(ctx);
1022 /* static */ TemporaryRef<DataSourceSurface>
1023 gfxUtils::CopySurfaceToDataSourceSurfaceWithFormat(SourceSurface* aSurface,
1024 SurfaceFormat aFormat)
1026 MOZ_ASSERT(aFormat != aSurface->GetFormat(),
1027 "Unnecessary - and very expersive - surface format conversion");
1029 Rect bounds(0, 0, aSurface->GetSize().width, aSurface->GetSize().height);
1031 if (aSurface->GetType() != SurfaceType::DATA) {
1032 // If the surface is NOT of type DATA then its data is not mapped into main
1033 // memory. Format conversion is probably faster on the GPU, and by doing it
1034 // there we can avoid any expensive uploads/readbacks except for (possibly)
1035 // a single readback due to the unavoidable GetDataSurface() call. Using
1036 // CreateOffscreenContentDrawTarget ensures the conversion happens on the
1037 // GPU.
1038 RefPtr<DrawTarget> dt = gfxPlatform::GetPlatform()->
1039 CreateOffscreenContentDrawTarget(aSurface->GetSize(), aFormat);
1040 // Using DrawSurface() here rather than CopySurface() because CopySurface
1041 // is optimized for memcpy and therefore isn't good for format conversion.
1042 // Using OP_OVER since in our case it's equivalent to OP_SOURCE and
1043 // generally more optimized.
1044 dt->DrawSurface(aSurface, bounds, bounds, DrawSurfaceOptions(),
1045 DrawOptions(1.0f, CompositionOp::OP_OVER));
1046 RefPtr<SourceSurface> surface = dt->Snapshot();
1047 return surface->GetDataSurface();
1050 // If the surface IS of type DATA then it may or may not be in main memory
1051 // depending on whether or not it has been mapped yet. We have no way of
1052 // knowing, so we can't be sure if it's best to create a data wrapping
1053 // DrawTarget for the conversion or an offscreen content DrawTarget. We could
1054 // guess it's not mapped and create an offscreen content DrawTarget, but if
1055 // it is then we'll end up uploading the surface data, and most likely the
1056 // caller is going to be accessing the resulting surface data, resulting in a
1057 // readback (both very expensive operations). Alternatively we could guess
1058 // the data is mapped and create a data wrapping DrawTarget and, if the
1059 // surface is not in main memory, then we will incure a readback. The latter
1060 // of these two "wrong choices" is the least costly (a readback, vs an
1061 // upload and a readback), and more than likely the DATA surface that we've
1062 // been passed actually IS in main memory anyway. For these reasons it's most
1063 // likely best to create a data wrapping DrawTarget here to do the format
1064 // conversion.
1065 RefPtr<DataSourceSurface> dataSurface =
1066 Factory::CreateDataSourceSurface(aSurface->GetSize(), aFormat);
1067 DataSourceSurface::MappedSurface map;
1068 if (!dataSurface ||
1069 !dataSurface->Map(DataSourceSurface::MapType::READ_WRITE, &map)) {
1070 return nullptr;
1072 RefPtr<DrawTarget> dt =
1073 Factory::CreateDrawTargetForData(BackendType::CAIRO,
1074 map.mData,
1075 dataSurface->GetSize(),
1076 map.mStride,
1077 aFormat);
1078 if (!dt) {
1079 dataSurface->Unmap();
1080 return nullptr;
1082 // Using DrawSurface() here rather than CopySurface() because CopySurface
1083 // is optimized for memcpy and therefore isn't good for format conversion.
1084 // Using OP_OVER since in our case it's equivalent to OP_SOURCE and
1085 // generally more optimized.
1086 dt->DrawSurface(aSurface, bounds, bounds, DrawSurfaceOptions(),
1087 DrawOptions(1.0f, CompositionOp::OP_OVER));
1088 dataSurface->Unmap();
1089 return dataSurface.forget();
1092 const uint32_t gfxUtils::sNumFrameColors = 8;
1094 /* static */ const gfx::Color&
1095 gfxUtils::GetColorForFrameNumber(uint64_t aFrameNumber)
1097 static bool initialized = false;
1098 static gfx::Color colors[sNumFrameColors];
1100 if (!initialized) {
1101 uint32_t i = 0;
1102 colors[i++] = gfx::Color::FromABGR(0xffff0000);
1103 colors[i++] = gfx::Color::FromABGR(0xffcc00ff);
1104 colors[i++] = gfx::Color::FromABGR(0xff0066cc);
1105 colors[i++] = gfx::Color::FromABGR(0xff00ff00);
1106 colors[i++] = gfx::Color::FromABGR(0xff33ffff);
1107 colors[i++] = gfx::Color::FromABGR(0xffff0099);
1108 colors[i++] = gfx::Color::FromABGR(0xff0000ff);
1109 colors[i++] = gfx::Color::FromABGR(0xff999999);
1110 MOZ_ASSERT(i == sNumFrameColors);
1111 initialized = true;
1114 return colors[aFrameNumber % sNumFrameColors];
1117 static nsresult
1118 EncodeSourceSurfaceInternal(SourceSurface* aSurface,
1119 const nsACString& aMimeType,
1120 const nsAString& aOutputOptions,
1121 gfxUtils::BinaryOrData aBinaryOrData,
1122 FILE* aFile,
1123 nsCString* aStrOut)
1125 MOZ_ASSERT(aBinaryOrData == gfxUtils::eDataURIEncode || aFile || aStrOut,
1126 "Copying binary encoding to clipboard not currently supported");
1128 const IntSize size = aSurface->GetSize();
1129 if (size.IsEmpty()) {
1130 return NS_ERROR_INVALID_ARG;
1132 const Size floatSize(size.width, size.height);
1134 RefPtr<DataSourceSurface> dataSurface;
1135 if (aSurface->GetFormat() != SurfaceFormat::B8G8R8A8) {
1136 // FIXME bug 995807 (B8G8R8X8), bug 831898 (R5G6B5)
1137 dataSurface =
1138 gfxUtils::CopySurfaceToDataSourceSurfaceWithFormat(aSurface,
1139 SurfaceFormat::B8G8R8A8);
1140 } else {
1141 dataSurface = aSurface->GetDataSurface();
1143 if (!dataSurface) {
1144 return NS_ERROR_FAILURE;
1147 DataSourceSurface::MappedSurface map;
1148 if (!dataSurface->Map(DataSourceSurface::MapType::READ, &map)) {
1149 return NS_ERROR_FAILURE;
1152 nsAutoCString encoderCID(
1153 NS_LITERAL_CSTRING("@mozilla.org/image/encoder;2?type=") + aMimeType);
1154 nsCOMPtr<imgIEncoder> encoder = do_CreateInstance(encoderCID.get());
1155 if (!encoder) {
1156 #ifdef DEBUG
1157 int32_t w = std::min(size.width, 8);
1158 int32_t h = std::min(size.height, 8);
1159 printf("Could not create encoder. Top-left %dx%d pixels contain:\n", w, h);
1160 for (int32_t y = 0; y < h; ++y) {
1161 for (int32_t x = 0; x < w; ++x) {
1162 printf("%x ", reinterpret_cast<uint32_t*>(map.mData)[y*map.mStride+x]);
1165 #endif
1166 dataSurface->Unmap();
1167 return NS_ERROR_FAILURE;
1170 nsresult rv = encoder->InitFromData(map.mData,
1171 BufferSizeFromStrideAndHeight(map.mStride, size.height),
1172 size.width,
1173 size.height,
1174 map.mStride,
1175 imgIEncoder::INPUT_FORMAT_HOSTARGB,
1176 aOutputOptions);
1177 dataSurface->Unmap();
1178 NS_ENSURE_SUCCESS(rv, rv);
1180 nsCOMPtr<nsIInputStream> imgStream;
1181 CallQueryInterface(encoder.get(), getter_AddRefs(imgStream));
1182 if (!imgStream) {
1183 return NS_ERROR_FAILURE;
1186 uint64_t bufSize64;
1187 rv = imgStream->Available(&bufSize64);
1188 NS_ENSURE_SUCCESS(rv, rv);
1190 NS_ENSURE_TRUE(bufSize64 < UINT32_MAX - 16, NS_ERROR_FAILURE);
1192 uint32_t bufSize = (uint32_t)bufSize64;
1194 // ...leave a little extra room so we can call read again and make sure we
1195 // got everything. 16 bytes for better padding (maybe)
1196 bufSize += 16;
1197 uint32_t imgSize = 0;
1198 Vector<char> imgData;
1199 if (!imgData.initCapacity(bufSize)) {
1200 return NS_ERROR_OUT_OF_MEMORY;
1202 uint32_t numReadThisTime = 0;
1203 while ((rv = imgStream->Read(imgData.begin() + imgSize,
1204 bufSize - imgSize,
1205 &numReadThisTime)) == NS_OK && numReadThisTime > 0)
1207 // Update the length of the vector without overwriting the new data.
1208 imgData.growByUninitialized(numReadThisTime);
1210 imgSize += numReadThisTime;
1211 if (imgSize == bufSize) {
1212 // need a bigger buffer, just double
1213 bufSize *= 2;
1214 if (!imgData.resizeUninitialized(bufSize)) {
1215 return NS_ERROR_OUT_OF_MEMORY;
1219 NS_ENSURE_SUCCESS(rv, rv);
1220 NS_ENSURE_TRUE(!imgData.empty(), NS_ERROR_FAILURE);
1222 if (aBinaryOrData == gfxUtils::eBinaryEncode) {
1223 if (aFile) {
1224 fwrite(imgData.begin(), 1, imgSize, aFile);
1226 return NS_OK;
1229 // base 64, result will be null-terminated
1230 nsCString encodedImg;
1231 rv = Base64Encode(Substring(imgData.begin(), imgSize), encodedImg);
1232 NS_ENSURE_SUCCESS(rv, rv);
1234 nsCString string("data:");
1235 string.Append(aMimeType);
1236 string.Append(";base64,");
1237 string.Append(encodedImg);
1239 if (aFile) {
1240 #ifdef ANDROID
1241 if (aFile == stdout || aFile == stderr) {
1242 // ADB logcat cuts off long strings so we will break it down
1243 const char* cStr = string.BeginReading();
1244 size_t len = strlen(cStr);
1245 while (true) {
1246 printf_stderr("IMG: %.140s\n", cStr);
1247 if (len <= 140)
1248 break;
1249 len -= 140;
1250 cStr += 140;
1253 #endif
1254 fprintf(aFile, "%s", string.BeginReading());
1255 } else if (aStrOut) {
1256 *aStrOut = string;
1257 } else {
1258 nsCOMPtr<nsIClipboardHelper> clipboard(do_GetService("@mozilla.org/widget/clipboardhelper;1", &rv));
1259 if (clipboard) {
1260 clipboard->CopyString(NS_ConvertASCIItoUTF16(string), nullptr);
1263 return NS_OK;
1266 static nsCString
1267 EncodeSourceSurfaceAsPNGURI(SourceSurface* aSurface)
1269 nsCString string;
1270 EncodeSourceSurfaceInternal(aSurface, NS_LITERAL_CSTRING("image/png"),
1271 EmptyString(), gfxUtils::eDataURIEncode,
1272 nullptr, &string);
1273 return string;
1276 /* static */ nsresult
1277 gfxUtils::EncodeSourceSurface(SourceSurface* aSurface,
1278 const nsACString& aMimeType,
1279 const nsAString& aOutputOptions,
1280 BinaryOrData aBinaryOrData,
1281 FILE* aFile)
1283 return EncodeSourceSurfaceInternal(aSurface, aMimeType, aOutputOptions,
1284 aBinaryOrData, aFile, nullptr);
1287 /* static */ void
1288 gfxUtils::WriteAsPNG(SourceSurface* aSurface, const nsAString& aFile)
1290 WriteAsPNG(aSurface, NS_ConvertUTF16toUTF8(aFile).get());
1293 /* static */ void
1294 gfxUtils::WriteAsPNG(SourceSurface* aSurface, const char* aFile)
1296 FILE* file = fopen(aFile, "wb");
1298 if (!file) {
1299 // Maybe the directory doesn't exist; try creating it, then fopen again.
1300 nsresult rv = NS_ERROR_FAILURE;
1301 nsCOMPtr<nsIFile> comFile = do_CreateInstance("@mozilla.org/file/local;1");
1302 if (comFile) {
1303 NS_ConvertUTF8toUTF16 utf16path((nsDependentCString(aFile)));
1304 rv = comFile->InitWithPath(utf16path);
1305 if (NS_SUCCEEDED(rv)) {
1306 nsCOMPtr<nsIFile> dirPath;
1307 comFile->GetParent(getter_AddRefs(dirPath));
1308 if (dirPath) {
1309 rv = dirPath->Create(nsIFile::DIRECTORY_TYPE, 0777);
1310 if (NS_SUCCEEDED(rv) || rv == NS_ERROR_FILE_ALREADY_EXISTS) {
1311 file = fopen(aFile, "wb");
1316 if (!file) {
1317 NS_WARNING("Failed to open file to create PNG!\n");
1318 return;
1322 EncodeSourceSurface(aSurface, NS_LITERAL_CSTRING("image/png"),
1323 EmptyString(), eBinaryEncode, file);
1324 fclose(file);
1327 /* static */ void
1328 gfxUtils::WriteAsPNG(DrawTarget* aDT, const nsAString& aFile)
1330 WriteAsPNG(aDT, NS_ConvertUTF16toUTF8(aFile).get());
1333 /* static */ void
1334 gfxUtils::WriteAsPNG(DrawTarget* aDT, const char* aFile)
1336 RefPtr<SourceSurface> surface = aDT->Snapshot();
1337 if (surface) {
1338 WriteAsPNG(surface, aFile);
1339 } else {
1340 NS_WARNING("Failed to get surface!");
1344 /* static */ void
1345 gfxUtils::WriteAsPNG(nsIPresShell* aShell, const char* aFile)
1347 int32_t width = 1000, height = 1000;
1348 nsRect r(0, 0, aShell->GetPresContext()->DevPixelsToAppUnits(width),
1349 aShell->GetPresContext()->DevPixelsToAppUnits(height));
1351 RefPtr<mozilla::gfx::DrawTarget> dt = gfxPlatform::GetPlatform()->
1352 CreateOffscreenContentDrawTarget(IntSize(width, height),
1353 SurfaceFormat::B8G8R8A8);
1354 NS_ENSURE_TRUE(dt, /*void*/);
1356 nsRefPtr<gfxContext> context = new gfxContext(dt);
1357 aShell->RenderDocument(r, 0, NS_RGB(255, 255, 0), context);
1358 WriteAsPNG(dt.get(), aFile);
1361 /* static */ void
1362 gfxUtils::DumpAsDataURI(SourceSurface* aSurface, FILE* aFile)
1364 EncodeSourceSurface(aSurface, NS_LITERAL_CSTRING("image/png"),
1365 EmptyString(), eDataURIEncode, aFile);
1368 /* static */ nsCString
1369 gfxUtils::GetAsDataURI(SourceSurface* aSurface)
1371 return EncodeSourceSurfaceAsPNGURI(aSurface);
1374 /* static */ void
1375 gfxUtils::DumpAsDataURI(DrawTarget* aDT, FILE* aFile)
1377 RefPtr<SourceSurface> surface = aDT->Snapshot();
1378 if (surface) {
1379 DumpAsDataURI(surface, aFile);
1380 } else {
1381 NS_WARNING("Failed to get surface!");
1385 /* static */ nsCString
1386 gfxUtils::GetAsLZ4Base64Str(DataSourceSurface* aSourceSurface)
1388 int32_t dataSize = aSourceSurface->GetSize().height * aSourceSurface->Stride();
1389 auto compressedData = MakeUnique<char[]>(LZ4::maxCompressedSize(dataSize));
1390 if (compressedData) {
1391 int nDataSize = LZ4::compress((char*)aSourceSurface->GetData(),
1392 dataSize,
1393 compressedData.get());
1394 if (nDataSize > 0) {
1395 nsCString encodedImg;
1396 nsresult rv = Base64Encode(Substring(compressedData.get(), nDataSize), encodedImg);
1397 if (rv == NS_OK) {
1398 nsCString string("");
1399 string.AppendPrintf("data:image/lz4bgra;base64,%i,%i,%i,",
1400 aSourceSurface->GetSize().width,
1401 aSourceSurface->Stride(),
1402 aSourceSurface->GetSize().height);
1403 string.Append(encodedImg);
1404 return string;
1408 return nsCString("");
1411 /* static */ nsCString
1412 gfxUtils::GetAsDataURI(DrawTarget* aDT)
1414 RefPtr<SourceSurface> surface = aDT->Snapshot();
1415 if (surface) {
1416 return EncodeSourceSurfaceAsPNGURI(surface);
1417 } else {
1418 NS_WARNING("Failed to get surface!");
1419 return nsCString("");
1423 /* static */ void
1424 gfxUtils::CopyAsDataURI(SourceSurface* aSurface)
1426 EncodeSourceSurface(aSurface, NS_LITERAL_CSTRING("image/png"),
1427 EmptyString(), eDataURIEncode, nullptr);
1430 /* static */ void
1431 gfxUtils::CopyAsDataURI(DrawTarget* aDT)
1433 RefPtr<SourceSurface> surface = aDT->Snapshot();
1434 if (surface) {
1435 CopyAsDataURI(surface);
1436 } else {
1437 NS_WARNING("Failed to get surface!");
1441 /* static */ bool
1442 gfxUtils::DumpDisplayList() {
1443 return gfxPrefs::LayoutDumpDisplayList();
1446 FILE *gfxUtils::sDumpPaintFile = stderr;
1448 #ifdef MOZ_DUMP_PAINTING
1449 bool gfxUtils::sDumpPainting = getenv("MOZ_DUMP_PAINT") != 0;
1450 bool gfxUtils::sDumpPaintingToFile = getenv("MOZ_DUMP_PAINT_TO_FILE") != 0;
1451 #else
1452 bool gfxUtils::sDumpPainting = false;
1453 bool gfxUtils::sDumpPaintingToFile = false;
1454 #endif
1456 namespace mozilla {
1457 namespace gfx {
1459 Color ToDeviceColor(Color aColor)
1461 // aColor is pass-by-value since to get return value optimization goodness we
1462 // need to return the same object from all return points in this function. We
1463 // could declare a local Color variable and use that, but we might as well
1464 // just use aColor.
1465 if (gfxPlatform::GetCMSMode() == eCMSMode_All) {
1466 qcms_transform *transform = gfxPlatform::GetCMSRGBTransform();
1467 if (transform) {
1468 gfxPlatform::TransformPixel(aColor, aColor, transform);
1469 // Use the original alpha to avoid unnecessary float->byte->float
1470 // conversion errors
1473 return aColor;
1476 Color ToDeviceColor(nscolor aColor)
1478 return ToDeviceColor(Color::FromABGR(aColor));
1481 Color ToDeviceColor(const gfxRGBA& aColor)
1483 return ToDeviceColor(ToColor(aColor));
1486 } // namespace gfx
1487 } // namespace mozilla