/* 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/. */

#include "gtest/gtest.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/gfx/Swizzle.h"
#include "Orientation.h"
#include <bit>

using namespace mozilla;
using namespace mozilla::gfx;
using namespace mozilla::image;

TEST(Moz2D, PremultiplyData)
{
  const uint8_t in_bgra[5 * 4] = {
      255, 255, 0,   255,  // verify 255 alpha leaves RGB unchanged
      0,   0,   255, 255,
      0,   255, 255, 0,  // verify 0 alpha zeroes out RGB
      0,   0,   0,   0,
      255, 0,   0,   128,  // verify that 255 RGB maps to alpha
  };
  uint8_t out[5 * 4];
  const uint8_t check_bgra[5 * 4] = {
      255, 255, 0, 255, 0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 128,
  };
  // check swizzled output
  const uint8_t check_rgba[5 * 4] = {
      0, 255, 255, 255, 255, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 128,
  };
  const uint8_t check_argb[5 * 4] = {
      255, 0, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 128,
  };

  PremultiplyData(in_bgra, sizeof(in_bgra), SurfaceFormat::B8G8R8A8, out,
                  sizeof(in_bgra), SurfaceFormat::B8G8R8A8, IntSize(5, 1));
  EXPECT_TRUE(ArrayEqual(out, check_bgra));

  PremultiplyData(in_bgra, sizeof(in_bgra), SurfaceFormat::B8G8R8A8, out,
                  sizeof(in_bgra), SurfaceFormat::R8G8B8A8, IntSize(5, 1));
  EXPECT_TRUE(ArrayEqual(out, check_rgba));

  PremultiplyData(in_bgra, sizeof(in_bgra), SurfaceFormat::B8G8R8A8, out,
                  sizeof(in_bgra), SurfaceFormat::A8R8G8B8, IntSize(5, 1));
  EXPECT_TRUE(ArrayEqual(out, check_argb));
}

TEST(Moz2D, PremultiplyRow)
{
  const uint8_t in_bgra[5 * 4] = {
      255, 255, 0,   255,  // verify 255 alpha leaves RGB unchanged
      0,   0,   255, 255,
      0,   255, 255, 0,  // verify 0 alpha zeroes out RGB
      0,   0,   0,   0,
      255, 0,   0,   128,  // verify that 255 RGB maps to alpha
  };
  uint8_t out[5 * 4];
  const uint8_t check_bgra[5 * 4] = {
      255, 255, 0, 255, 0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 128,
  };
  // check swizzled output
  const uint8_t check_rgba[5 * 4] = {
      0, 255, 255, 255, 255, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 128,
  };
  const uint8_t check_argb[5 * 4] = {
      255, 0, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 128,
  };

  SwizzleRowFn func =
      PremultiplyRow(SurfaceFormat::B8G8R8A8, SurfaceFormat::B8G8R8A8);
  func(in_bgra, out, 5);
  EXPECT_TRUE(ArrayEqual(out, check_bgra));

  func = PremultiplyRow(SurfaceFormat::B8G8R8A8, SurfaceFormat::R8G8B8A8);
  func(in_bgra, out, 5);
  EXPECT_TRUE(ArrayEqual(out, check_rgba));

  func = PremultiplyRow(SurfaceFormat::B8G8R8A8, SurfaceFormat::A8R8G8B8);
  func(in_bgra, out, 5);
  EXPECT_TRUE(ArrayEqual(out, check_argb));
}

