summaryrefslogtreecommitdiffstats
path: root/gfx/thebes/gfxUtils.h
blob: a7b9f067ebbfcf820443ca39ab5f9ab754ad34c8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef GFX_UTILS_H
#define GFX_UTILS_H

#include "gfxMatrix.h"
#include "gfxRect.h"
#include "gfxTypes.h"
#include "ImageTypes.h"
#include "imgIContainer.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/Maybe.h"
#include "mozilla/RefPtr.h"
#include "mozilla/UniquePtr.h"
#include "nsColor.h"
#include "nsContentUtils.h"
#include "nsPrintfCString.h"
#include "nsRegionFwd.h"
#include "mozilla/gfx/Rect.h"
#include "mozilla/CheckedInt.h"
#include "mozilla/webrender/WebRenderTypes.h"
#include "qcms.h"

class gfxASurface;
class gfxDrawable;
class gfxTextRun;
struct gfxQuad;
class nsICookieJarSettings;
class nsIInputStream;
class nsIGfxInfo;

namespace mozilla {
namespace dom {
class Element;
}  // namespace dom
namespace layers {
class WebRenderBridgeChild;
class GlyphArray;
struct PlanarYCbCrData;
class WebRenderCommand;
}  // namespace layers
namespace image {
class ImageRegion;
}  // namespace image
namespace wr {
class DisplayListBuilder;
}  // namespace wr
}  // namespace mozilla

enum class ImageType {
  BMP,
  ICO,
  JPEG,
  PNG,
};

class gfxUtils {
 public:
  typedef mozilla::gfx::DataSourceSurface DataSourceSurface;
  typedef mozilla::gfx::DrawTarget DrawTarget;
  typedef mozilla::gfx::IntPoint IntPoint;
  typedef mozilla::gfx::Matrix Matrix;
  typedef mozilla::gfx::Matrix4x4 Matrix4x4;
  typedef mozilla::gfx::SourceSurface SourceSurface;
  typedef mozilla::gfx::SurfaceFormat SurfaceFormat;
  typedef mozilla::image::ImageRegion ImageRegion;

  /*
   * Premultiply or Unpremultiply aSourceSurface, writing the result
   * to aDestSurface or back into aSourceSurface if aDestSurface is null.
   *
   * If aDestSurface is given, it must have identical format, dimensions, and
   * stride as the source.
   *
   * If the source is not SurfaceFormat::A8R8G8B8_UINT32, no operation is
   * performed.  If aDestSurface is given, the data is copied over.
   */
  static bool PremultiplyDataSurface(DataSourceSurface* srcSurf,
                                     DataSourceSurface* destSurf);
  static bool UnpremultiplyDataSurface(DataSourceSurface* srcSurf,
                                       DataSourceSurface* destSurf);

  static already_AddRefed<DataSourceSurface> CreatePremultipliedDataSurface(
      DataSourceSurface* srcSurf);
  static already_AddRefed<DataSourceSurface> CreateUnpremultipliedDataSurface(
      DataSourceSurface* srcSurf);

  static void ConvertBGRAtoRGBA(uint8_t* aData, uint32_t aLength);

  /**
   * Draw something drawable while working around limitations like bad support
   * for EXTEND_PAD, lack of source-clipping, or cairo / pixman bugs with
   * extreme user-space-to-image-space transforms.
   *
   * The input parameters here usually come from the output of our image
   * snapping algorithm in nsLayoutUtils.cpp.
   * This method is split from nsLayoutUtils::DrawPixelSnapped to allow for
   * adjusting the parameters. For example, certain images with transparent
   * margins only have a drawable subimage. For those images, imgFrame::Draw
   * will tweak the rects and transforms that it gets from the pixel snapping
   * algorithm before passing them on to this method.
   */
  static void DrawPixelSnapped(gfxContext* aContext, gfxDrawable* aDrawable,
                               const gfxSize& aImageSize,
                               const ImageRegion& aRegion,
                               const mozilla::gfx::SurfaceFormat aFormat,
                               mozilla::gfx::SamplingFilter aSamplingFilter,
                               uint32_t aImageFlags = imgIContainer::FLAG_NONE,
                               gfxFloat aOpacity = 1.0,
                               bool aUseOptimalFillOp = true);

