Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/gfx/thebes/gfxUtils.cpp
Line
Count
Source (jump to first uncovered line)
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
void mozilla_dump_image(void* bytes, int width, int height, int bytepp,
72
                        int strideBytes)
73
0
{
74
0
    if (0 == strideBytes) {
75
0
        strideBytes = width * bytepp;
76
0
    }
77
0
    SurfaceFormat format;
78
0
    // TODO more flexible; parse string?
79
0
    switch (bytepp) {
80
0
    case 2:
81
0
        format = SurfaceFormat::R5G6B5_UINT16;
82
0
        break;
83
0
    case 4:
84
0
    default:
85
0
        format = SurfaceFormat::R8G8B8A8;
86
0
        break;
87
0
    }
88
0
89
0
    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
MapSrcDest(DataSourceSurface* srcSurf,
100
           DataSourceSurface* destSurf,
101
           DataSourceSurface::MappedSurface* out_srcMap,
102
           DataSourceSurface::MappedSurface* out_destMap)
103
0
{
104
0
    MOZ_ASSERT(srcSurf && destSurf);
105
0
    MOZ_ASSERT(out_srcMap && out_destMap);
106
0
107
0
    if (srcSurf->GetSize() != destSurf->GetSize()) {
108
0
        MOZ_ASSERT(false, "Width and height must match.");
109
0
        return false;
110
0
    }
111
0
112
0
    if (srcSurf == destSurf) {
113
0
        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
0
        }
118
0
119
0
        *out_srcMap = map;
120
0
        *out_destMap = map;
121
0
        return true;
122
0
    }
123
0
124
0
    // Map src for reading.
125
0
    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
0
    }
130
0
131
0
    // Map dest for writing.
132
0
    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
0
    }
138
0
139
0
    *out_srcMap = srcMap;
140
0
    *out_destMap = destMap;
141
0
    return true;
142
0
}
143
144
static void
145
UnmapSrcDest(DataSourceSurface* srcSurf,
146
             DataSourceSurface* destSurf)
147
0
{
148
0
    if (srcSurf == destSurf) {
149
0
        srcSurf->Unmap();
150
0
    } else {
151
0
        srcSurf->Unmap();
152
0
        destSurf->Unmap();
153
0
    }
154
0
}
155
156
bool
157
gfxUtils::PremultiplyDataSurface(DataSourceSurface* srcSurf,
158
                                 DataSourceSurface* destSurf)
159
0
{
160
0
    MOZ_ASSERT(srcSurf && destSurf);
161
0
162
0
    DataSourceSurface::MappedSurface srcMap;
163
0
    DataSourceSurface::MappedSurface destMap;
164
0
    if (!MapSrcDest(srcSurf, destSurf, &srcMap, &destMap))
165
0
        return false;
166
0
167
0
    PremultiplyData(srcMap.mData, srcMap.mStride, srcSurf->GetFormat(),
168
0
                    destMap.mData, destMap.mStride, destSurf->GetFormat(),
169
0
                    srcSurf->GetSize());
170
0
171
0
    UnmapSrcDest(srcSurf, destSurf);
172
0
    return true;
173
0
}
174
175
bool
176
gfxUtils::UnpremultiplyDataSurface(DataSourceSurface* srcSurf,
177
                                   DataSourceSurface* destSurf)
178
0
{
179
0
    MOZ_ASSERT(srcSurf && destSurf);
180
0
181
0
    DataSourceSurface::MappedSurface srcMap;
182
0
    DataSourceSurface::MappedSurface destMap;
183
0
    if (!MapSrcDest(srcSurf, destSurf, &srcMap, &destMap))
184
0
        return false;
185
0
186
0
    UnpremultiplyData(srcMap.mData, srcMap.mStride, srcSurf->GetFormat(),
187
0
                      destMap.mData, destMap.mStride, destSurf->GetFormat(),
188
0
                      srcSurf->GetSize());
189
0
190
0
    UnmapSrcDest(srcSurf, destSurf);
191
0
    return true;
192
0
}
193
194
static bool
195
MapSrcAndCreateMappedDest(DataSourceSurface* srcSurf,
196
                          RefPtr<DataSourceSurface>* out_destSurf,
197
                          DataSourceSurface::MappedSurface* out_srcMap,
198
                          DataSourceSurface::MappedSurface* out_destMap)
199
0
{
200
0
    MOZ_ASSERT(srcSurf);
201
0
    MOZ_ASSERT(out_destSurf && out_srcMap && out_destMap);
202
0
203
0
    // Ok, map source for reading.
204
0
    DataSourceSurface::MappedSurface srcMap;
205
0
    if (!srcSurf->Map(DataSourceSurface::MapType::READ, &srcMap)) {
206
0
        MOZ_ASSERT(false, "Couldn't Map srcSurf.");
207
0
        return false;
208
0
    }
209
0
210
0
    // Make our dest surface based on the src.
211
0
    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
0
        return false;
217
0
    }
218
0
219
0
    DataSourceSurface::MappedSurface destMap;
220
0
    if (!destSurf->Map(DataSourceSurface::MapType::WRITE, &destMap)) {
221
0
        MOZ_ASSERT(false, "Couldn't Map destSurf.");
222
0
        srcSurf->Unmap();
223
0
        return false;
224
0
    }
225
0
226
0
    *out_destSurf = destSurf;
227
0
    *out_srcMap = srcMap;
228
0
    *out_destMap = destMap;
229
0
    return true;