TEST(Moz2D, PremultiplyYFlipData)
{
  const uint8_t stride = 2 * 4;
  const uint8_t in_bgra[6 * 4] = {
      255, 255, 0,   255,  // row 1: verify 255 alpha leaves RGB unchanged
      0,   0,   255, 255,
      0,   255, 255, 0,  // row 2: verify 0 alpha zeroes out RGB
      0,   0,   0,   0,
      255, 0,   0,   128,  // row 3: verify that 255 RGB maps to alpha
      255, 255, 255, 128,
  };
  const uint8_t in_bgra_2[4 * 4] = {
      255, 255, 0,   255,  // row 1: verify 255 alpha leaves RGB unchanged
      0,   0,   255, 255,
      0,   255, 255, 0,  // row 2: verify 0 alpha zeroes out RGB
      0,   0,   0,   0,
  };
  const uint8_t in_bgra_3[2 * 4] = {
      255, 0,   0,   128,  // row 1: verify that 255 RGB maps to alpha
      255, 255, 255, 128,
  };
  uint8_t out[6 * 4];
  uint8_t out_2[4 * 4];
  uint8_t out_3[2 * 4];
  const uint8_t check_bgra[6 * 4] = {
      128, 0, 0, 128, 128, 128, 128, 128, 0, 0, 0,   0,
      0,   0, 0, 0,   255, 255, 0,   255, 0, 0, 255, 255,
  };
  const uint8_t check_bgra_2[4 * 4] = {
      0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 0, 255, 0, 0, 255, 255,
  };
  const uint8_t check_bgra_3[2 * 4] = {
      128, 0, 0, 128, 128, 128, 128, 128,
  };
  // check swizzled output
  const uint8_t check_rgba[6 * 4] = {
      0, 0, 128, 128, 128, 128, 128, 128, 0,   0, 0, 0,
      0, 0, 0,   0,   0,   255, 255, 255, 255, 0, 0, 255,
  };

  // Premultiply.
  PremultiplyYFlipData(in_bgra, stride, SurfaceFormat::B8G8R8A8, out, stride,
                       SurfaceFormat::B8G8R8A8, IntSize(2, 3));
  EXPECT_TRUE(ArrayEqual(out, check_bgra));

  // Premultiply in-place with middle row.
  memcpy(out, in_bgra, sizeof(out));
  PremultiplyYFlipData(out, stride, SurfaceFormat::B8G8R8A8, out, stride,
                       SurfaceFormat::B8G8R8A8, IntSize(2, 3));
  EXPECT_TRUE(ArrayEqual(out, check_bgra));

  // Premultiply in-place without middle row.
  memcpy(out_2, in_bgra_2, sizeof(out_2));
  PremultiplyYFlipData(out_2, stride, SurfaceFormat::B8G8R8A8, out_2, stride,
                       SurfaceFormat::B8G8R8A8, IntSize(2, 2));
  EXPECT_TRUE(ArrayEqual(out_2, check_bgra_2));

  // Premultiply in-place only middle row.
  memcpy(out_3, in_bgra_3, sizeof(out_3));
  PremultiplyYFlipData(out_3, stride, SurfaceFormat::B8G8R8A8, out_3, stride,
                       SurfaceFormat::B8G8R8A8, IntSize(2, 1));
  EXPECT_TRUE(ArrayEqual(out_3, check_bgra_3));

  // Premultiply and swizzle with middle row.
  PremultiplyYFlipData(in_bgra, stride, SurfaceFormat::B8G8R8A8, out, stride,
                       SurfaceFormat::R8G8B8A8, IntSize(2, 3));
  EXPECT_TRUE(ArrayEqual(out, check_rgba));
}

TEST(Moz2D, UnpremultiplyData)
{
  const uint8_t in_bgra[5 * 4] = {
      255, 255, 0,   255,              // verify 255 alpha leaves RGB unchanged
      0,   0,   255, 255, 0, 0, 0, 0,  // verify 0 alpha leaves RGB at 0
      0,   0,   0,   64,   // verify 0 RGB stays 0 with non-zero alpha
      128, 0,   0,   128,  // verify that RGB == alpha maps to 255

  };
  uint8_t out[5 * 4];
  const uint8_t check_bgra[5 * 4] = {
      255, 255, 0, 255, 0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 64, 255, 0, 0, 128,
  };
  // check swizzled output
  const uint8_t check_rgba[5 * 4] = {
      0, 255, 255, 255, 255, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 255, 128,
  };
  const uint8_t check_argb[5 * 4] = {
      255, 0, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, 128, 0, 0, 255,
  };

  UnpremultiplyData(in_bgra, sizeof(in_bgra), SurfaceFormat::B8G8R8A8, out,
                    sizeof(in_bgra), SurfaceFormat::B8G8R8A8, IntSize(5, 1));
  EXPECT_TRUE(ArrayEqual(out, check_bgra));

  UnpremultiplyData(in_bgra, sizeof(in_bgra), SurfaceFormat::B8G8R8A8, out,
                    sizeof(in_bgra), SurfaceFormat::R8G8B8A8, IntSize(5, 1));
  EXPECT_TRUE(ArrayEqual(out, check_rgba));

  UnpremultiplyData(in_bgra, sizeof(in_bgra), SurfaceFormat::B8G8R8A8, out,
                    sizeof(in_bgra), SurfaceFormat::A8R8G8B8, IntSize(5, 1));
  EXPECT_TRUE(ArrayEqual(out, check_argb));
}