  /**
   * Clip aContext to the region aRegion.
   */
  static void ClipToRegion(gfxContext* aContext, const nsIntRegion& aRegion);

  /**
   * Clip aTarget to the region aRegion.
   */
  static void ClipToRegion(mozilla::gfx::DrawTarget* aTarget,
                           const nsIntRegion& aRegion);

  /*
   * Convert image format to depth value
   */
  static int ImageFormatToDepth(gfxImageFormat aFormat);

  /**
   * Return the transform matrix that maps aFrom to the rectangle defined by
   * aToTopLeft/aToTopRight/aToBottomRight. aFrom must be
   * nonempty and the destination rectangle must be axis-aligned.
   */
  static gfxMatrix TransformRectToRect(const gfxRect& aFrom,
                                       const gfxPoint& aToTopLeft,
                                       const gfxPoint& aToTopRight,
                                       const gfxPoint& aToBottomRight);

  static Matrix TransformRectToRect(const gfxRect& aFrom,
                                    const IntPoint& aToTopLeft,
                                    const IntPoint& aToTopRight,
                                    const IntPoint& aToBottomRight);

  /**
   * If aIn can be represented exactly using an gfx::IntRect (i.e.
   * integer-aligned edges and coordinates in the int32_t range) then we
   * set aOut to that rectangle, otherwise return failure.
   */
  static bool GfxRectToIntRect(const gfxRect& aIn, mozilla::gfx::IntRect* aOut);

  /* Conditions this border to Cairo's max coordinate space.
   * The caller can check IsEmpty() after Condition() -- if it's TRUE,
   * the caller can possibly avoid doing any extra rendering.
   */
  static void ConditionRect(gfxRect& aRect);

  /*
   * Transform this rectangle with aMatrix, resulting in a gfxQuad.
   */
  static gfxQuad TransformToQuad(const gfxRect& aRect,
                                 const mozilla::gfx::Matrix4x4& aMatrix);

  /**
   * Return the smallest power of kScaleResolution (2) greater than or equal to
   * aVal. If aRoundDown is specified, the power of 2 will rather be less than
   * or equal to aVal.
   */
  static float ClampToScaleFactor(float aVal, bool aRoundDown = false);

  /**
   * We can snap layer transforms for two reasons:
   * 1) To avoid unnecessary resampling when a transform is a translation
   * by a non-integer number of pixels.
   * Snapping the translation to an integer number of pixels avoids
   * blurring the layer and can be faster to composite.
   * 2) When a layer is used to render a rectangular object, we need to
   * emulate the rendering of rectangular inactive content and snap the
   * edges of the rectangle to pixel boundaries. This is both to ensure
   * layer rendering is consistent with inactive content rendering, and to
   * avoid seams.
   * This function implements type 1 snapping. If aTransform is a 2D
   * translation, and this layer's layer manager has enabled snapping
   * (which is the default), return aTransform with the translation snapped
   * to nearest pixels. Otherwise just return aTransform. Call this when the
   * layer does not correspond to a single rectangular content object.
   * This function does not try to snap if aTransform has a scale, because in
   * that case resampling is inevitable and there's no point in trying to
   * avoid it. In fact snapping can cause problems because pixel edges in the
   * layer's content can be rendered unpredictably (jiggling) as the scale
   * interacts with the snapping of the translation, especially with animated
   * transforms.
   * @param aResidualTransform a transform to apply before the result transform
   * in order to get the results to completely match aTransform.
   */
  static Matrix4x4 SnapTransformTranslation(const Matrix4x4& aTransform,
                                            Matrix* aResidualTransform);
  static Matrix SnapTransformTranslation(const Matrix& aTransform,
                                         Matrix* aResidualTransform);
  static Matrix4x4 SnapTransformTranslation3D(const Matrix4x4& aTransform,
                                              Matrix* aResidualTransform);
  /**
   * See comment for SnapTransformTranslation.
   * This function implements type 2 snapping. If aTransform is a translation
   * and/or scale, transform aSnapRect by aTransform, snap to pixel boundaries,
   * and return the transform that maps aSnapRect to that rect. Otherwise
   * just return aTransform.
   * @param aSnapRect a rectangle whose edges should be snapped to pixel
   * boundaries in the destination surface.
   * @param aResidualTransform a transform to apply before the result transform
   * in order to get the results to completely match aTransform.
   */
  static Matrix4x4 SnapTransform(const Matrix4x4& aTransform,
                                 const gfxRect& aSnapRect,
                                 Matrix* aResidualTransform);
  static Matrix SnapTransform(const Matrix& aTransform,
                              const gfxRect& aSnapRect,
                              Matrix* aResidualTransform);

