blob: d0985dd914abdb332fc1605870e8dd511d79ccbd [file]
/*
* Copyright 2026 Google LLC
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "include/core/SkTypes.h"
#if defined(SK_CODEC_DECODES_JPEGXL)
#include "include/codec/SkAndroidCodec.h"
#include "include/codec/SkCodec.h"
#include "include/codec/SkEncodedImageFormat.h"
#include "include/codec/SkJpegxlDecoder.h"
#include "include/core/SkAlphaType.h"
#include "include/core/SkBitmap.h"
#include "include/core/SkColor.h"
#include "include/core/SkColorSpace.h"
#include "include/core/SkColorType.h"
#include "include/core/SkData.h"
#include "include/core/SkImageInfo.h"
#include "tests/Test.h"
#include "tools/Resources.h"
#include <cstdint>
DEF_TEST(Jpegxl_DecodeP3, r) {
const char* path = "images/Webkit-logo-P3.jxl";
auto data = GetResourceAsData(path);
if (!data) {
ERRORF(r, "Failed to find %s", path);
return;
}
REPORTER_ASSERT(r, SkJpegxlDecoder::IsJpegxl(data->data(), data->size()));
SkCodec::Result result;
auto codec = SkJpegxlDecoder::Decode(data, &result);
if (!codec) {
ERRORF(r, "Could not create codec from %s - error %s", path, SkCodec::ResultToString(result));
return;
}
REPORTER_ASSERT(r, result == SkCodec::kSuccess);
REPORTER_ASSERT(r, codec->getEncodedFormat() == SkEncodedImageFormat::kJPEGXL);
REPORTER_ASSERT(r, codec->dimensions().width() == 1000);
REPORTER_ASSERT(r, codec->dimensions().height() == 1000);
// Verify the image resolves to the Display P3 color space.
SkColorSpace* cs = codec->getInfo().colorSpace();
REPORTER_ASSERT(r, cs);
if (cs) {
sk_sp<SkColorSpace> p3 =
SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, SkNamedGamut::kDisplayP3);
REPORTER_ASSERT(r, SkColorSpace::Equals(cs, p3.get()));
}
// Verify decoding pixels succeeds.
SkImageInfo decodeInfo =
codec->getInfo().makeColorType(kRGBA_8888_SkColorType).makeAlphaType(kPremul_SkAlphaType);
SkBitmap bm;
bm.allocPixels(decodeInfo);
result = codec->getPixels(decodeInfo, bm.getPixels(), bm.rowBytes());
REPORTER_ASSERT(r, result == SkCodec::kSuccess);
// Spot-check the two shades of red in the image:
// 1. Background red (R=255, G=0, B=0, A=255)
// 2. WebKit logo red (R=241, G=0, B=0, A=255)
constexpr SkColor kBgRed = SkColorSetARGB(0xFF, 0xFF, 0x00, 0x00);
constexpr SkColor kLogoRed = SkColorSetARGB(0xFF, 0xF1, 0x00, 0x00);
REPORTER_ASSERT(r, bm.getColor(0, 0) == kBgRed);
REPORTER_ASSERT(r, bm.getColor(100, 100) == kBgRed);
REPORTER_ASSERT(r, bm.getColor(900, 900) == kBgRed);
REPORTER_ASSERT(r, bm.getColor(500, 200) == kBgRed);
REPORTER_ASSERT(r, bm.getColor(500, 800) == kBgRed);
REPORTER_ASSERT(r, bm.getColor(500, 500) == kLogoRed);
REPORTER_ASSERT(r, bm.getColor(500, 400) == kLogoRed);
REPORTER_ASSERT(r, bm.getColor(500, 600) == kLogoRed);
}
DEF_TEST(Jpegxl_Decode1010102, r) {
// 64x64 10-bit sRGB JXL (solid green: R=117, G=251, B=76 in 8-bit sRGB scale).
static constexpr uint8_t kRgb10BitSrgbJxl[] = {
0xff, 0x0a, 0x4f, 0x50, 0x24, 0x08, 0x04, 0x01, 0x00, 0x44, 0x00, 0x4b, 0x18, 0x8b,
0x15, 0x80, 0xdd, 0xfe, 0xff, 0x00, 0xe2, 0x3f, 0xf7, 0x05, 0x80, 0x21, 0x9d, 0xff,
};
auto data = SkData::MakeWithoutCopy(kRgb10BitSrgbJxl, sizeof(kRgb10BitSrgbJxl));
auto androidCodec = SkAndroidCodec::MakeFromData(data);
REPORTER_ASSERT(r, androidCodec);
if (!androidCodec) {
return;
}
REPORTER_ASSERT(
r, androidCodec->computeOutputColorType(kN32_SkColorType) == kRGBA_1010102_SkColorType);
REPORTER_ASSERT(r,
androidCodec->computeOutputColorType(kRGBA_1010102_SkColorType) ==
kRGBA_1010102_SkColorType);
SkImageInfo decodeInfo = androidCodec->getInfo()
.makeColorType(kRGBA_1010102_SkColorType)
.makeAlphaType(kOpaque_SkAlphaType);
SkBitmap bm;
bm.allocPixels(decodeInfo);
SkCodec::Result result = androidCodec->getAndroidPixels(decodeInfo, bm.getPixels(), bm.rowBytes());
REPORTER_ASSERT(r, result == SkCodec::kSuccess);
}
DEF_TEST(Jpegxl_AlphaPremulAndUnpremul, r) {
// 64x64 8-bit sRGB RGBA JXL with unpremultiplied pixel (R=117, G=251, B=76, A=127).
static constexpr uint8_t kRgba8BitSrgbUnpremulJxl[] = {
0xff, 0x0a, 0x4f, 0xc0, 0x4a, 0x08, 0x10, 0x10, 0x00, 0x8c, 0x00, 0x4b,
0x18, 0x8b, 0x15, 0x00, 0xd4, 0x88, 0x31, 0xc6, 0x8d, 0xbb, 0x7b, 0x00,
0x07, 0x80, 0x44, 0x01, 0xc0, 0x67, 0x4d, 0xfd, 0xff, 0x6f, 0x13, 0x17,
0x14, 0xdd, 0xdc, 0xc1, 0xca, 0x1e, 0xbf, 0x27, 0xf9, 0x01,
};
// 64x64 8-bit sRGB RGBA JXL with associated (premultiplied) pixel (R=58, G=125, B=38, A=127).
static constexpr uint8_t kRgba8BitSrgbPremulJxl[] = {
0xff, 0x0a, 0x4f, 0xc0, 0x00, 0x28, 0x01, 0x08, 0x10, 0x10, 0x00, 0x84,
0x00, 0x4b, 0x18, 0x8b, 0x15, 0x00, 0x94, 0x88, 0x31, 0x6e, 0xdc, 0xdd,
0x03, 0x38, 0x80, 0x53, 0x0a, 0x00, 0xc5, 0xd5, 0x74, 0x5b, 0x85, 0x06,
0xff, 0xff, 0xdc, 0xda, 0xa1, 0x50, 0xd2, 0xf0, 0x65, 0x7e,
};
for (sk_sp<SkData> data : {
SkData::MakeWithoutCopy(kRgba8BitSrgbUnpremulJxl, sizeof(kRgba8BitSrgbUnpremulJxl)),
SkData::MakeWithoutCopy(kRgba8BitSrgbPremulJxl, sizeof(kRgba8BitSrgbPremulJxl)),
}) {
SkCodec::Result result;
auto codec = SkJpegxlDecoder::Decode(data, &result);
REPORTER_ASSERT(r, codec && result == SkCodec::kSuccess);
if (!codec) {
continue;
}
// Requesting kOpaque_SkAlphaType on a non-opaque image must fail with kInvalidConversion.
{
SkImageInfo opaqueInfo = codec->getInfo()
.makeColorType(kRGBA_8888_SkColorType)
.makeAlphaType(kOpaque_SkAlphaType);
SkBitmap bm;
bm.allocPixels(opaqueInfo);
REPORTER_ASSERT(r,
codec->getPixels(opaqueInfo, bm.getPixels(), bm.rowBytes()) ==
SkCodec::kInvalidConversion);
}
// RGBA_8888 + kPremul_SkAlphaType (no colorXform): expect premultiplied (58, 125, 38, 127).
{
SkImageInfo info = codec->getInfo()
.makeColorType(kRGBA_8888_SkColorType)
.makeAlphaType(kPremul_SkAlphaType);
SkBitmap bm;
bm.allocPixels(info);
REPORTER_ASSERT(r,
codec->getPixels(info, bm.getPixels(), bm.rowBytes()) ==
SkCodec::kSuccess);
const uint8_t* px = static_cast<const uint8_t*>(bm.getAddr(0, 0));
REPORTER_ASSERT(r, px[0] == 58 && px[1] == 125 && px[2] == 38 && px[3] == 127);
}
// RGBA_8888 + kUnpremul_SkAlphaType (no colorXform): expect unpremultiplied (117, 251, 76, 127).
{
SkImageInfo info = codec->getInfo()
.makeColorType(kRGBA_8888_SkColorType)
.makeAlphaType(kUnpremul_SkAlphaType);
SkBitmap bm;
bm.allocPixels(info);
REPORTER_ASSERT(r,
codec->getPixels(info, bm.getPixels(), bm.rowBytes()) ==
SkCodec::kSuccess);
const uint8_t* px = static_cast<const uint8_t*>(bm.getAddr(0, 0));
REPORTER_ASSERT(r, px[0] == 117 && px[1] == 251 && px[2] == 76 && px[3] == 127);
}
// BGRA_8888 + kPremul_SkAlphaType (no colorXform): expect premultiplied BGRA (38, 125, 58, 127).
{
SkImageInfo info = codec->getInfo()
.makeColorType(kBGRA_8888_SkColorType)
.makeAlphaType(kPremul_SkAlphaType);
SkBitmap bm;
bm.allocPixels(info);
REPORTER_ASSERT(r,
codec->getPixels(info, bm.getPixels(), bm.rowBytes()) ==
SkCodec::kSuccess);
const uint8_t* px = static_cast<const uint8_t*>(bm.getAddr(0, 0));
REPORTER_ASSERT(r, px[0] == 38 && px[1] == 125 && px[2] == 58 && px[3] == 127);
}
// BGRA_8888 + kUnpremul_SkAlphaType (no colorXform): expect unpremultiplied BGRA (76, 251, 117, 127).
{
SkImageInfo info = codec->getInfo()
.makeColorType(kBGRA_8888_SkColorType)
.makeAlphaType(kUnpremul_SkAlphaType);
SkBitmap bm;
bm.allocPixels(info);
REPORTER_ASSERT(r,
codec->getPixels(info, bm.getPixels(), bm.rowBytes()) ==
SkCodec::kSuccess);
const uint8_t* px = static_cast<const uint8_t*>(bm.getAddr(0, 0));
REPORTER_ASSERT(r, px[0] == 76 && px[1] == 251 && px[2] == 117 && px[3] == 127);
}
}
}
#endif // defined(SK_CODEC_DECODES_JPEGXL)