TEST(Moz2D, UnpremultiplyRow)
{
  const uint8_t in_bgra[5 * 4] = {
      255, 255, 0,   255,              // verify 255 alpha leaves RGB unchanged
      0,   0,   255, 255, 0, 0, 0, 0,  // verify 0 alpha leaves RGB at 0
      0,   0,   0,   64,   // verify 0 RGB stays 0 with non-zero alpha
      128, 0,   0,   128,  // verify that RGB == alpha maps to 255

  };
  uint8_t out[5 * 4];
  const uint8_t check_bgra[5 * 4] = {
      255, 255, 0, 255, 0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 64, 255, 0, 0, 128,
  };
  // check swizzled output
  const uint8_t check_rgba[5 * 4] = {
      0, 255, 255, 255, 255, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 255, 128,
  };
  const uint8_t check_argb[5 * 4] = {
      255, 0, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, 128, 0, 0, 255,
  };

  SwizzleRowFn func =
      UnpremultiplyRow(SurfaceFormat::B8G8R8A8, SurfaceFormat::B8G8R8A8);
  func(in_bgra, out, 5);
  EXPECT_TRUE(ArrayEqual(out, check_bgra));

  func = UnpremultiplyRow(SurfaceFormat::B8G8R8A8, SurfaceFormat::R8G8B8A8);
  func(in_bgra, out, 5);
  EXPECT_TRUE(ArrayEqual(out, check_rgba));

  func = UnpremultiplyRow(SurfaceFormat::B8G8R8A8, SurfaceFormat::A8R8G8B8);
  func(in_bgra, out, 5);
  EXPECT_TRUE(ArrayEqual(out, check_argb));
}

TEST(Moz2D, SwizzleData)
{
  const uint8_t in_bgra[5 * 4] = {
      253, 254, 0, 255, 0, 0, 255, 255, 0, 0, 0, 0, 1, 2, 3, 64, 127, 0, 9, 128,

  };
  uint8_t out[5 * 4];
  // check copy
  const uint8_t check_bgra[5 * 4] = {
      253, 254, 0, 255, 0, 0, 255, 255, 0, 0, 0, 0, 1, 2, 3, 64, 127, 0, 9, 128,
  };
  // check swaps
  const uint8_t check_rgba[5 * 4] = {
      0, 254, 253, 255, 255, 0, 0, 255, 0, 0, 0, 0, 3, 2, 1, 64, 9, 0, 127, 128,
  };
  const uint8_t check_argb[5 * 4] = {
      255, 0, 254, 253, 255, 255, 0, 0, 0, 0, 0, 0, 64, 3, 2, 1, 128, 9, 0, 127,
  };
  // check opaquifying
  const uint8_t check_rgbx[5 * 4] = {
      0, 254, 253, 255, 255, 0,   0, 255, 0,   0,
      0, 255, 3,   2,   1,   255, 9, 0,   127, 255,
  };
  // check packing
  uint8_t out24[5 * 3];
  const uint8_t check_bgr[5 * 3] = {253, 254, 0, 0, 0,   255, 0, 0,
                                    0,   1,   2, 3, 127, 0,   9};
  const uint8_t check_rgb[5 * 3] = {
      0, 254, 253, 255, 0, 0, 0, 0, 0, 3, 2, 1, 9, 0, 127,
  };
  uint8_t out8[5];
  const uint8_t check_a[5] = {255, 255, 0, 64, 128};
  uint16_t out16[5];
#define PACK_RGB565(b, g, r) \
  (((b & 0xF8) >> 3) | ((g & 0xFC) << 3) | ((r & 0xF8) << 8))
  const uint16_t check_16[5] = {
      PACK_RGB565(253, 254, 0), PACK_RGB565(0, 0, 255), PACK_RGB565(0, 0, 0),
      PACK_RGB565(1, 2, 3),     PACK_RGB565(127, 0, 9),
  };

  SwizzleData(in_bgra, sizeof(in_bgra), SurfaceFormat::B8G8R8A8, out,
              sizeof(out), SurfaceFormat::B8G8R8A8, IntSize(5, 1));
  EXPECT_TRUE(ArrayEqual(out, check_bgra));

  SwizzleData(in_bgra, sizeof(in_bgra), SurfaceFormat::B8G8R8A8, out,
              sizeof(out), SurfaceFormat::R8G8B8A8, IntSize(5, 1));
  EXPECT_TRUE(ArrayEqual(out, check_rgba));

  SwizzleData(in_bgra, sizeof(in_bgra), SurfaceFormat::B8G8R8A8, out,
              sizeof(out), SurfaceFormat::A8R8G8B8, IntSize(5, 1));
  EXPECT_TRUE(ArrayEqual(out, check_argb));

  SwizzleData(in_bgra, sizeof(in_bgra), SurfaceFormat::B8G8R8A8, out,
              sizeof(out), SurfaceFormat::R8G8B8X8, IntSize(5, 1));
  EXPECT_TRUE(ArrayEqual(out, check_rgbx));

  SwizzleData(in_bgra, sizeof(in_bgra), SurfaceFormat::B8G8R8A8, out24,
              sizeof(out24), SurfaceFormat::B8G8R8, IntSize(5, 1));
  EXPECT_TRUE(ArrayEqual(out24, check_bgr));

  SwizzleData(in_bgra, sizeof(in_bgra), SurfaceFormat::B8G8R8A8, out24,
              sizeof(out24), SurfaceFormat::R8G8B8, IntSize(5, 1));
  EXPECT_TRUE(ArrayEqual(out24, check_rgb));

  SwizzleData(in_bgra, sizeof(in_bgra), SurfaceFormat::B8G8R8A8, out8,
              sizeof(out8), SurfaceFormat::A8, IntSize(5, 1));
  EXPECT_TRUE(ArrayEqual(out8, check_a));

  const uint8_t* uint32_argb;

  if constexpr (std::endian::native == std::endian::big) {
    EXPECT_EQ(SurfaceFormat::A8R8G8B8_UINT32, SurfaceFormat::A8R8G8B8);
    uint32_argb = check_argb;
  } else {
    EXPECT_EQ(SurfaceFormat::A8R8G8B8_UINT32, SurfaceFormat::B8G8R8A8);
    uint32_argb = check_bgra;
  }

  SwizzleData(uint32_argb, sizeof(in_bgra), SurfaceFormat::A8R8G8B8_UINT32,
              reinterpret_cast<uint8_t*>(out16), sizeof(out16),
              SurfaceFormat::R5G6B5_UINT16, IntSize(5, 1));
  EXPECT_TRUE(ArrayEqual(out16, check_16));
}