230
0
}
231
232
already_AddRefed<DataSourceSurface>
233
gfxUtils::CreatePremultipliedDataSurface(DataSourceSurface* srcSurf)
234
0
{
235
0
    RefPtr<DataSourceSurface> destSurf;
236
0
    DataSourceSurface::MappedSurface srcMap;
237
0
    DataSourceSurface::MappedSurface destMap;
238
0
    if (!MapSrcAndCreateMappedDest(srcSurf, &destSurf, &srcMap, &destMap)) {
239
0
        MOZ_ASSERT(false, "MapSrcAndCreateMappedDest failed.");
240
0
        RefPtr<DataSourceSurface> surface(srcSurf);
241
0
        return surface.forget();
242
0
    }
243
0
244
0
    PremultiplyData(srcMap.mData, srcMap.mStride, srcSurf->GetFormat(),
245
0
                    destMap.mData, destMap.mStride, destSurf->GetFormat(),
246
0
                    srcSurf->GetSize());
247
0
248
0
    UnmapSrcDest(srcSurf, destSurf);
249
0
    return destSurf.forget();
250
0
}
251
252
already_AddRefed<DataSourceSurface>
253
gfxUtils::CreateUnpremultipliedDataSurface(DataSourceSurface* srcSurf)
254
0
{
255
0
    RefPtr<DataSourceSurface> destSurf;
256
0
    DataSourceSurface::MappedSurface srcMap;
257
0
    DataSourceSurface::MappedSurface destMap;
258
0
    if (!MapSrcAndCreateMappedDest(srcSurf, &destSurf, &srcMap, &destMap)) {
259
0
        MOZ_ASSERT(false, "MapSrcAndCreateMappedDest failed.");
260
0
        RefPtr<DataSourceSurface> surface(srcSurf);
261
0
        return surface.forget();
262
0
    }
263
0
264
0
    UnpremultiplyData(srcMap.mData, srcMap.mStride, srcSurf->GetFormat(),
265
0
                      destMap.mData, destMap.mStride, destSurf->GetFormat(),
266
0
                      srcSurf->GetSize());
267
0
268
0
    UnmapSrcDest(srcSurf, destSurf);
269
0
    return destSurf.forget();
270
0
}
271
272
void
273
gfxUtils::ConvertBGRAtoRGBA(uint8_t* aData, uint32_t aLength)
274
0
{
275
0
    MOZ_ASSERT((aLength % 4) == 0, "Loop below will pass srcEnd!");
276
0
    SwizzleData(aData, aLength, SurfaceFormat::B8G8R8A8,
277
0
                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
0
{
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
0
}
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
CreateSamplingRestrictedDrawable(gfxDrawable* aDrawable,
303
                                 gfxContext* aContext,
304
                                 const ImageRegion& aRegion,
305
                                 const SurfaceFormat aFormat,
306
                                 bool aUseOptimalFillOp)
307
0
{
308
0
    AUTO_PROFILER_LABEL("CreateSamplingRestrictedDrawable", GRAPHICS);
309
0
310
0
    DrawTarget* destDrawTarget = aContext->GetDrawTarget();
311
0
    if (destDrawTarget->GetBackendType() == BackendType::DIRECT2D1_1) {
312
0
      return nullptr;
313
0
    }
314
0
315
0
    gfxRect clipExtents = aContext->GetClipExtents();
316
0
317
0
    // Inflate by one pixel because bilinear filtering will sample at most
318
0
    // one pixel beyond the computed image pixel coordinate.
319
0
    clipExtents.Inflate(1.0);
320
0
321
0
    gfxRect needed = aRegion.IntersectAndRestrict(clipExtents);
322
0
    needed.RoundOut();
323
0
324
0
    // if 'needed' is empty, nothing will be drawn since aFill
325
0
    // must be entirely outside the clip region, so it doesn't
326
0
    // matter what we do here, but we should avoid trying to
327
0
    // create a zero-size surface.
328
0
    if (needed.IsEmpty())
329
0
        return nullptr;
330
0
331
0
    IntSize size(int32_t(needed.Width()), int32_t(needed.Height()));
332
0
333
0
    RefPtr<DrawTarget> target =
334
0
      gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(size, aFormat);
335
0
    if (!target || !target->IsValid()) {
336
0
      return nullptr;
337
0
    }
338
0
339
0
    RefPtr<gfxContext> tmpCtx = gfxContext::CreateOrNull(target);
340
0
    MOZ_ASSERT(tmpCtx); // already checked the target above
341
0
342
0
    if (aUseOptimalFillOp) {
343
0
        tmpCtx->SetOp(OptimalFillOp());
344
0
    }
345
0
    aDrawable->Draw(tmpCtx, needed - needed.TopLeft(), ExtendMode::REPEAT,
346
0
                    SamplingFilter::LINEAR,
347
0
                    1.0, gfxMatrix::Translation(needed.TopLeft()));
348
0
    RefPtr<SourceSurface> surface = target->Snapshot();
349
0
350
0
    RefPtr<gfxDrawable> drawable = new gfxSurfaceDrawable(surface, size, gfxMatrix::Translation(-needed.TopLeft()));
351
0
    return drawable.forget();
352
0
}
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
0
{
417
0
    // Just pass the filter through unchanged
418
0
    return aSamplingFilter;
419
0
}
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
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
0
{
533
0
    AUTO_PROFILER_LABEL("gfxUtils::DrawPixelSnapped", GRAPHICS);
534
0
535
0
    gfxRect imageRect(gfxPoint(0, 0), aImageSize);
536
0
    gfxRect region(aRegion.Rect());
537
0
    ExtendMode extendMode = aRegion.GetExtendMode();
538
0
539
0
    RefPtr<gfxDrawable> drawable = aDrawable;
540
0
541
0
    aSamplingFilter =
542
0
      ReduceResamplingFilter(aSamplingFilter,
543
0
                             imageRect.Width(), imageRect.Height(),
544
0
                             region.Width(), region.Height());
545
0
546
0
    // OK now, the hard part left is to account for the subimage sampling
547
0
    // restriction. If all the transforms involved are just integer
548
0
    // translations, then we assume no resampling will occur so there's
549
0
    // nothing to do.
550
0
    // XXX if only we had source-clipping in cairo!
551
0
552
0
    if (aContext->CurrentMatrix().HasNonIntegerTranslation()) {
553
0
        if ((extendMode != ExtendMode::CLAMP) || !aRegion.RestrictionContains(imageRect)) {
554
0
            if (drawable->DrawWithSamplingRect(aContext->GetDrawTarget(),
555
0
                                               aContext->CurrentOp(),
556
0
                                               aContext->CurrentAntialiasMode(),
557
0
                                               aRegion.Rect(),
558
0
                                               aRegion.Restriction(),
559
0
                                               extendMode, aSamplingFilter,
560
0
                                               aOpacity)) {
561
0
              return;
562
0
            }
563
0
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
0
            // On Mobile, we don't ever want to do this; it has the potential for
573
0
            // allocating very large temporary surfaces, especially since we'll
574
0
            // do full-page snapshots often (see bug 749426).
575
0
#if !defined(MOZ_GFX_OPTIMIZE_MOBILE)
576
0
            RefPtr<gfxDrawable> restrictedDrawable =
577
0
              CreateSamplingRestrictedDrawable(aDrawable, aContext,
578
0
                                               aRegion, aFormat,
579
0
                                               aUseOptimalFillOp);
580
0
            if (restrictedDrawable) {
581
0
              drawable.swap(restrictedDrawable);
582
0
583
0
              // We no longer need to tile: Either we never needed to, or we already
584
0
              // filled a surface with the tiled pattern; this surface can now be
585
0
              // drawn without tiling.
586
0
              extendMode = ExtendMode::CLAMP;
587
0
            }
588
0
#endif
589
0
        }
590
0
    }
591
0
592
0
    drawable->Draw(aContext, aRegion.Rect(), extendMode, aSamplingFilter,
593
0
                   aOpacity, gfxMatrix());
594
0
}
595
596
/* static */ int
597
gfxUtils::ImageFormatToDepth(gfxImageFormat aFormat)
598
0
{
599
0
    switch (aFormat) {
600
0
        case SurfaceFormat::A8R8G8B8_UINT32:
601
0
            return 32;
602
0
        case SurfaceFormat::X8R8G8B8_UINT32:
603
0
            return 24;
604
0
        case SurfaceFormat::R5G6B5_UINT16:
605
0
            return 16;
606
0
        default:
607
0
            break;
608
0
    }
609
0
    return 0;
610
0
}
611
612
/*static*/ void
613
gfxUtils::ClipToRegion(gfxContext* aContext, const nsIntRegion& aRegion)
614
0
{
615
0
  aContext->NewPath();
616
0
  for (auto iter = aRegion.RectIter(); !iter.Done(); iter.Next()) {
617
0
    const IntRect& r = iter.Get();
618
0
    aContext->Rectangle(gfxRect(r.X(), r.Y(), r.Width(), r.Height()));
619
0
  }
620
0
  aContext->Clip();
621
0
}
622
623
/*static*/ void
624
gfxUtils::ClipToRegion(DrawTarget* aTarget, const nsIntRegion& aRegion)
625
0
{
626
0
  uint32_t numRects = aRegion.GetNumRects();
627
0
  // If there is only one rect, then the region bounds are equivalent to the
628
0
  // contents. So just use push a single clip rect with the bounds.
629
0
  if (numRects == 1) {
630
0
    aTarget->PushClipRect(Rect(aRegion.GetBounds()));
631
0
    return;
632
0
  }
633
0
634
0
  // Check if the target's transform will preserve axis-alignment and
635
0
  // pixel-alignment for each rect. For now, just handle the common case
636
0
  // 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
0
    // 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
0
    }
649
0
    aTarget->PushDeviceSpaceClipRects(rects.Elements(), rects.Length());
650
0
  } else {
651
0
    // The transform does not produce axis-aligned rects or a rect was not
652
0
    // pixel-aligned. So just build a path with all the rects and clip to it
653
0
    // 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
0
    }
658
0
    RefPtr<Path> path = pathBuilder->Finish();
659
0
    aTarget->PushClip(path);
660
0
  }