  /**
   * Clears surface to aColor (which defaults to transparent black).
   */
  static void ClearThebesSurface(gfxASurface* aSurface);

  static const float* YuvToRgbMatrix4x3RowMajor(
      mozilla::gfx::YUVColorSpace aYUVColorSpace);
  static const float* YuvToRgbMatrix3x3ColumnMajor(
      mozilla::gfx::YUVColorSpace aYUVColorSpace);
  static const float* YuvToRgbMatrix4x4ColumnMajor(
      mozilla::gfx::YUVColorSpace aYUVColorSpace);

  static mozilla::Maybe<mozilla::gfx::YUVColorSpace> CicpToColorSpace(
      const mozilla::gfx::CICP::MatrixCoefficients,
      const mozilla::gfx::CICP::ColourPrimaries,
      mozilla::LazyLogModule& aLogger);

  static mozilla::Maybe<mozilla::gfx::ColorSpace2> CicpToColorPrimaries(
      const mozilla::gfx::CICP::ColourPrimaries,
      mozilla::LazyLogModule& aLogger);

  static mozilla::Maybe<mozilla::gfx::TransferFunction> CicpToTransferFunction(
      const mozilla::gfx::CICP::TransferCharacteristics);

  /**
   * Creates a copy of aSurface, but having the SurfaceFormat aFormat.
   *
   * This function always creates a new surface. Do not call it if aSurface's
   * format is the same as aFormat. Such a non-conversion would just be an
   * unnecessary and wasteful copy (this function asserts to prevent that).
   *
   * This function is intended to be called by code that needs to access the
   * pixel data of the surface, but doesn't want to have lots of branches
   * to handle different pixel data formats (code which would become out of
   * date if and when new formats are added). Callers can use this function
   * to copy the surface to a specified format so that they only have to
   * handle pixel data in that one format.
   *
   * WARNING: There are format conversions that will not be supported by this
   * function. It very much depends on what the Moz2D backends support. If
   * the temporary B8G8R8A8 DrawTarget that this function creates has a
   * backend that supports DrawSurface() calls passing a surface with
   * aSurface's format it will work. Otherwise it will not.
   *
   *                      *** IMPORTANT PERF NOTE ***
   *
   * This function exists partly because format conversion is fraught with
   * non-obvious performance hazards, so we don't want Moz2D consumers to be
   * doing their own format conversion. Do not try to do so, or at least read
   * the comments in this functions implemtation. That said, the copy that
   * this function carries out has a cost and, although this function tries
   * to avoid perf hazards such as expensive uploads to/readbacks from the
   * GPU, it can't guarantee that it always successfully does so. Perf
   * critical code that can directly handle the common formats that it
   * encounters in a way that is cheaper than a copy-with-format-conversion
   * should consider doing so, and only use this function as a fallback to
   * handle other formats.
   *
   * XXXjwatt it would be nice if SourceSurface::GetDataSurface took a
   * SurfaceFormat argument (with a default argument meaning "use the
   * existing surface's format") and returned a DataSourceSurface in that
   * format. (There would then be an issue of callers maybe failing to
   * realize format conversion may involve expensive copying/uploading/
   * readback.)
   */
  static already_AddRefed<DataSourceSurface>
  CopySurfaceToDataSourceSurfaceWithFormat(SourceSurface* aSurface,
                                           SurfaceFormat aFormat);