TEST(Moz2D, SwizzleYFlipData)
{
  const uint8_t stride = 2 * 4;
  const uint8_t in_bgra[6 * 4] = {
      255, 255, 0,   255,                      // row 1
      0,   0,   255, 255, 0,   255, 255, 0,    // row 2
      0,   0,   0,   0,   255, 0,   0,   128,  // row 3
      255, 255, 255, 128,
  };
  const uint8_t in_bgra_2[4 * 4] = {
      255, 255, 0,   255,                  // row 1
      0,   0,   255, 255, 0, 255, 255, 0,  // row 2
      0,   0,   0,   0,
  };
  const uint8_t in_bgra_3[2 * 4] = {
      255, 0,   0,   128,  // row 1
      255, 255, 255, 128,
  };
  uint8_t out[6 * 4];
  uint8_t out_2[4 * 4];
  uint8_t out_3[2 * 4];
  const uint8_t check_rgba[6 * 4] = {
      0, 0, 255, 128, 255, 255, 255, 128, 255, 255, 0, 0,
      0, 0, 0,   0,   0,   255, 255, 255, 255, 0,   0, 255,
  };
  const uint8_t check_rgba_2[4 * 4] = {
      255, 255, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 255,
  };
  const uint8_t check_rgba_3[2 * 4] = {
      0, 0, 255, 128, 255, 255, 255, 128,
  };

  // Swizzle.
  SwizzleYFlipData(in_bgra, stride, SurfaceFormat::B8G8R8A8, out, stride,
                   SurfaceFormat::R8G8B8A8, IntSize(2, 3));
  EXPECT_TRUE(ArrayEqual(out, check_rgba));

  // Swizzle in-place with middle row.
  memcpy(out, in_bgra, sizeof(out));
  SwizzleYFlipData(out, stride, SurfaceFormat::B8G8R8A8, out, stride,
                   SurfaceFormat::R8G8B8A8, IntSize(2, 3));
  EXPECT_TRUE(ArrayEqual(out, check_rgba));

  // Swizzle in-place without middle row.
  memcpy(out_2, in_bgra_2, sizeof(out_2));
  SwizzleYFlipData(out_2, stride, SurfaceFormat::B8G8R8A8, out_2, stride,
                   SurfaceFormat::R8G8B8A8, IntSize(2, 2));
  EXPECT_TRUE(ArrayEqual(out_2, check_rgba_2));

  // Swizzle in-place only middle row.
  memcpy(out_3, in_bgra_3, sizeof(out_3));
  SwizzleYFlipData(out_3, stride, SurfaceFormat::B8G8R8A8, out_3, stride,
                   SurfaceFormat::R8G8B8A8, IntSize(2, 1));
  EXPECT_TRUE(ArrayEqual(out_3, check_rgba_3));
}