661
0
}
662
663
/*static*/ float
664
gfxUtils::ClampToScaleFactor(float aVal, bool aRoundDown)
665
0
{
666
0
  // Arbitary scale factor limitation. We can increase this
667
0
  // for better scaling performance at the cost of worse
668
0
  // quality.
669
0
  static const float kScaleResolution = 2;
670
0
671
0
  // Negative scaling is just a flip and irrelevant to
672
0
  // our resolution calculation.
673
0
  if (aVal < 0.0) {
674
0
    aVal = -aVal;
675
0
  }
676
0
677
0
  bool inverse = false;
678
0
  if (aVal < 1.0) {
679
0
    inverse = true;
680
0
    aVal = 1 / aVal;
681
0
  }
682
0
683
0
  float power = logf(aVal)/logf(kScaleResolution);
684
0
685
0
  // If power is within 1e-5 of an integer, round to nearest to
686
0
  // prevent floating point errors, otherwise round up to the
687
0
  // next integer value.
688
0
  if (fabs(power - NS_round(power)) < 1e-5) {
689
0
    power = NS_round(power);
690
0
  // Use floor when we are either inverted or rounding down, but
691
0
  // not both.
692
0
  } else if (inverse != aRoundDown) {
693
0
    power = floor(power);
694
0
  // Otherwise, ceil when we are not inverted and not rounding
695
0
  // down, or we are inverted and rounding down.
696
0
  } else {
697
0
    power = ceil(power);
698
0
  }
699
0
700
0
  float scale = powf(kScaleResolution, power);
701
0
702
0
  if (inverse) {
703
0
    scale = 1 / scale;
704
0
  }
705
0
706
0
  return scale;
707
0
}
708
709
gfxMatrix
710
gfxUtils::TransformRectToRect(const gfxRect& aFrom, const gfxPoint& aToTopLeft,
711
                              const gfxPoint& aToTopRight, const gfxPoint& aToBottomRight)
