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