TEST(Moz2D, SwizzleRow)
{
  const uint8_t in_bgra[5 * 4] = {
      253, 254, 0, 255, 0, 0, 255, 255, 0, 0, 0, 0, 1, 2, 3, 64, 127, 0, 9, 128,

  };
  uint8_t out[5 * 4];
  // check swaps
  const uint8_t check_rgba[5 * 4] = {
      0, 254, 253, 255, 255, 0, 0, 255, 0, 0, 0, 0, 3, 2, 1, 64, 9, 0, 127, 128,
  };
  // check opaquifying
  const uint8_t check_rgbx[5 * 4] = {
      0, 254, 253, 255, 255, 0,   0, 255, 0,   0,
      0, 255, 3,   2,   1,   255, 9, 0,   127, 255,
  };
  // check packing
  uint8_t out24[5 * 3];
  const uint8_t check_bgr[5 * 3] = {253, 254, 0, 0, 0,   255, 0, 0,
                                    0,   1,   2, 3, 127, 0,   9};
  const uint8_t check_rgb[5 * 3] = {
      0, 254, 253, 255, 0, 0, 0, 0, 0, 3, 2, 1, 9, 0, 127,
  };
  // check unpacking
  uint8_t out_unpack[16 * 4];
  const uint8_t in_rgb[16 * 3] = {
      0,  254, 253, 255, 0,  0,  0,  0,  0,  3,  2,  1,  9,  0,  127, 4,
      5,  6,   9,   8,   7,  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,
  };
  const uint8_t check_unpack_rgbx[16 * 4] = {
      0,  254, 253, 255, 255, 0,  0,  255, 0,  0,  0,  255, 3,  2,  1,  255,
      9,  0,   127, 255, 4,   5,  6,  255, 9,  8,  7,  255, 10, 11, 12, 255,
      13, 14,  15,  255, 16,  17, 18, 255, 19, 20, 21, 255, 22, 23, 24, 255,
      25, 26,  27,  255, 28,  29, 30, 255, 31, 32, 33, 255, 34, 35, 36, 255,
  };
  const uint8_t check_unpack_bgrx[16 * 4] = {
      253, 254, 0,  255, 0,  0,  255, 255, 0,  0,  0,  255, 1,  2,  3,  255,
      127, 0,   9,  255, 6,  5,  4,   255, 7,  8,  9,  255, 12, 11, 10, 255,
      15,  14,  13, 255, 18, 17, 16,  255, 21, 20, 19, 255, 24, 23, 22, 255,
      27,  26,  25, 255, 30, 29, 28,  255, 33, 32, 31, 255, 36, 35, 34, 255,
  };
  const uint8_t check_unpack_xrgb[16 * 4] = {
      255, 0,  254, 253, 255, 255, 0,  0,  255, 0,  0,  0,  255, 3,  2,  1,
      255, 9,  0,   127, 255, 4,   5,  6,  255, 9,  8,  7,  255, 10, 11, 12,
      255, 13, 14,  15,  255, 16,  17, 18, 255, 19, 20, 21, 255, 22, 23, 24,
      255, 25, 26,  27,  255, 28,  29, 30, 255, 31, 32, 33, 255, 34, 35, 36,
  };

  SwizzleRowFn func =
      SwizzleRow(SurfaceFormat::B8G8R8A8, SurfaceFormat::R8G8B8A8);
  func(in_bgra, out, 5);
  EXPECT_TRUE(ArrayEqual(out, check_rgba));

  func = SwizzleRow(SurfaceFormat::B8G8R8A8, SurfaceFormat::R8G8B8X8);
  func(in_bgra, out, 5);
  EXPECT_TRUE(ArrayEqual(out, check_rgbx));

  func = SwizzleRow(SurfaceFormat::B8G8R8A8, SurfaceFormat::B8G8R8A8);
  func(in_bgra, out, 5);
  EXPECT_TRUE(ArrayEqual(out, in_bgra));

  func = SwizzleRow(SurfaceFormat::B8G8R8A8, SurfaceFormat::B8G8R8);
  func(in_bgra, out24, 5);
  EXPECT_TRUE(ArrayEqual(out24, check_bgr));

  func = SwizzleRow(SurfaceFormat::B8G8R8A8, SurfaceFormat::R8G8B8);
  func(in_bgra, out24, 5);
  EXPECT_TRUE(ArrayEqual(out24, check_rgb));

  func = SwizzleRow(SurfaceFormat::R8G8B8, SurfaceFormat::B8G8R8X8);
  func(in_rgb, out_unpack, 16);
  EXPECT_TRUE(ArrayEqual(out_unpack, check_unpack_bgrx));

  memset(out_unpack, 0xE5, sizeof(out_unpack));
  memcpy(out_unpack, in_rgb, sizeof(in_rgb));
  func(out_unpack, out_unpack, 16);
  EXPECT_TRUE(ArrayEqual(out_unpack, check_unpack_bgrx));

  func = SwizzleRow(SurfaceFormat::R8G8B8, SurfaceFormat::R8G8B8X8);
  func(in_rgb, out_unpack, 16);
  EXPECT_TRUE(ArrayEqual(out_unpack, check_unpack_rgbx));

  memset(out_unpack, 0xE5, sizeof(out_unpack));
  memcpy(out_unpack, in_rgb, sizeof(in_rgb));
  func(out_unpack, out_unpack, 16);
  EXPECT_TRUE(ArrayEqual(out_unpack, check_unpack_rgbx));

  func = SwizzleRow(SurfaceFormat::R8G8B8, SurfaceFormat::X8R8G8B8);
  func(in_rgb, out_unpack, 16);
  EXPECT_TRUE(ArrayEqual(out_unpack, check_unpack_xrgb));

  memset(out_unpack, 0xE5, sizeof(out_unpack));
  memcpy(out_unpack, in_rgb, sizeof(in_rgb));
  func(out_unpack, out_unpack, 16);
  EXPECT_TRUE(ArrayEqual(out_unpack, check_unpack_xrgb));
}