712
0
{
713
0
  gfxMatrix m;
714
0
  if (aToTopRight.y == aToTopLeft.y && aToTopRight.x == aToBottomRight.x) {
715
0
    // Not a rotation, so xy and yx are zero
716
0
    m._21 = m._12 = 0.0;
717
0
    m._11 = (aToBottomRight.x - aToTopLeft.x)/aFrom.Width();
718
0
    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
0
  } else {
722
0
    NS_ASSERTION(aToTopRight.y == aToBottomRight.y && aToTopRight.x == aToTopLeft.x,
723
0
                 "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
0
  }
730
0
  return m;
731
0
}
732
733
Matrix
734
gfxUtils::TransformRectToRect(const gfxRect& aFrom, const IntPoint& aToTopLeft,
735
                              const IntPoint& aToTopRight, const IntPoint& aToBottomRight)
736
0
{
737
0
  Matrix m;
738
0
  if (aToTopRight.y == aToTopLeft.y && aToTopRight.x == aToBottomRight.x) {
739
0
    // 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
0
  } else {
746
0
    NS_ASSERTION(aToTopRight.y == aToBottomRight.y && aToTopRight.x == aToTopLeft.x,
747
0
                 "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
0
  }
754
0
  return m;
755
0
}
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
gfxUtils::GfxRectToIntRect(const gfxRect& aIn, IntRect* aOut)
762
0
{
763
0
  *aOut = IntRect(int32_t(aIn.X()), int32_t(aIn.Y()),
764
0
  int32_t(aIn.Width()), int32_t(aIn.Height()));
765
0
  return gfxRect(aOut->X(), aOut->Y(), aOut->Width(), aOut->Height()).IsEqualEdges(aIn);
766
0
}
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
gfxUtils::ConditionRect(gfxRect& aRect)
776
0
{
777
0
#define CAIRO_COORD_MAX (16777215.0)
778
0
#define CAIRO_COORD_MIN (-16777216.0)
779
0
  // if either x or y is way out of bounds;
780
0
  // 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
0
  }
784
0
785
0
  if (aRect.Y() > CAIRO_COORD_MAX) {
786
0
    aRect.SetRectY(CAIRO_COORD_MAX, 0.0);
787
0
  }
788
0
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
0
    }
794
0
    aRect.MoveToX(CAIRO_COORD_MIN);
795
0
  }
796
0
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
0
    }
802
0
    aRect.MoveToY(CAIRO_COORD_MIN);
803
0
  }
804
0
805
0
  if (aRect.XMost() > CAIRO_COORD_MAX) {
806
0
    aRect.SetRightEdge(CAIRO_COORD_MAX);
807
0
  }
808
0
809
0
  if (aRect.YMost() > CAIRO_COORD_MAX) {
810
0
    aRect.SetBottomEdge(CAIRO_COORD_MAX);
811
0
  }
812
0
#undef CAIRO_COORD_MAX
813
0
#undef CAIRO_COORD_MIN
814
0
}
815
816
/*static*/ gfxQuad
817
gfxUtils::TransformToQuad(const gfxRect& aRect,
818
                          const mozilla::gfx::Matrix4x4 &aMatrix)
819
0
{
820
0
  gfxPoint points[4];
821
0
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
0
827
0
  // 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
0
}
830
831
/* static */ void gfxUtils::ClearThebesSurface(gfxASurface* aSurface)
832
0
{
833
0
  if (aSurface->CairoStatus()) {
834
0
    return;
835
0
  }
836
0
  cairo_surface_t* surf = aSurface->CairoSurface();
837
0
  if (cairo_surface_status(surf)) {
838
0
    return;
839
0
  }
840
0
  cairo_t* ctx = cairo_create(surf);
841
0
  cairo_set_source_rgba(ctx, 0.0, 0.0, 0.0, 0.0);
842
0
  cairo_set_operator(ctx, CAIRO_OPERATOR_SOURCE);
843
0
  IntRect bounds(nsIntPoint(0, 0), aSurface->GetSize());
844
0
  cairo_rectangle(ctx, bounds.X(), bounds.Y(), bounds.Width(), bounds.Height());
845
0
  cairo_fill(ctx);
846
0
  cairo_destroy(ctx);
847
0
}
848
849
/* static */ already_AddRefed<DataSourceSurface>
850
gfxUtils::CopySurfaceToDataSourceSurfaceWithFormat(SourceSurface* aSurface,
851
                                                   SurfaceFormat aFormat)