  /**
   * Return a color that can be used to identify a frame with a given frame
   * number. The colors will cycle after sNumFrameColors.  You can query colors
   * 0 .. sNumFrameColors-1 to get all the colors back.
   */
  static const mozilla::gfx::DeviceColor& GetColorForFrameNumber(
      uint64_t aFrameNumber);
  static const uint32_t sNumFrameColors;

  enum BinaryOrData { eBinaryEncode, eDataURIEncode };

  /**
   * Encodes the given surface to PNG/JPEG/BMP/etc. using imgIEncoder.
   * If both aFile and aString are null, the encoded data is copied to the
   * clipboard.
   *
   * @param aImageType The image type that the surface is to be encoded to.
   *   Used to create an appropriate imgIEncoder instance to do the encoding.
   *
   * @param aOutputOptions Passed directly to imgIEncoder::InitFromData as
   *   the value of the |outputOptions| parameter. Callers are responsible
   *   for making sure that this is a reasonable value for the passed MIME-type
   *   (i.e. for the type of encoder that will be created).
   *
   * @aBinaryOrData Flag used to determine if the surface is simply encoded
   *   to the requested binary image format, or if the binary image is
   *   further converted to base-64 and written out as a 'data:' URI.
   *
   * @aFile If specified, the encoded data is written out to aFile.
   *
   * @aString If specified, the encoded data is written out to aString.
   *
   * TODO: Copying to the clipboard as a binary file is not currently
   * supported.
   */
  static nsresult EncodeSourceSurface(SourceSurface* aSurface,
                                      const ImageType aImageType,
                                      const nsAString& aOutputOptions,
                                      BinaryOrData aBinaryOrData, FILE* aFile,
                                      nsACString* aString = nullptr);

  /**
   * Encodes the given surface to PNG/JPEG/BMP/etc. using imgIEncoder
   * and returns the result as an nsIInputStream.
   *
   * @param aSurface The source surface to encode
   *
   * @param aImageType The image type that the surface is to be encoded to.
   *   Used to create an appropriate imgIEncoder instance to do the encoding.
   *
   * @param aOutputOptions Passed directly to imgIEncoder::InitFromData as
   *   the value of the |outputOptions| parameter. Callers are responsible
   *   for making sure that this is a reasonable value for the passed MIME-type
   *   (i.e. for the type of encoder that will be created).
   *
   * @param aOutStream pointer to the output stream
   *
   */
  static nsresult EncodeSourceSurfaceAsStream(SourceSurface* aSurface,
                                              const ImageType aImageType,
                                              const nsAString& aOutputOptions,
                                              nsIInputStream** aOutStream);

  /**
   * Encodes the given surface to PNG/JPEG/BMP/etc. using imgIEncoder
   * and returns the result as a vector of bytes
   *
   * @param aImageType The image type that the surface is to be encoded to.
   *   Used to create an appropriate imgIEncoder instance to do the encoding.
   *
   * @param aOutputOptions Passed directly to imgIEncoder::InitFromData as
   *   the value of the |outputOptions| parameter. Callers are responsible
   *   for making sure that this is a reasonable value for the passed MIME-type
   *   (i.e. for the type of encoder that will be created).
   *
   */
  static mozilla::Maybe<nsTArray<uint8_t>> EncodeSourceSurfaceAsBytes(
      SourceSurface* aSurface, const ImageType aImageType,
      const nsAString& aOutputOptions);

  /**
   * Write as a PNG file to the path aFile.
   */
  static void WriteAsPNG(SourceSurface* aSurface, const nsAString& aFile);
  static void WriteAsPNG(SourceSurface* aSurface, const char* aFile);
  static void WriteAsPNG(DrawTarget* aDT, const nsAString& aFile);
  static void WriteAsPNG(DrawTarget* aDT, const char* aFile);