TEST(Moz2D, ReorientRow)
{
  // Input is a 3x4 image.
  const uint8_t in_row0[3 * 4] = {
      0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
  };
  const uint8_t in_row1[3 * 4] = {
      12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
  };
  const uint8_t in_row2[3 * 4] = {
      24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35,
  };
  const uint8_t in_row3[3 * 4] = {
      36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
  };

  // Output is either a 3x4 image or 4x3 image.
  uint8_t out[3 * 4 * 4];
  IntSize outSize(3, 4);
  IntSize outSizeSwap(4, 3);
  int32_t outStride = 3 * 4;
  int32_t outStrideSwap = 4 * 4;
  IntRect dirty;

  auto func = ReorientRow(Orientation());
  dirty = func(in_row0, 0, out, outSize, outStride);
  EXPECT_EQ(dirty, IntRect(0, 0, 3, 1));
  dirty = func(in_row1, 1, out, outSize, outStride);
  EXPECT_EQ(dirty, IntRect(0, 1, 3, 1));
  dirty = func(in_row2, 2, out, outSize, outStride);
  EXPECT_EQ(dirty, IntRect(0, 2, 3, 1));
  dirty = func(in_row3, 3, out, outSize, outStride);
  EXPECT_EQ(dirty, IntRect(0, 3, 3, 1));

  // clang-format off
  const uint8_t check_identity[3 * 4 * 4] = {
      0,  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,
  };
  // clang-format on
  EXPECT_TRUE(ArrayEqual(out, check_identity));

  func = ReorientRow(Orientation(Angle::D90));
  dirty = func(in_row0, 0, out, outSizeSwap, outStrideSwap);
  EXPECT_EQ(dirty, IntRect(3, 0, 1, 3));
  dirty = func(in_row1, 1, out, outSizeSwap, outStrideSwap);
  EXPECT_EQ(dirty, IntRect(2, 0, 1, 3));
  dirty = func(in_row2, 2, out, outSizeSwap, outStrideSwap);
  EXPECT_EQ(dirty, IntRect(1, 0, 1, 3));
  dirty = func(in_row3, 3, out, outSizeSwap, outStrideSwap);
  EXPECT_EQ(dirty, IntRect(0, 0, 1, 3));

  // clang-format off
  const uint8_t check_d90[3 * 4 * 4] = {
      36, 37, 38, 39, 24, 25, 26, 27, 12, 13, 14, 15, 0,  1,  2,  3,
      40, 41, 42, 43, 28, 29, 30, 31, 16, 17, 18, 19, 4,  5,  6,  7,
      44, 45, 46, 47, 32, 33, 34, 35, 20, 21, 22, 23, 8,  9,  10, 11,
  };
  // clang-format on
  EXPECT_TRUE(ArrayEqual(out, check_d90));

  func = ReorientRow(Orientation(Angle::D180));
  dirty = func(in_row0, 0, out, outSize, outStride);
  EXPECT_EQ(dirty, IntRect(0, 3, 3, 1));
  dirty = func(in_row1, 1, out, outSize, outStride);
  EXPECT_EQ(dirty, IntRect(0, 2, 3, 1));
  dirty = func(in_row2, 2, out, outSize, outStride);
  EXPECT_EQ(dirty, IntRect(0, 1, 3, 1));
  dirty = func(in_row3, 3, out, outSize, outStride);
  EXPECT_EQ(dirty, IntRect(0, 0, 3, 1));

  // clang-format off
  const uint8_t check_d180[3 * 4 * 4] = {
      44, 45, 46, 47, 40, 41, 42, 43, 36, 37, 38, 39,
      32, 33, 34, 35, 28, 29, 30, 31, 24, 25, 26, 27,
      20, 21, 22, 23, 16, 17, 18, 19, 12, 13, 14, 15,
      8,  9,  10, 11, 4,  5,  6,  7,  0,  1,  2,  3,
  };
  // clang-format on
  EXPECT_TRUE(ArrayEqual(out, check_d180));

  func = ReorientRow(Orientation(Angle::D270));
  dirty = func(in_row0, 0, out, outSizeSwap, outStrideSwap);
  EXPECT_EQ(dirty, IntRect(0, 0, 1, 3));
  dirty = func(in_row1, 1, out, outSizeSwap, outStrideSwap);
  EXPECT_EQ(dirty, IntRect(1, 0, 1, 3));
  dirty = func(in_row2, 2, out, outSizeSwap, outStrideSwap);
  EXPECT_EQ(dirty, IntRect(2, 0, 1, 3));
  dirty = func(in_row3, 3, out, outSizeSwap, outStrideSwap);
  EXPECT_EQ(dirty, IntRect(3, 0, 1, 3));

  // clang-format off
  const uint8_t check_d270[3 * 4 * 4] = {
      8,  9,  10, 11, 20, 21, 22, 23, 32, 33, 34, 35, 44, 45, 46, 47,
      4,  5,  6,  7,  16, 17, 18, 19, 28, 29, 30, 31, 40, 41, 42, 43,
      0,  1,  2,  3,  12, 13, 14, 15, 24, 25, 26, 27, 36, 37, 38, 39,
  };
  // clang-format on
  EXPECT_TRUE(ArrayEqual(out, check_d270));

  func = ReorientRow(Orientation(Angle::D0, Flip::Horizontal));
  dirty = func(in_row0, 0, out, outSize, outStride);
  EXPECT_EQ(dirty, IntRect(0, 0, 3, 1));
  dirty = func(in_row1, 1, out, outSize, outStride);
  EXPECT_EQ(dirty, IntRect(0, 1, 3, 1));
  dirty = func(in_row2, 2, out, outSize, outStride);
  EXPECT_EQ(dirty, IntRect(0, 2, 3, 1));
  dirty = func(in_row3, 3, out, outSize, outStride);
  EXPECT_EQ(dirty, IntRect(0, 3, 3, 1));

  // clang-format off
  const uint8_t check_d0_flip[3 * 4 * 4] = {
      8,  9,  10, 11, 4,  5,  6,  7,  0,  1,  2,  3,
      20, 21, 22, 23, 16, 17, 18, 19, 12, 13, 14, 15,
      32, 33, 34, 35, 28, 29, 30, 31, 24, 25, 26, 27,
      44, 45, 46, 47, 40, 41, 42, 43, 36, 37, 38, 39,
  };
  // clang-format on
  EXPECT_TRUE(ArrayEqual(out, check_d0_flip));

  func = ReorientRow(Orientation(Angle::D90, Flip::Horizontal));
  dirty = func(in_row0, 0, out, outSizeSwap, outStrideSwap);
  EXPECT_EQ(dirty, IntRect(0, 0, 1, 3));
  dirty = func(in_row1, 1, out, outSizeSwap, outStrideSwap);
  EXPECT_EQ(dirty, IntRect(1, 0, 1, 3));
  dirty = func(in_row2, 2, out, outSizeSwap, outStrideSwap);
  EXPECT_EQ(dirty, IntRect(2, 0, 1, 3));
  dirty = func(in_row3, 3, out, outSizeSwap, outStrideSwap);
  EXPECT_EQ(dirty, IntRect(3, 0, 1, 3));

  // clang-format off
  const uint8_t check_d90_flip[3 * 4 * 4] = {
      0,  1,  2,  3,  12, 13, 14, 15, 24, 25, 26, 27, 36, 37, 38, 39,
      4,  5,  6,  7,  16, 17, 18, 19, 28, 29, 30, 31, 40, 41, 42, 43,
      8,  9,  10, 11, 20, 21, 22, 23, 32, 33, 34, 35, 44, 45, 46, 47,
  };
  // clang-format on
  EXPECT_TRUE(ArrayEqual(out, check_d90_flip));

  func = ReorientRow(Orientation(Angle::D180, Flip::Horizontal));
  dirty = func(in_row0, 0, out, outSize, outStride);
  EXPECT_EQ(dirty, IntRect(0, 3, 3, 1));
  dirty = func(in_row1, 1, out, outSize, outStride);
  EXPECT_EQ(dirty, IntRect(0, 2, 3, 1));
  dirty = func(in_row2, 2, out, outSize, outStride);
  EXPECT_EQ(dirty, IntRect(0, 1, 3, 1));
  dirty = func(in_row3, 3, out, outSize, outStride);
  EXPECT_EQ(dirty, IntRect(0, 0, 3, 1));

  // clang-format off
  const uint8_t check_d180_flip[3 * 4 * 4] = {
      36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
      24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35,
      12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
      0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  10, 11,
  };
  // clang-format on
  EXPECT_TRUE(ArrayEqual(out, check_d180_flip));

  func = ReorientRow(Orientation(Angle::D270, Flip::Horizontal));
  dirty = func(in_row0, 0, out, outSizeSwap, outStrideSwap);
  EXPECT_EQ(dirty, IntRect(3, 0, 1, 3));
  dirty = func(in_row1, 1, out, outSizeSwap, outStrideSwap);
  EXPECT_EQ(dirty, IntRect(2, 0, 1, 3));
  dirty = func(in_row2, 2, out, outSizeSwap, outStrideSwap);
  EXPECT_EQ(dirty, IntRect(1, 0, 1, 3));
  dirty = func(in_row3, 3, out, outSizeSwap, outStrideSwap);
  EXPECT_EQ(dirty, IntRect(0, 0, 1, 3));

  // clang-format off
  const uint8_t check_d270_flip[3 * 4 * 4] = {
      44, 45, 46, 47, 32, 33, 34, 35, 20, 21, 22, 23, 8,  9,  10, 11,
      40, 41, 42, 43, 28, 29, 30, 31, 16, 17, 18, 19, 4,  5,  6,  7,
      36, 37, 38, 39, 24, 25, 26, 27, 12, 13, 14, 15, 0,  1,  2,  3,
  };
  // clang-format on
  EXPECT_TRUE(ArrayEqual(out, check_d270_flip));

  func = ReorientRow(
      Orientation(Angle::D0, Flip::Horizontal, /* aFlipFirst */ true));
  dirty = func(in_row0, 0, out, outSize, outStride);
  EXPECT_EQ(dirty, IntRect(0, 0, 3, 1));
  dirty = func(in_row1, 1, out, outSize, outStride);
  EXPECT_EQ(dirty, IntRect(0, 1, 3, 1));
  dirty = func(in_row2, 2, out, outSize, outStride);
  EXPECT_EQ(dirty, IntRect(0, 2, 3, 1));
  dirty = func(in_row3, 3, out, outSize, outStride);
  EXPECT_EQ(dirty, IntRect(0, 3, 3, 1));

  // No rotation, so flipping before and after are the same.
  EXPECT_TRUE(ArrayEqual(out, check_d0_flip));

  func = ReorientRow(
      Orientation(Angle::D90, Flip::Horizontal, /* aFlipFirst */ true));
  dirty = func(in_row0, 0, out, outSizeSwap, outStrideSwap);
  EXPECT_EQ(dirty, IntRect(3, 0, 1, 3));
  dirty = func(in_row1, 1, out, outSizeSwap, outStrideSwap);
  EXPECT_EQ(dirty, IntRect(2, 0, 1, 3));
  dirty = func(in_row2, 2, out, outSizeSwap, outStrideSwap);
  EXPECT_EQ(dirty, IntRect(1, 0, 1, 3));
  dirty = func(in_row3, 3, out, outSizeSwap, outStrideSwap);
  EXPECT_EQ(dirty, IntRect(0, 0, 1, 3));

  // Flip, rotate 90 degrees is the same as rotate 270 degrees, flip.
  EXPECT_TRUE(ArrayEqual(out, check_d270_flip));

  func = ReorientRow(
      Orientation(Angle::D180, Flip::Horizontal, /* aFlipFirst */ true));
  dirty = func(in_row0, 0, out, outSize, outStride);
  EXPECT_EQ(dirty, IntRect(0, 3, 3, 1));
  dirty = func(in_row1, 1, out, outSize, outStride);
  EXPECT_EQ(dirty, IntRect(0, 2, 3, 1));
  dirty = func(in_row2, 2, out, outSize, outStride);
  EXPECT_EQ(dirty, IntRect(0, 1, 3, 1));
  dirty = func(in_row3, 3, out, outSize, outStride);
  EXPECT_EQ(dirty, IntRect(0, 0, 3, 1));

  // Flip, rotate 180 degrees is the same as rotate 180 degrees, flip.
  EXPECT_TRUE(ArrayEqual(out, check_d180_flip));

  func = ReorientRow(
      Orientation(Angle::D270, Flip::Horizontal, /* aFlipFirst */ true));
  dirty = func(in_row0, 0, out, outSizeSwap, outStrideSwap);
  EXPECT_EQ(dirty, IntRect(0, 0, 1, 3));
  dirty = func(in_row1, 1, out, outSizeSwap, outStrideSwap);
  EXPECT_EQ(dirty, IntRect(1, 0, 1, 3));
  dirty = func(in_row2, 2, out, outSizeSwap, outStrideSwap);
  EXPECT_EQ(dirty, IntRect(2, 0, 1, 3));
  dirty = func(in_row3, 3, out, outSizeSwap, outStrideSwap);
  EXPECT_EQ(dirty, IntRect(3, 0, 1, 3));

  // Flip, rotate 270 degrees is the same as rotate 90 degrees, flip.
  EXPECT_TRUE(ArrayEqual(out, check_d90_flip));
}