852
0
{
853
0
  MOZ_ASSERT(aFormat != aSurface->GetFormat(),
854
0
             "Unnecessary - and very expersive - surface format conversion");
855
0
856
0
  Rect bounds(0, 0, aSurface->GetSize().width, aSurface->GetSize().height);
857
0
858
0
  if (!aSurface->IsDataSourceSurface()) {
859
0
    // If the surface is NOT of type DATA then its data is not mapped into main
860
0
    // memory. Format conversion is probably faster on the GPU, and by doing it
861
0
    // there we can avoid any expensive uploads/readbacks except for (possibly)
862
0
    // a single readback due to the unavoidable GetDataSurface() call. Using
863
0
    // CreateOffscreenContentDrawTarget ensures the conversion happens on the
864
0
    // GPU.
865
0
    RefPtr<DrawTarget> dt = gfxPlatform::GetPlatform()->
866
0
      CreateOffscreenContentDrawTarget(aSurface->GetSize(), aFormat);
867
0
    if (!dt) {
868
0
      gfxWarning() << "gfxUtils::CopySurfaceToDataSourceSurfaceWithFormat failed in CreateOffscreenContentDrawTarget";
869
0
      return nullptr;
870
0
    }
871
0
872
0
    // Using DrawSurface() here rather than CopySurface() because CopySurface
873
0
    // is optimized for memcpy and therefore isn't good for format conversion.
874
0
    // Using OP_OVER since in our case it's equivalent to OP_SOURCE and
875
0
    // 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
0
  }
881
0
882
0
  // If the surface IS of type DATA then it may or may not be in main memory
883
0
  // depending on whether or not it has been mapped yet. We have no way of
884
0
  // knowing, so we can't be sure if it's best to create a data wrapping
885
0
  // DrawTarget for the conversion or an offscreen content DrawTarget. We could
886
0
  // guess it's not mapped and create an offscreen content DrawTarget, but if
887
0
  // it is then we'll end up uploading the surface data, and most likely the
888
0
  // caller is going to be accessing the resulting surface data, resulting in a
889
0
  // readback (both very expensive operations). Alternatively we could guess
890
0
  // the data is mapped and create a data wrapping DrawTarget and, if the
891
0
  // surface is not in main memory, then we will incure a readback. The latter
892
0
  // of these two "wrong choices" is the least costly (a readback, vs an
893
0
  // upload and a readback), and more than likely the DATA surface that we've
894
0
  // been passed actually IS in main memory anyway. For these reasons it's most
895
0
  // likely best to create a data wrapping DrawTarget here to do the format
896
0
  // conversion.
897
0
  RefPtr<DataSourceSurface> dataSurface =
898
0
    Factory::CreateDataSourceSurface(aSurface->GetSize(), aFormat);
899
0
  DataSourceSurface::MappedSurface map;
900
0
  if (!dataSurface ||
901
0
      !dataSurface->Map(DataSourceSurface::MapType::READ_WRITE, &map)) {
902
0
    return nullptr;
903
0
  }
904
0
  RefPtr<DrawTarget> dt =
905
0
    Factory::CreateDrawTargetForData(BackendType::CAIRO,
906
0
                                     map.mData,
907
0
                                     dataSurface->GetSize(),
908
0
                                     map.mStride,
909
0
                                     aFormat);
910
0
  if (!dt) {
911
0
    dataSurface->Unmap();
912
0
    return nullptr;
913
0
  }
914
0
  // Using DrawSurface() here rather than CopySurface() because CopySurface
915
0
  // is optimized for memcpy and therefore isn't good for format conversion.
916
0
  // Using OP_OVER since in our case it's equivalent to OP_SOURCE and
917
0
  // generally more optimized.
918
0
  dt->DrawSurface(aSurface, bounds, bounds, DrawSurfaceOptions(),
919
0
                  DrawOptions(1.0f, CompositionOp::OP_OVER));
920
0
  dataSurface->Unmap();
921
0
  return dataSurface.forget();
922
0
}
923
924
const uint32_t gfxUtils::sNumFrameColors = 8;
925
926
/* static */ const gfx::Color&
927
gfxUtils::GetColorForFrameNumber(uint64_t aFrameNumber)
928
0
{
929
0
    static bool initialized = false;
930
0
    static gfx::Color colors[sNumFrameColors];
931
0
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
0
        MOZ_ASSERT(i == sNumFrameColors);
943
0
        initialized = true;
944
0
    }
945
0
946
0
    return colors[aFrameNumber % sNumFrameColors];
947
0
}
948
949
/* static */ nsresult
950
gfxUtils::EncodeSourceSurface(SourceSurface* aSurface,
951
                              const nsACString& aMimeType,
952
                              const nsAString& aOutputOptions,
953
                              BinaryOrData aBinaryOrData,
954
                              FILE* aFile,
955
                              nsACString* aStrOut)
956
0
{
957
0
  MOZ_ASSERT(aBinaryOrData == gfxUtils::eDataURIEncode || aFile || aStrOut,
958
0
             "Copying binary encoding to clipboard not currently supported");
959
0
960
0
  const IntSize size = aSurface->GetSize();
961
0
  if (size.IsEmpty()) {
962
0
    return NS_ERROR_INVALID_ARG;
963
0
  }
964
0
  const Size floatSize(size.width, size.height);
965
0
966
0
  RefPtr<DataSourceSurface> dataSurface;
967
0
  if (aSurface->GetFormat() != SurfaceFormat::B8G8R8A8) {
968
0
    // FIXME bug 995807 (B8G8R8X8), bug 831898 (R5G6B5)
969
0
    dataSurface =
970
0
      gfxUtils::CopySurfaceToDataSourceSurfaceWithFormat(aSurface,
971
0
                                                         SurfaceFormat::B8G8R8A8);
972
0
  } else {
973
0
    dataSurface = aSurface->GetDataSurface();
974
0
  }
975
0
  if (!dataSurface) {
976
0
    return NS_ERROR_FAILURE;
977
0
  }
978
0
979
0
  DataSourceSurface::MappedSurface map;
980
0
  if (!dataSurface->Map(DataSourceSurface::MapType::READ, &map)) {
981
0
    return NS_ERROR_FAILURE;
982
0
  }
983
0
984
0
  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
    int32_t w = std::min(size.width, 8);
990
    int32_t h = std::min(size.height, 8);
991
    printf("Could not create encoder. Top-left %dx%d pixels contain:\n", w, h);
992
    for (int32_t y = 0; y < h; ++y) {
993
      for (int32_t x = 0; x < w; ++x) {
994
        printf("%x ", reinterpret_cast<uint32_t*>(map.mData)[y*map.mStride+x]);
995
      }
996
    }
997
#endif
998
    dataSurface->Unmap();
999
0
    return NS_ERROR_FAILURE;
1000
0
  }
1001
0
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
0
                                      imgIEncoder::INPUT_FORMAT_HOSTARGB,
1008
0
                                      aOutputOptions);
1009
0
  dataSurface->Unmap();
1010
0
  NS_ENSURE_SUCCESS(rv, rv);
1011
0
1012
0
  nsCOMPtr<nsIInputStream> imgStream;
1013
0
  CallQueryInterface(encoder.get(), getter_AddRefs(imgStream));
1014
0
  if (!imgStream) {
1015
0
    return NS_ERROR_FAILURE;
1016
0
  }
1017
0
1018
0
  uint64_t bufSize64;
1019
0
  rv = imgStream->Available(&bufSize64);
1020
0
  NS_ENSURE_SUCCESS(rv, rv);
1021
0
1022
0
  NS_ENSURE_TRUE(bufSize64 < UINT32_MAX - 16, NS_ERROR_FAILURE);
1023
0
1024
0
  uint32_t bufSize = (uint32_t)bufSize64;
1025
0
1026
0
  // ...leave a little extra room so we can call read again and make sure we
1027
0
  // 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