  /**
   * Dump as a PNG encoded Data URL to a FILE stream (using stdout by
   * default).
   *
   * Rather than giving aFile a default argument we have separate functions
   * to make them easier to use from a debugger.
   */
  static void DumpAsDataURI(SourceSurface* aSourceSurface, FILE* aFile);
  static inline void DumpAsDataURI(SourceSurface* aSourceSurface) {
    DumpAsDataURI(aSourceSurface, stdout);
  }
  static void DumpAsDataURI(DrawTarget* aDT, FILE* aFile);
  static inline void DumpAsDataURI(DrawTarget* aDT) {
    DumpAsDataURI(aDT, stdout);
  }
  static nsCString GetAsDataURI(SourceSurface* aSourceSurface);
  static nsCString GetAsDataURI(DrawTarget* aDT);
  static nsCString GetAsLZ4Base64Str(DataSourceSurface* aSourceSurface);

  static mozilla::UniquePtr<uint8_t[]> GetImageBuffer(
      DataSourceSurface* aSurface, bool aIsAlphaPremultiplied,
      int32_t* outFormat);

  static mozilla::UniquePtr<uint8_t[]> GetImageBufferWithRandomNoise(
      DataSourceSurface* aSurface, bool aIsAlphaPremultiplied,
      nsICookieJarSettings* aCookieJarSettings, int32_t* outFormat);

  static nsresult GetInputStream(DataSourceSurface* aSurface,
                                 bool aIsAlphaPremultiplied,
                                 const char* aMimeType,
                                 const nsAString& aEncoderOptions,
                                 nsIInputStream** outStream);

  static nsresult GetInputStreamWithRandomNoise(
      DataSourceSurface* aSurface, bool aIsAlphaPremultiplied,
      const char* aMimeType, const nsAString& aEncoderOptions,
      nsICookieJarSettings* aCookieJarSettings, nsIInputStream** outStream);

  static void RemoveShaderCacheFromDiskIfNecessary();

  /**
   * Copy to the clipboard as a PNG encoded Data URL.
   */
  static void CopyAsDataURI(SourceSurface* aSourceSurface);
  static void CopyAsDataURI(DrawTarget* aDT);

  static bool DumpDisplayList();

  static FILE* sDumpPaintFile;
};

namespace mozilla {

// Container for either a single element of type T, or an nsTArray<T>.
// Provides a minimal subset of nsTArray's API, just enough to support use
// by ContextState for the clipsAndTransforms list, and by gfxTextRun for
// its mGlyphRuns.
// Using this instead of a simple nsTArray avoids an extra allocation in the
// common case where no more than one element is ever added to the list.
// Unlike an AutoTArray<..., 1>, this class is memmovable and therefore can
// be used in ContextState without breaking its movability.
template <typename T>
class ElementOrArray {
  union {
    T mElement;
    nsTArray<T> mArray;
  };
  enum class Tag : uint8_t {
    Element,
    Array,
  } mTag;

  // gfxTextRun::SortGlyphRuns and SanitizeGlyphRuns directly access the array.
  friend class ::gfxTextRun;
  nsTArray<T>& Array() {
    MOZ_DIAGNOSTIC_ASSERT(mTag == Tag::Array);
    return mArray;
  }

 public:
  // Construct as an empty array.
  ElementOrArray() : mTag(Tag::Array) { new (&mArray) nsTArray<T>(); }

  // For now, don't support copy/move.
  ElementOrArray(const ElementOrArray&) = delete;
  ElementOrArray(ElementOrArray&&) = delete;

  ElementOrArray& operator=(const ElementOrArray&) = delete;
  ElementOrArray& operator=(ElementOrArray&&) = delete;

  // Destroy the appropriate variant.
  ~ElementOrArray() {
    switch (mTag) {
      case Tag::Element:
        mElement.~T();
        break;
      case Tag::Array:
        mArray.~nsTArray();
        break;
    }
  }

  size_t Length() const { return mTag == Tag::Element ? 1 : mArray.Length(); }

  T* AppendElement(const T& aElement) {
    switch (mTag) {
      case Tag::Element: {
        // Move the existing element into an array, then append the new one.
        T temp = std::move(mElement);
        mElement.~T();
        mTag = Tag::Array;
        new (&mArray) nsTArray<T>();
        mArray.AppendElement(std::move(temp));
        return mArray.AppendElement(aElement);
      }
      case Tag::Array: {
        // If currently empty, just store the element directly.
        if (mArray.IsEmpty()) {
          mArray.~nsTArray();
          mTag = Tag::Element;
          new (&mElement) T(aElement);
          return &mElement;
        }
        // Otherwise, append it to the array.
        return mArray.AppendElement(aElement);
      }
      default:
        MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("invalid tag");
    }
  }

