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