0
    return NS_ERROR_OUT_OF_MEMORY;
1033
0
  }
1034
0
  uint32_t numReadThisTime = 0;
1035
0
  while ((rv = imgStream->Read(imgData.begin() + imgSize,
1036
0
                               bufSize - imgSize,
1037
0
                               &numReadThisTime)) == NS_OK && numReadThisTime > 0)
1038
0
  {
1039
0
    // Update the length of the vector without overwriting the new data.
1040
0
    if (!imgData.growByUninitialized(numReadThisTime)) {
1041
0
      return NS_ERROR_OUT_OF_MEMORY;
1042
0
    }
1043
0
1044
0
    imgSize += numReadThisTime;
1045
0
    if (imgSize == bufSize) {
1046
0
      // need a bigger buffer, just double
1047
0
      bufSize *= 2;
1048
0
      if (!imgData.resizeUninitialized(bufSize)) {
1049
0
        return NS_ERROR_OUT_OF_MEMORY;
1050
0
      }
1051
0
    }
1052
0
  }
1053
0
  NS_ENSURE_SUCCESS(rv, rv);
1054
0
  NS_ENSURE_TRUE(!imgData.empty(), NS_ERROR_FAILURE);
1055
0
1056
0
  if (aBinaryOrData == gfxUtils::eBinaryEncode) {
1057
0
    if (aFile) {
1058
0
      Unused << fwrite(imgData.begin(), 1, imgSize, aFile);
1059
0
    }
1060
0
    return NS_OK;
1061
0
  }
1062
0
1063
0
  // 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
0
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
0
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
    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
0
    }
1096
0
  }
1097
0
  return NS_OK;
1098
0
}
1099
1100
static nsCString
1101
EncodeSourceSurfaceAsPNGURI(SourceSurface* aSurface)
1102
0
{
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
0
}
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
gfxUtils::YuvToRgbMatrix4x3RowMajor(YUVColorSpace aYUVColorSpace)
1126
0
{
1127
0
  #define X(x) { x[0], x[1], x[ 2], 0.0f, \
1128
0
                 x[4], x[5], x[ 6], 0.0f, \
1129
0
                 x[8], x[9], x[10], 0.0f }
1130
0
1131
0
  static const float rec601[12] = X(kBT601NarrowYCbCrToRGB_RowMajor);
1132
0
  static const float rec709[12] = X(kBT709NarrowYCbCrToRGB_RowMajor);
1133
0
1134
0
  #undef X
1135
0
1136
0
  switch (aYUVColorSpace) {
1137
0
  case YUVColorSpace::BT601:
1138
0
    return rec601;
1139
0
  case YUVColorSpace::BT709:
1140
0
    return rec709;
1141
0
  default: // YUVColorSpace::UNKNOWN
1142
0
    MOZ_ASSERT(false, "unknown aYUVColorSpace");
1143
0
    return rec601;
1144
0
  }
1145
0
}
1146
1147
/* static */ const float*
1148
gfxUtils::YuvToRgbMatrix3x3ColumnMajor(YUVColorSpace aYUVColorSpace)
1149
0
{
1150
0
  #define X(x) { x[0], x[4], x[ 8], \
1151
0
                 x[1], x[5], x[ 9], \
1152
0
                 x[2], x[6], x[10] }
1153
0
1154
0
  static const float rec601[9] = X(kBT601NarrowYCbCrToRGB_RowMajor);
1155
0
  static const float rec709[9] = X(kBT709NarrowYCbCrToRGB_RowMajor);
1156
0
1157
0
  #undef X
1158
0
1159
0
  switch (aYUVColorSpace) {
1160
0
  case YUVColorSpace::BT601:
1161
0
    return rec601;
1162
0
  case YUVColorSpace::BT709:
1163
0
    return rec709;
1164
0
  default: // YUVColorSpace::UNKNOWN
1165
0
    MOZ_ASSERT(false, "unknown aYUVColorSpace");
1166
0
    return rec601;
1167
0
  }
1168
0
}
1169
1170
/* static */ const float*
1171
gfxUtils::YuvToRgbMatrix4x4ColumnMajor(YUVColorSpace aYUVColorSpace)
1172
0
{
1173
0
  #define X(x) { x[0], x[4], x[ 8], x[12], \
1174
0
                 x[1], x[5], x[ 9], x[13], \
1175
0
                 x[2], x[6], x[10], x[14], \
1176
0
                 x[3], x[7], x[11], x[15] }
1177
0
1178
0
  static const float rec601[16] = X(kBT601NarrowYCbCrToRGB_RowMajor);
1179
0
  static const float rec709[16] = X(kBT709NarrowYCbCrToRGB_RowMajor);
1180
0
1181
0
  #undef X
1182
0
1183
0
  switch (aYUVColorSpace) {
1184
0
  case YUVColorSpace::BT601:
1185
0
    return rec601;
1186
0
  case YUVColorSpace::BT709:
1187
0
    return rec709;
1188
0
  default: // YUVColorSpace::UNKNOWN
1189
0
    MOZ_ASSERT(false, "unknown aYUVColorSpace");
1190
0
    return rec601;
1191
0
  }
1192
0
}
1193
1194
/* static */ void
1195
gfxUtils::WriteAsPNG(SourceSurface* aSurface, const nsAString& aFile)
1196
0
{
1197
0
  WriteAsPNG(aSurface, NS_ConvertUTF16toUTF8(aFile).get());
1198
0
}
1199
1200
/* static */ void
1201
gfxUtils::WriteAsPNG(SourceSurface* aSurface, const char* aFile)
1202
0
{
1203
0
  FILE* file = fopen(aFile, "wb");
1204
0
1205
0
  if (!file) {
1206
0
    // 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
0
          }
1220
0
        }
1221
0
      }
1222
0
    }
1223
0
    if (!file) {
1224
0
      NS_WARNING("Failed to open file to create PNG!");
1225
0
      return;
1226
0
    }
1227
0
  }