  const T& LastElement() const {
    return mTag == Tag::Element ? mElement : mArray.LastElement();
  }

  T& LastElement() {
    return mTag == Tag::Element ? mElement : mArray.LastElement();
  }

  bool IsEmpty() const {
    return mTag == Tag::Element ? false : mArray.IsEmpty();
  }

  void TruncateLength(uint32_t aLength = 0) {
    MOZ_DIAGNOSTIC_ASSERT(aLength <= Length());
    switch (mTag) {
      case Tag::Element:
        if (aLength == 0) {
          // Destroy the single element, and convert to an empty array.
          mElement.~T();
          mTag = Tag::Array;
          new (&mArray) nsTArray<T>();
        }
        break;
      case Tag::Array:
        mArray.TruncateLength(aLength);
        break;
    }
  }

  void Clear() {
    switch (mTag) {
      case Tag::Element:
        mElement.~T();
        mTag = Tag::Array;
        new (&mArray) nsTArray<T>();
        break;
      case Tag::Array:
        mArray.Clear();
        break;
    }
  }

  // Convert from Array to Element storage. Only to be used when the current
  // state is a single-element array!
  void ConvertToElement() {
    MOZ_DIAGNOSTIC_ASSERT(mTag == Tag::Array && mArray.Length() == 1);
    T temp = std::move(mArray[0]);
    mArray.~nsTArray();
    mTag = Tag::Element;
    new (&mElement) T(std::move(temp));
  }

  const T& operator[](uint32_t aIndex) const {
    MOZ_DIAGNOSTIC_ASSERT(aIndex < Length());
    return mTag == Tag::Element ? mElement : mArray[aIndex];
  }
  T& operator[](uint32_t aIndex) {
    MOZ_DIAGNOSTIC_ASSERT(aIndex < Length());
    return mTag == Tag::Element ? mElement : mArray[aIndex];
  }

  // Simple iterators to support range-for loops.
  const T* begin() const {
    return mTag == Tag::Array ? mArray.IsEmpty() ? nullptr : &*mArray.begin()
                              : &mElement;
  }
  T* begin() {
    return mTag == Tag::Array ? mArray.IsEmpty() ? nullptr : &*mArray.begin()
                              : &mElement;
  }

  const T* end() const {
    return mTag == Tag::Array ? begin() + mArray.Length() : &mElement + 1;
  }
  T* end() {
    return mTag == Tag::Array ? begin() + mArray.Length() : &mElement + 1;
  }

  size_t ShallowSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) {
    return mTag == Tag::Array ? mArray.ShallowSizeOfExcludingThis(aMallocSizeOf)
                              : 0;
  }
};

struct StyleAbsoluteColor;

namespace gfx {

/**
 * If the CMS mode is CMSMode::All, these functions transform the passed
 * color to a device color using the transform returned by
 * gfxPlatform::GetCMSRGBTransform().  If the CMS mode is some other value, the
 * color is returned unchanged (other than a type change to Moz2D Color, if
 * applicable).
 */
DeviceColor ToDeviceColor(const sRGBColor&);
DeviceColor ToDeviceColor(const StyleAbsoluteColor&);
DeviceColor ToDeviceColor(nscolor);

sRGBColor ToSRGBColor(const StyleAbsoluteColor&);

/**
 * Performs a checked multiply of the given width, height, and bytes-per-pixel
 * values.
 */
static inline CheckedInt<uint32_t> SafeBytesForBitmap(uint32_t aWidth,
                                                      uint32_t aHeight,
                                                      unsigned aBytesPerPixel) {
  MOZ_ASSERT(aBytesPerPixel > 0);
  CheckedInt<uint32_t> width = uint32_t(aWidth);
  CheckedInt<uint32_t> height = uint32_t(aHeight);
  return width * height * aBytesPerPixel;
}

}  // namespace gfx
}  // namespace mozilla

#endif