/* * Copyright 2013 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "include/codec/SkCodec.h" #include "include/codec/SkEncodedOrigin.h" #include "include/core/SkColorType.h" #include "include/core/SkData.h" #include "include/core/SkImageInfo.h" #include "include/core/SkPixmap.h" #include "include/core/SkScalar.h" #include "include/core/SkSize.h" #include "include/core/SkStream.h" #include "include/core/SkTypes.h" #include "include/core/SkYUVAInfo.h" #include "include/core/SkYUVAPixmaps.h" #include "include/effects/SkColorMatrix.h" #include "include/encode/SkJpegEncoder.h" #include "include/private/base/SkTo.h" #include "tests/Test.h" #include "tools/Resources.h" #include #include #include #include static void codec_yuv(skiatest::Reporter* reporter, const char path[], const SkYUVAInfo* expectedInfo) { std::unique_ptr stream(GetResourceAsStream(path)); if (!stream) { return; } std::unique_ptr codec(SkCodec::MakeFromStream(std::move(stream))); REPORTER_ASSERT(reporter, codec); if (!codec) { return; } // Test queryYUBAInfo() SkYUVAPixmapInfo yuvaPixmapInfo; static constexpr auto kAllTypes = SkYUVAPixmapInfo::SupportedDataTypes::All(); static constexpr auto kNoTypes = SkYUVAPixmapInfo::SupportedDataTypes(); // SkYUVAInfo param is required to be non-null. bool success = codec->queryYUVAInfo(kAllTypes, nullptr); REPORTER_ASSERT(reporter, !success); // Fails when there is no support for YUVA planes. success = codec->queryYUVAInfo(kNoTypes, &yuvaPixmapInfo); REPORTER_ASSERT(reporter, !success); success = codec->queryYUVAInfo(kAllTypes, &yuvaPixmapInfo); REPORTER_ASSERT(reporter, SkToBool(expectedInfo) == success); if (!success) { return; } REPORTER_ASSERT(reporter, *expectedInfo == yuvaPixmapInfo.yuvaInfo()); int numPlanes = yuvaPixmapInfo.numPlanes(); REPORTER_ASSERT(reporter, numPlanes <= SkYUVAInfo::kMaxPlanes); for (int i = 0; i < numPlanes; ++i) { const SkImageInfo& planeInfo = yuvaPixmapInfo.planeInfo(i); SkColorType planeCT = planeInfo.colorType(); REPORTER_ASSERT(reporter, !planeInfo.isEmpty()); REPORTER_ASSERT(reporter, planeCT != kUnknown_SkColorType); REPORTER_ASSERT(reporter, planeInfo.validRowBytes(yuvaPixmapInfo.rowBytes(i))); // Currently all planes must share a data type, gettable as SkYUVAPixmapInfo::dataType(). auto [numChannels, planeDataType] = SkYUVAPixmapInfo::NumChannelsAndDataType(planeCT); REPORTER_ASSERT(reporter, planeDataType == yuvaPixmapInfo.dataType()); } for (int i = numPlanes; i < SkYUVAInfo::kMaxPlanes; ++i) { const SkImageInfo& planeInfo = yuvaPixmapInfo.planeInfo(i); REPORTER_ASSERT(reporter, planeInfo.dimensions().isEmpty()); REPORTER_ASSERT(reporter, planeInfo.colorType() == kUnknown_SkColorType); REPORTER_ASSERT(reporter, yuvaPixmapInfo.rowBytes(i) == 0); } // Allocate the memory for the YUV decode. auto pixmaps = SkYUVAPixmaps::Allocate(yuvaPixmapInfo); REPORTER_ASSERT(reporter, pixmaps.isValid()); for (int i = 0; i < SkYUVAPixmaps::kMaxPlanes; ++i) { REPORTER_ASSERT(reporter, pixmaps.plane(i).info() == yuvaPixmapInfo.planeInfo(i)); } for (int i = numPlanes; i < SkYUVAInfo::kMaxPlanes; ++i) { REPORTER_ASSERT(reporter, pixmaps.plane(i).rowBytes() == 0); } // Test getYUVAPlanes() REPORTER_ASSERT(reporter, SkCodec::kSuccess == codec->getYUVAPlanes(pixmaps)); } DEF_TEST(Jpeg_YUV_Codec, r) { auto setExpectations = [](SkISize dims, SkYUVAInfo::Subsampling subsampling) { return SkYUVAInfo(dims, SkYUVAInfo::PlaneConfig::kY_U_V, subsampling, kJPEG_Full_SkYUVColorSpace, kTopLeft_SkEncodedOrigin, SkYUVAInfo::Siting::kCentered, SkYUVAInfo::Siting::kCentered); }; SkYUVAInfo expectations = setExpectations({128, 128}, SkYUVAInfo::Subsampling::k420); codec_yuv(r, "images/color_wheel.jpg", &expectations); // H2V2 expectations = setExpectations({512, 512}, SkYUVAInfo::Subsampling::k420); codec_yuv(r, "images/mandrill_512_q075.jpg", &expectations); // H1V1 expectations = setExpectations({512, 512}, SkYUVAInfo::Subsampling::k444); codec_yuv(r, "images/mandrill_h1v1.jpg", &expectations); // H2V1 expectations = setExpectations({512, 512}, SkYUVAInfo::Subsampling::k422); codec_yuv(r, "images/mandrill_h2v1.jpg", &expectations); // Non-power of two dimensions expectations = setExpectations({439, 154}, SkYUVAInfo::Subsampling::k420); codec_yuv(r, "images/cropped_mandrill.jpg", &expectations); expectations = setExpectations({8, 8}, SkYUVAInfo::Subsampling::k420); codec_yuv(r, "images/randPixels.jpg", &expectations); // Progressive images expectations = setExpectations({512, 512}, SkYUVAInfo::Subsampling::k444); codec_yuv(r, "images/brickwork-texture.jpg", &expectations); codec_yuv(r, "images/brickwork_normal-map.jpg", &expectations); // A CMYK encoded image should fail. codec_yuv(r, "images/CMYK.jpg", nullptr); // A grayscale encoded image should fail. codec_yuv(r, "images/grayscale.jpg", nullptr); // A PNG should fail. codec_yuv(r, "images/arrow.png", nullptr); } SkYUVAPixmaps decode_yuva(skiatest::Reporter* r, std::unique_ptr stream) { static constexpr auto kAllTypes = SkYUVAPixmapInfo::SupportedDataTypes::All(); SkYUVAPixmaps result; std::unique_ptr codec(SkCodec::MakeFromStream(std::move(stream))); REPORTER_ASSERT(r, codec); SkYUVAPixmapInfo yuvaPixmapInfo; REPORTER_ASSERT(r, codec->queryYUVAInfo(kAllTypes, &yuvaPixmapInfo)); result = SkYUVAPixmaps::Allocate(yuvaPixmapInfo); REPORTER_ASSERT(r, result.isValid()); REPORTER_ASSERT(r, SkCodec::kSuccess == codec->getYUVAPlanes(result)); return result; } static void verify_same(skiatest::Reporter* r, const SkYUVAPixmaps a, const SkYUVAPixmaps& b) { REPORTER_ASSERT(r, a.yuvaInfo() == b.yuvaInfo()); REPORTER_ASSERT(r, a.numPlanes() == b.numPlanes()); for (int plane = 0; plane < a.numPlanes(); ++plane) { const SkPixmap& aPlane = a.plane(plane); const SkPixmap& bPlane = b.plane(plane); REPORTER_ASSERT(r, aPlane.computeByteSize() == bPlane.computeByteSize()); const uint8_t* aData = reinterpret_cast(aPlane.addr()); const uint8_t* bData = reinterpret_cast(bPlane.addr()); for (int row = 0; row < aPlane.height(); ++row) { for (int col = 0; col < aPlane.width() * aPlane.info().bytesPerPixel(); ++col) { int32_t aByte = aData[col]; int32_t bByte = bData[col]; // Allow at most one bit of difference. REPORTER_ASSERT(r, std::abs(aByte - bByte) <= 1); } aData += aPlane.rowBytes(); bData += bPlane.rowBytes(); } } } DEF_TEST(Jpeg_YUV_Encode, r) { const char* paths[] = { "images/color_wheel.jpg", "images/mandrill_512_q075.jpg", "images/mandrill_h1v1.jpg", "images/mandrill_h2v1.jpg", "images/cropped_mandrill.jpg", "images/randPixels.jpg", }; for (const auto* path : paths) { SkYUVAPixmaps decoded; { std::unique_ptr stream(GetResourceAsStream(path)); decoded = decode_yuva(r, std::move(stream)); } SkYUVAPixmaps roundtrip; { SkJpegEncoder::Options options; SkDynamicMemoryWStream encodeStream; REPORTER_ASSERT(r, SkJpegEncoder::Encode(&encodeStream, decoded, nullptr, options)); auto encodedData = encodeStream.detachAsData(); roundtrip = decode_yuva(r, SkMemoryStream::Make(encodedData)); } verify_same(r, decoded, roundtrip); } } // Be sure that the two matrices are inverses of each other // (i.e. rgb2yuv and yuv2rgb DEF_TEST(YUVMath, reporter) { const SkYUVColorSpace spaces[] = { kJPEG_SkYUVColorSpace, kRec601_SkYUVColorSpace, kRec709_SkYUVColorSpace, kBT2020_SkYUVColorSpace, kIdentity_SkYUVColorSpace, }; // Not sure what the theoretical precision we can hope for is, so pick a big value that // passes (when I think we're correct). const float tolerance = 1.0f/(1 << 18); for (auto cs : spaces) { SkColorMatrix r2ym = SkColorMatrix::RGBtoYUV(cs), y2rm = SkColorMatrix::YUVtoRGB(cs); r2ym.postConcat(y2rm); float tmp[20]; r2ym.getRowMajor(tmp); for (int i = 0; i < 20; ++i) { float expected = 0; if (i % 6 == 0) { // diagonal expected = 1; } REPORTER_ASSERT(reporter, SkScalarNearlyEqual(tmp[i], expected, tolerance)); } } }