1228
0
1229
0
  EncodeSourceSurface(aSurface, NS_LITERAL_CSTRING("image/png"),
1230
0
                      EmptyString(), eBinaryEncode, file);
1231
0
  fclose(file);
1232
0
}
1233
1234
/* static */ void
1235
gfxUtils::WriteAsPNG(DrawTarget* aDT, const nsAString& aFile)
1236
0
{
1237
0
  WriteAsPNG(aDT, NS_ConvertUTF16toUTF8(aFile).get());
1238
0
}
1239
1240
/* static */ void
1241
gfxUtils::WriteAsPNG(DrawTarget* aDT, const char* aFile)
1242
0
{
1243
0
  RefPtr<SourceSurface> surface = aDT->Snapshot();
1244
0
  if (surface) {
1245
0
    WriteAsPNG(surface, aFile);
1246
0
  } else {
1247
0
    NS_WARNING("Failed to get surface!");
1248
0
  }
1249
0
}
1250
1251
/* static */ void
1252
gfxUtils::WriteAsPNG(nsIPresShell* aShell, const char* aFile)
1253
0
{
1254
0
  int32_t width = 1000, height = 1000;
1255
0
  nsRect r(0, 0, aShell->GetPresContext()->DevPixelsToAppUnits(width),
1256
0
           aShell->GetPresContext()->DevPixelsToAppUnits(height));
1257
0
1258
0
  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
0
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
0
}
1268
1269
/* static */ void
1270
gfxUtils::DumpAsDataURI(SourceSurface* aSurface, FILE* aFile)
1271
0
{
1272
0
  EncodeSourceSurface(aSurface, NS_LITERAL_CSTRING("image/png"),
1273
0
                      EmptyString(), eDataURIEncode, aFile);
1274
0
}
1275
1276
/* static */ nsCString
1277
gfxUtils::GetAsDataURI(SourceSurface* aSurface)
1278
0
{
1279
0
  return EncodeSourceSurfaceAsPNGURI(aSurface);
1280
0
}
1281
1282
/* static */ void
1283
gfxUtils::DumpAsDataURI(DrawTarget* aDT, FILE* aFile)
1284
0
{
1285
0
  RefPtr<SourceSurface> surface = aDT->Snapshot();
1286
0
  if (surface) {
1287
0
    DumpAsDataURI(surface, aFile);
1288
0
  } else {
1289
0
    NS_WARNING("Failed to get surface!");
1290
0
  }
1291
0
}
1292
1293
/* static */ nsCString
1294
gfxUtils::GetAsLZ4Base64Str(DataSourceSurface* aSourceSurface)
1295
0
{
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
0
                                  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
0
                             map.GetStride(),
1311
0
                             aSourceSurface->GetSize().height);
1312
0
        string.Append(encodedImg);
1313
0
        return string;
1314
0
      }
1315
0
    }
1316
0
  }
1317
0
  return nsCString("");
1318
0
}
1319
1320
/* static */ nsCString
1321
gfxUtils::GetAsDataURI(DrawTarget* aDT)
1322
0
{
1323
0
  RefPtr<SourceSurface> surface = aDT->Snapshot();
1324
0
  if (surface) {
1325
0
    return EncodeSourceSurfaceAsPNGURI(surface);
1326
0
  } else {
1327
0
    NS_WARNING("Failed to get surface!");
1328
0
    return nsCString("");
1329
0
  }
1330
0
}
1331
1332
/* static */ void
1333
gfxUtils::CopyAsDataURI(SourceSurface* aSurface)
1334
0
{
1335
0
  EncodeSourceSurface(aSurface, NS_LITERAL_CSTRING("image/png"),
1336
0
                      EmptyString(), eDataURIEncode, nullptr);
1337
0
}
1338
1339
/* static */ void
1340
gfxUtils::CopyAsDataURI(DrawTarget* aDT)
1341
0
{
1342
0
  RefPtr<SourceSurface> surface = aDT->Snapshot();
1343
0
  if (surface) {
1344
0
    CopyAsDataURI(surface);
1345
0
  } else {
1346
0
    NS_WARNING("Failed to get surface!");
1347
0
  }
1348
0
}
1349
1350
/* static */ UniquePtr<uint8_t[]>
1351
gfxUtils::GetImageBuffer(gfx::DataSourceSurface* aSurface,
1352
                         bool aIsAlphaPremultiplied,
1353
                         int32_t* outFormat)
1354
0
{
1355
0
    *outFormat = 0;
1356
0
1357
0
    DataSourceSurface::MappedSurface map;
1358
0
    if (!aSurface->Map(DataSourceSurface::MapType::READ, &map))
1359
0
        return nullptr;
1360
0
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
0
        return nullptr;
1366
0
    }
1367
0
    memcpy(imageBuffer.get(), map.mData, bufferSize);
1368
0
1369
0
    aSurface->Unmap();
1370
0
1371
0
    int32_t format = imgIEncoder::INPUT_FORMAT_HOSTARGB;
1372
0
    if (!aIsAlphaPremultiplied) {
1373
0
        // We need to convert to INPUT_FORMAT_RGBA, otherwise
1374
0
        // we are automatically considered premult, and unpremult'd.
1375
0
        // Yes, it is THAT silly.
1376
0
        // Except for different lossy conversions by color,
1377
0
        // 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
0
    }
1381
0
1382
0
    *outFormat = format;
1383
0
    return imageBuffer;
1384
0
}
1385
1386
/* static */ nsresult
1387
gfxUtils::GetInputStream(gfx::DataSourceSurface* aSurface,
1388
                         bool aIsAlphaPremultiplied,
1389
                         const char* aMimeType,
1390
                         const char16_t* aEncoderOptions,
1391
                         nsIInputStream** outStream)
