Autogenerated by:
cp dom/.clang-format image/ && mach format image/**.{cpp,h,mm}
No manual changes required.
Differential Revision: https://phabricator.services.mozilla.com/D311693
323 lines
10 KiB
C++
323 lines
10 KiB
C++
/* 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 <cmath>
|
|
|
|
#include "Common.h"
|
|
#include "Decoder.h"
|
|
#include "DecoderFactory.h"
|
|
#include "IDecodingTask.h"
|
|
#include "SourceBuffer.h"
|
|
#include "gtest/gtest.h"
|
|
#include "imgIEncoder.h"
|
|
#include "mozilla/gfx/2D.h"
|
|
#include "nsCOMPtr.h"
|
|
#include "nsComponentManagerUtils.h"
|
|
#include "nsStreamUtils.h"
|
|
#include "nsString.h"
|
|
|
|
using namespace mozilla;
|
|
using namespace mozilla::gfx;
|
|
using namespace mozilla::image;
|
|
|
|
static const int32_t kWidth = 5;
|
|
static const int32_t kHeight = 4;
|
|
|
|
static int32_t StrideForFormat(uint32_t aFormat) {
|
|
if (aFormat == imgIEncoder::INPUT_FORMAT_R10G10B10A2) {
|
|
return kWidth * 4;
|
|
}
|
|
return kWidth * 8;
|
|
}
|
|
|
|
// 5x4 test image with 10 distinct colors and mixed alpha:
|
|
// Row 0 (opaque): red, green, blue, orange, purple
|
|
// Row 1 (opaque): white, gray, cyan, yellow, lime
|
|
// Row 2 (alpha ~1/3): same as row 0
|
|
// Row 3 (alpha ~2/3): same as row 1
|
|
//
|
|
// RGB as fractions: red(1,0,0) green(0,1,0) blue(0,0,1) orange(1,.5,0)
|
|
// purple(.5,0,.5) white(1,1,1) gray(.5,.5,.5) cyan(0,1,1)
|
|
// yellow(1,1,0) lime(.5,1,0)
|
|
|
|
struct ColorRGB {
|
|
float r, g, b;
|
|
};
|
|
|
|
static const ColorRGB kRow0Colors[] = {
|
|
{1, 0, 0}, {0, 1, 0}, {0, 0, 1}, {1, 0.5f, 0}, {0.5f, 0, 0.5f},
|
|
};
|
|
static const ColorRGB kRow1Colors[] = {
|
|
{1, 1, 1}, {0.5f, 0.5f, 0.5f}, {0, 1, 1}, {1, 1, 0}, {0.5f, 1, 0},
|
|
};
|
|
|
|
static uint16_t Scale(float aFrac, uint16_t aMax) {
|
|
return static_cast<uint16_t>(std::lround(static_cast<double>(aFrac) * aMax));
|
|
}
|
|
|
|
static void FillU16Row(uint16_t* aDst, const ColorRGB* aColors, uint16_t aAlpha,
|
|
uint16_t aMax) {
|
|
for (int i = 0; i < kWidth; i++) {
|
|
aDst[i * 4 + 0] = Scale(aColors[i].r, aMax);
|
|
aDst[i * 4 + 1] = Scale(aColors[i].g, aMax);
|
|
aDst[i * 4 + 2] = Scale(aColors[i].b, aMax);
|
|
aDst[i * 4 + 3] = aAlpha;
|
|
}
|
|
}
|
|
|
|
static void FillU16TestPixels(uint16_t* p) {
|
|
FillU16Row(p + 0 * kWidth * 4, kRow0Colors, 65535, 65535);
|
|
FillU16Row(p + 1 * kWidth * 4, kRow1Colors, 65535, 65535);
|
|
FillU16Row(p + 2 * kWidth * 4, kRow0Colors, 21845, 65535); // ~1/3
|
|
FillU16Row(p + 3 * kWidth * 4, kRow1Colors, 43690, 65535); // ~2/3
|
|
}
|
|
|
|
static void FillU10TestPixels(uint16_t* p) {
|
|
FillU16Row(p + 0 * kWidth * 4, kRow0Colors, 1023, 1023);
|
|
FillU16Row(p + 1 * kWidth * 4, kRow1Colors, 1023, 1023);
|
|
FillU16Row(p + 2 * kWidth * 4, kRow0Colors, 341, 1023);
|
|
FillU16Row(p + 3 * kWidth * 4, kRow1Colors, 682, 1023);
|
|
}
|
|
|
|
static void FillU12TestPixels(uint16_t* p) {
|
|
FillU16Row(p + 0 * kWidth * 4, kRow0Colors, 4095, 4095);
|
|
FillU16Row(p + 1 * kWidth * 4, kRow1Colors, 4095, 4095);
|
|
FillU16Row(p + 2 * kWidth * 4, kRow0Colors, 1365, 4095);
|
|
FillU16Row(p + 3 * kWidth * 4, kRow1Colors, 2730, 4095);
|
|
}
|
|
|
|
static uint32_t PackR10G10B10A2(uint32_t r, uint32_t g, uint32_t b,
|
|
uint32_t a) {
|
|
return (b & 0x3FF) | ((g & 0x3FF) << 10) | ((r & 0x3FF) << 20) |
|
|
((a & 0x3) << 30);
|
|
}
|
|
|
|
static void FillR10G10B10A2Row(uint32_t* aDst, const ColorRGB* aColors,
|
|
uint32_t aAlpha) {
|
|
for (int i = 0; i < kWidth; i++) {
|
|
aDst[i] =
|
|
PackR10G10B10A2(Scale(aColors[i].r, 1023), Scale(aColors[i].g, 1023),
|
|
Scale(aColors[i].b, 1023), aAlpha);
|
|
}
|
|
}
|
|
|
|
static void FillR10G10B10A2TestPixels(uint32_t* p) {
|
|
FillR10G10B10A2Row(p + 0 * kWidth, kRow0Colors, 3);
|
|
FillR10G10B10A2Row(p + 1 * kWidth, kRow1Colors, 3);
|
|
FillR10G10B10A2Row(p + 2 * kWidth, kRow0Colors, 1);
|
|
FillR10G10B10A2Row(p + 3 * kWidth, kRow1Colors, 2);
|
|
}
|
|
|
|
// float16 constants
|
|
static const uint16_t kF16_0 = 0x0000;
|
|
static const uint16_t kF16_Half = 0x3800;
|
|
static const uint16_t kF16_1 = 0x3C00;
|
|
static const uint16_t kF16_Third = 0x3555; // ~1/3
|
|
static const uint16_t kF16_TwoThirds = 0x3955; // ~2/3
|
|
|
|
static uint16_t FracToF16(float aFrac) {
|
|
if (aFrac <= 0.0f) return kF16_0;
|
|
if (aFrac >= 1.0f) return kF16_1;
|
|
return kF16_Half; // only other color fraction we use is 0.5
|
|
}
|
|
|
|
static void FillF16Row(uint16_t* aDst, const ColorRGB* aColors,
|
|
uint16_t aAlphaF16) {
|
|
for (int i = 0; i < kWidth; i++) {
|
|
aDst[i * 4 + 0] = FracToF16(aColors[i].r);
|
|
aDst[i * 4 + 1] = FracToF16(aColors[i].g);
|
|
aDst[i * 4 + 2] = FracToF16(aColors[i].b);
|
|
aDst[i * 4 + 3] = aAlphaF16;
|
|
}
|
|
}
|
|
|
|
static void FillF16TestPixels(uint16_t* p) {
|
|
FillF16Row(p + 0 * kWidth * 4, kRow0Colors, kF16_1);
|
|
FillF16Row(p + 1 * kWidth * 4, kRow1Colors, kF16_1);
|
|
FillF16Row(p + 2 * kWidth * 4, kRow0Colors, kF16_Third);
|
|
FillF16Row(p + 3 * kWidth * 4, kRow1Colors, kF16_TwoThirds);
|
|
}
|
|
|
|
// Encode pixels using nsPNGEncoder, returning the encoded PNG as a byte buffer.
|
|
static nsTArray<uint8_t> EncodeHDRPNG(const uint8_t* aData, uint32_t aLength,
|
|
uint32_t aFormat) {
|
|
nsCOMPtr<imgIEncoder> encoder =
|
|
do_CreateInstance("@mozilla.org/image/encoder;2?type=image/png");
|
|
EXPECT_TRUE(encoder != nullptr);
|
|
|
|
nsresult rv = encoder->InitFromData(aData, aLength, kWidth, kHeight,
|
|
StrideForFormat(aFormat), aFormat, u""_ns,
|
|
VoidCString());
|
|
EXPECT_NS_SUCCEEDED(rv);
|
|
|
|
nsCOMPtr<nsIInputStream> stream(encoder);
|
|
EXPECT_TRUE(stream != nullptr);
|
|
|
|
uint64_t available;
|
|
rv = stream->Available(&available);
|
|
EXPECT_NS_SUCCEEDED(rv);
|
|
|
|
nsTArray<uint8_t> result;
|
|
result.SetLength(available);
|
|
uint32_t bytesRead;
|
|
rv = stream->Read(reinterpret_cast<char*>(result.Elements()), available,
|
|
&bytesRead);
|
|
EXPECT_NS_SUCCEEDED(rv);
|
|
result.SetLength(bytesRead);
|
|
return result;
|
|
}
|
|
|
|
// Decode a PNG byte buffer and return the decoded SourceSurface.
|
|
static RefPtr<SourceSurface> DecodePNG(const nsTArray<uint8_t>& aPNGData) {
|
|
auto sourceBuffer = MakeNotNull<RefPtr<SourceBuffer>>();
|
|
sourceBuffer->ExpectLength(aPNGData.Length());
|
|
nsresult rv = sourceBuffer->Append(
|
|
reinterpret_cast<const char*>(aPNGData.Elements()), aPNGData.Length());
|
|
EXPECT_NS_SUCCEEDED(rv);
|
|
sourceBuffer->Complete(NS_OK);
|
|
|
|
DecoderType decoderType = DecoderFactory::GetDecoderType("image/png");
|
|
RefPtr<Decoder> decoder = DecoderFactory::CreateAnonymousDecoder(
|
|
decoderType, sourceBuffer, Nothing(), DecoderFlags::FIRST_FRAME_ONLY,
|
|
DefaultSurfaceFlags());
|
|
EXPECT_TRUE(decoder != nullptr);
|
|
|
|
auto task = MakeRefPtr<AnonymousDecodingTask>(WrapNotNull(decoder),
|
|
/* aResumable */ false);
|
|
task->Run();
|
|
|
|
EXPECT_TRUE(decoder->GetDecodeDone());
|
|
EXPECT_FALSE(decoder->HasError());
|
|
|
|
OrientedIntSize size = decoder->Size();
|
|
EXPECT_EQ(kWidth, size.width);
|
|
EXPECT_EQ(kHeight, size.height);
|
|
|
|
RawAccessFrameRef currentFrame = decoder->GetCurrentFrameRef();
|
|
RefPtr<SourceSurface> surface = currentFrame->GetSourceSurface();
|
|
EXPECT_TRUE(surface != nullptr);
|
|
|
|
return surface;
|
|
}
|
|
|
|
// Build expected 8-bit BGRAColor row from color fractions and alpha.
|
|
// The decoded surface is premultiplied, so we premultiply expected values.
|
|
static std::vector<BGRAColor> MakeExpectedRow(const ColorRGB* aColors,
|
|
uint8_t aAlpha) {
|
|
std::vector<BGRAColor> row;
|
|
for (int i = 0; i < kWidth; i++) {
|
|
uint8_t r = static_cast<uint8_t>(std::lround(aColors[i].r * 255));
|
|
uint8_t g = static_cast<uint8_t>(std::lround(aColors[i].g * 255));
|
|
uint8_t b = static_cast<uint8_t>(std::lround(aColors[i].b * 255));
|
|
row.push_back(BGRAColor(b, g, r, aAlpha).Premultiply());
|
|
}
|
|
return row;
|
|
}
|
|
|
|
static void VerifyPixels(SourceSurface* aSurface) {
|
|
const ColorRGB* rowColors[] = {kRow0Colors, kRow1Colors, kRow0Colors,
|
|
kRow1Colors};
|
|
const uint8_t rowAlphas[] = {255, 255, 85, 170};
|
|
|
|
for (int row = 0; row < kHeight; row++) {
|
|
auto expected = MakeExpectedRow(rowColors[row], rowAlphas[row]);
|
|
for (int col = 0; col < kWidth; col++) {
|
|
// Allow fuzz for rounding differences in premultiplication and
|
|
// bit-depth scaling.
|
|
EXPECT_TRUE(RectIsSolidColor(aSurface, IntRect(col, row, 1, 1),
|
|
expected[col], /* aFuzz = */ 1));
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST(ImageHDRPNGEncoder, R10G10B10A2RoundTrip)
|
|
{
|
|
AutoInitializeImageLib initLib;
|
|
|
|
uint32_t pixels[kWidth * kHeight];
|
|
FillR10G10B10A2TestPixels(pixels);
|
|
|
|
nsTArray<uint8_t> pngData =
|
|
EncodeHDRPNG(reinterpret_cast<const uint8_t*>(pixels), sizeof(pixels),
|
|
imgIEncoder::INPUT_FORMAT_R10G10B10A2);
|
|
ASSERT_GT(pngData.Length(), 0u);
|
|
|
|
RefPtr<SourceSurface> surface = DecodePNG(pngData);
|
|
ASSERT_TRUE(surface != nullptr);
|
|
|
|
VerifyPixels(surface);
|
|
}
|
|
|
|
TEST(ImageHDRPNGEncoder, U10RoundTrip)
|
|
{
|
|
AutoInitializeImageLib initLib;
|
|
|
|
uint16_t pixels[kWidth * kHeight * 4];
|
|
FillU10TestPixels(pixels);
|
|
|
|
nsTArray<uint8_t> pngData =
|
|
EncodeHDRPNG(reinterpret_cast<const uint8_t*>(pixels), sizeof(pixels),
|
|
imgIEncoder::INPUT_FORMAT_RGBA_U10);
|
|
ASSERT_GT(pngData.Length(), 0u);
|
|
|
|
RefPtr<SourceSurface> surface = DecodePNG(pngData);
|
|
ASSERT_TRUE(surface != nullptr);
|
|
|
|
VerifyPixels(surface);
|
|
}
|
|
|
|
TEST(ImageHDRPNGEncoder, U12RoundTrip)
|
|
{
|
|
AutoInitializeImageLib initLib;
|
|
|
|
uint16_t pixels[kWidth * kHeight * 4];
|
|
FillU12TestPixels(pixels);
|
|
|
|
nsTArray<uint8_t> pngData =
|
|
EncodeHDRPNG(reinterpret_cast<const uint8_t*>(pixels), sizeof(pixels),
|
|
imgIEncoder::INPUT_FORMAT_RGBA_U12);
|
|
ASSERT_GT(pngData.Length(), 0u);
|
|
|
|
RefPtr<SourceSurface> surface = DecodePNG(pngData);
|
|
ASSERT_TRUE(surface != nullptr);
|
|
|
|
VerifyPixels(surface);
|
|
}
|
|
|
|
TEST(ImageHDRPNGEncoder, U16RoundTrip)
|
|
{
|
|
AutoInitializeImageLib initLib;
|
|
|
|
uint16_t pixels[kWidth * kHeight * 4];
|
|
FillU16TestPixels(pixels);
|
|
|
|
nsTArray<uint8_t> pngData =
|
|
EncodeHDRPNG(reinterpret_cast<const uint8_t*>(pixels), sizeof(pixels),
|
|
imgIEncoder::INPUT_FORMAT_RGBA_U16);
|
|
ASSERT_GT(pngData.Length(), 0u);
|
|
|
|
RefPtr<SourceSurface> surface = DecodePNG(pngData);
|
|
ASSERT_TRUE(surface != nullptr);
|
|
|
|
VerifyPixels(surface);
|
|
}
|
|
|
|
TEST(ImageHDRPNGEncoder, F16RoundTrip)
|
|
{
|
|
AutoInitializeImageLib initLib;
|
|
|
|
uint16_t pixels[kWidth * kHeight * 4];
|
|
FillF16TestPixels(pixels);
|
|
|
|
nsTArray<uint8_t> pngData =
|
|
EncodeHDRPNG(reinterpret_cast<const uint8_t*>(pixels), sizeof(pixels),
|
|
imgIEncoder::INPUT_FORMAT_RGBA_F16);
|
|
ASSERT_GT(pngData.Length(), 0u);
|
|
|
|
RefPtr<SourceSurface> surface = DecodePNG(pngData);
|
|
ASSERT_TRUE(surface != nullptr);
|
|
|
|
VerifyPixels(surface);
|
|
}
|