1392
0
{
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
0
        return NS_ERROR_FAILURE;
1398
0
1399
0
    int32_t format = 0;
1400
0
    UniquePtr<uint8_t[]> imageBuffer = GetImageBuffer(aSurface, aIsAlphaPremultiplied, &format);
1401
0
    if (!imageBuffer)
1402
0
        return NS_ERROR_FAILURE;
1403
0
1404
0
    return dom::ImageEncoder::GetInputStream(aSurface->GetSize().width,
1405
0
                                             aSurface->GetSize().height,
1406
0
                                             imageBuffer.get(), format,
1407
0
                                             encoder, aEncoderOptions, outStream);
1408
0
}
1409
1410
class GetFeatureStatusRunnable final : public dom::WorkerMainThreadRunnable
1411
{
1412
public:
1413
    GetFeatureStatusRunnable(dom::WorkerPrivate* workerPrivate,
1414
                             const nsCOMPtr<nsIGfxInfo>& gfxInfo,
1415
                             int32_t feature,
1416
                             nsACString& failureId,
1417
                             int32_t* status)
1418
      : WorkerMainThreadRunnable(workerPrivate,
1419
                                 NS_LITERAL_CSTRING("GFX :: GetFeatureStatus"))
1420
      , mGfxInfo(gfxInfo)
1421
      , mFeature(feature)
1422
      , mStatus(status)
1423
      , mFailureId(failureId)
1424
      , mNSResult(NS_OK)
1425
0
    {
1426
0
    }
1427
1428
    bool MainThreadRun() override
1429
0
    {
1430
0
      if (mGfxInfo) {
1431
0
        mNSResult = mGfxInfo->GetFeatureStatus(mFeature, mFailureId, mStatus);
1432
0
      }
1433
0
      return true;
1434
0
    }
1435
1436
    nsresult GetNSResult() const
1437
0
    {
1438
0
      return mNSResult;
1439
0
    }
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
gfxUtils::ThreadSafeGetFeatureStatus(const nsCOMPtr<nsIGfxInfo>& gfxInfo,
1454
                                     int32_t feature, nsACString& failureId,
1455
                                     int32_t* status)
1456
0
{
1457
0
  if (!NS_IsMainThread()) {
1458
0
    dom::WorkerPrivate* workerPrivate = dom::GetCurrentThreadWorkerPrivate();
1459
0
1460
0
    RefPtr<GetFeatureStatusRunnable> runnable =
1461
0
      new GetFeatureStatusRunnable(workerPrivate, gfxInfo, feature, failureId,
1462
0
                                   status);
1463
0
1464
0
    ErrorResult rv;
1465
0
    runnable->Dispatch(dom::WorkerStatus::Canceling, rv);
1466
0
    if (rv.Failed()) {
1467
0
        // XXXbz This is totally broken, since we're supposed to just abort
1468
0
        // everything up the callstack but the callers basically eat the
1469
0
        // exception.  Ah, well.
1470
0
        return rv.StealNSResult();
1471
0
    }
1472
0
1473
0
    return runnable->GetNSResult();
1474
0
  }
1475
0
1476
0
  return gfxInfo->GetFeatureStatus(feature, failureId, status);
1477
0
}
1478
1479
0
#define GFX_SHADER_CHECK_BUILD_VERSION_PREF "gfx-shader-check.build-version"
1480
0
#define GFX_SHADER_CHECK_DEVICE_ID_PREF "gfx-shader-check.device-id"
1481
0
#define GFX_SHADER_CHECK_DRIVER_VERSION_PREF "gfx-shader-check.driver-version"
1482
1483
/* static */ void
1484
gfxUtils::RemoveShaderCacheFromDiskIfNecessary()
1485
0
{
1486
0
  if (!gfxVars::UseWebRenderProgramBinaryDisk()) {
1487
0
    return;
1488
0
  }
1489
0
1490
0
  nsCOMPtr<nsIGfxInfo> gfxInfo = services::GetGfxInfo();
1491
0
1492
0
  // 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
0
1498
0
  // 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
0
1505
0
  if (buildID == buildIDChecked &&
1506
0
      deviceID == deviceIDChecked &&
1507
0
      driverVersion == driverVersionChecked) {
1508
0
      return;
1509
0
  }
1510
0
1511
0
  nsAutoString path(gfx::gfxVars::ProfDirectory());
1512
0
1513
0
  if (!wr::remove_program_binary_disk_cache(&path)) {
1514
0
    // Failed to remove program binary disk cache. The disk cache might have
1515
0
    // invalid data. Disable program binary disk cache usage.
1516
0
    gfxVars::SetUseWebRenderProgramBinaryDisk(false);
1517
0
    return;
1518
0
  }
1519
0
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
0
}
1525
1526
1527
/* static */ bool
1528
0
gfxUtils::DumpDisplayList() {
1529
0
  return gfxPrefs::LayoutDumpDisplayList() ||
1530
0
         (gfxPrefs::LayoutDumpDisplayListParent() && XRE_IsParentProcess()) ||
1531
0
         (gfxPrefs::LayoutDumpDisplayListContent() && XRE_IsContentProcess());
1532
0
}
1533
1534
FILE *gfxUtils::sDumpPaintFile = stderr;
1535
1536
namespace mozilla {
1537
namespace gfx {
1538
1539
Color ToDeviceColor(Color aColor)
1540
0
{
1541
0
  // aColor is pass-by-value since to get return value optimization goodness we
1542
0
  // need to return the same object from all return points in this function. We
1543
0
  // could declare a local Color variable and use that, but we might as well
1544
0
  // just use aColor.
1545
0
  if (gfxPlatform::GetCMSMode() == eCMSMode_All) {
1546
0
    qcms_transform *transform = gfxPlatform::GetCMSRGBTransform();
1547
0
    if (transform) {
1548
0
      gfxPlatform::TransformPixel(aColor, aColor, transform);
1549
0
      // Use the original alpha to avoid unnecessary float->byte->float
1550
0
      // conversion errors
1551
0
    }
1552
0
  }
1553
0
  return aColor;
1554
0
}
1555
1556
Color ToDeviceColor(nscolor aColor)
1557
0
{
1558
0
  return ToDeviceColor(Color::FromABGR(aColor));
1559
0
}
1560
1561
} // namespace gfx
1562
} // namespace mozilla