xref: /aosp_15_r20/external/skia/tests/YUVTest.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1 /*
2  * Copyright 2013 Google Inc.
3  *
4  * Use of this source code is governed by a BSD-style license that can be
5  * found in the LICENSE file.
6  */
7 
8 #include "include/codec/SkCodec.h"
9 #include "include/codec/SkEncodedOrigin.h"
10 #include "include/core/SkColorType.h"
11 #include "include/core/SkData.h"
12 #include "include/core/SkImageInfo.h"
13 #include "include/core/SkPixmap.h"
14 #include "include/core/SkScalar.h"
15 #include "include/core/SkSize.h"
16 #include "include/core/SkStream.h"
17 #include "include/core/SkTypes.h"
18 #include "include/core/SkYUVAInfo.h"
19 #include "include/core/SkYUVAPixmaps.h"
20 #include "include/effects/SkColorMatrix.h"
21 #include "include/encode/SkJpegEncoder.h"
22 #include "include/private/base/SkTo.h"
23 #include "tests/Test.h"
24 #include "tools/Resources.h"
25 
26 #include <cmath>
27 #include <cstdint>
28 #include <memory>
29 #include <utility>
30 
codec_yuv(skiatest::Reporter * reporter,const char path[],const SkYUVAInfo * expectedInfo)31 static void codec_yuv(skiatest::Reporter* reporter,
32                       const char path[],
33                       const SkYUVAInfo* expectedInfo) {
34     std::unique_ptr<SkStream> stream(GetResourceAsStream(path));
35     if (!stream) {
36         return;
37     }
38     std::unique_ptr<SkCodec> codec(SkCodec::MakeFromStream(std::move(stream)));
39     REPORTER_ASSERT(reporter, codec);
40     if (!codec) {
41         return;
42     }
43 
44     // Test queryYUBAInfo()
45     SkYUVAPixmapInfo yuvaPixmapInfo;
46 
47     static constexpr auto kAllTypes = SkYUVAPixmapInfo::SupportedDataTypes::All();
48     static constexpr auto kNoTypes  = SkYUVAPixmapInfo::SupportedDataTypes();
49 
50     // SkYUVAInfo param is required to be non-null.
51     bool success = codec->queryYUVAInfo(kAllTypes, nullptr);
52     REPORTER_ASSERT(reporter, !success);
53     // Fails when there is no support for YUVA planes.
54     success = codec->queryYUVAInfo(kNoTypes, &yuvaPixmapInfo);
55     REPORTER_ASSERT(reporter, !success);
56 
57     success = codec->queryYUVAInfo(kAllTypes, &yuvaPixmapInfo);
58     REPORTER_ASSERT(reporter, SkToBool(expectedInfo) == success);
59     if (!success) {
60         return;
61     }
62     REPORTER_ASSERT(reporter, *expectedInfo == yuvaPixmapInfo.yuvaInfo());
63 
64     int numPlanes = yuvaPixmapInfo.numPlanes();
65     REPORTER_ASSERT(reporter, numPlanes <= SkYUVAInfo::kMaxPlanes);
66     for (int i = 0; i < numPlanes; ++i) {
67         const SkImageInfo& planeInfo = yuvaPixmapInfo.planeInfo(i);
68         SkColorType planeCT = planeInfo.colorType();
69         REPORTER_ASSERT(reporter, !planeInfo.isEmpty());
70         REPORTER_ASSERT(reporter, planeCT != kUnknown_SkColorType);
71         REPORTER_ASSERT(reporter, planeInfo.validRowBytes(yuvaPixmapInfo.rowBytes(i)));
72         // Currently all planes must share a data type, gettable as SkYUVAPixmapInfo::dataType().
73         auto [numChannels, planeDataType] = SkYUVAPixmapInfo::NumChannelsAndDataType(planeCT);
74         REPORTER_ASSERT(reporter, planeDataType == yuvaPixmapInfo.dataType());
75     }
76     for (int i = numPlanes; i < SkYUVAInfo::kMaxPlanes; ++i) {
77         const SkImageInfo& planeInfo = yuvaPixmapInfo.planeInfo(i);
78         REPORTER_ASSERT(reporter, planeInfo.dimensions().isEmpty());
79         REPORTER_ASSERT(reporter, planeInfo.colorType() == kUnknown_SkColorType);
80         REPORTER_ASSERT(reporter, yuvaPixmapInfo.rowBytes(i) == 0);
81     }
82 
83     // Allocate the memory for the YUV decode.
84     auto pixmaps = SkYUVAPixmaps::Allocate(yuvaPixmapInfo);
85     REPORTER_ASSERT(reporter, pixmaps.isValid());
86 
87     for (int i = 0; i < SkYUVAPixmaps::kMaxPlanes; ++i) {
88         REPORTER_ASSERT(reporter, pixmaps.plane(i).info() == yuvaPixmapInfo.planeInfo(i));
89     }
90     for (int i = numPlanes; i < SkYUVAInfo::kMaxPlanes; ++i) {
91         REPORTER_ASSERT(reporter, pixmaps.plane(i).rowBytes() == 0);
92     }
93 
94     // Test getYUVAPlanes()
95     REPORTER_ASSERT(reporter, SkCodec::kSuccess == codec->getYUVAPlanes(pixmaps));
96 }
97 
DEF_TEST(Jpeg_YUV_Codec,r)98 DEF_TEST(Jpeg_YUV_Codec, r) {
99     auto setExpectations = [](SkISize dims, SkYUVAInfo::Subsampling subsampling) {
100         return SkYUVAInfo(dims,
101                           SkYUVAInfo::PlaneConfig::kY_U_V,
102                           subsampling,
103                           kJPEG_Full_SkYUVColorSpace,
104                           kTopLeft_SkEncodedOrigin,
105                           SkYUVAInfo::Siting::kCentered,
106                           SkYUVAInfo::Siting::kCentered);
107     };
108 
109     SkYUVAInfo expectations = setExpectations({128, 128}, SkYUVAInfo::Subsampling::k420);
110     codec_yuv(r, "images/color_wheel.jpg", &expectations);
111 
112     // H2V2
113     expectations = setExpectations({512, 512}, SkYUVAInfo::Subsampling::k420);
114     codec_yuv(r, "images/mandrill_512_q075.jpg", &expectations);
115 
116     // H1V1
117     expectations = setExpectations({512, 512}, SkYUVAInfo::Subsampling::k444);
118     codec_yuv(r, "images/mandrill_h1v1.jpg", &expectations);
119 
120     // H2V1
121     expectations = setExpectations({512, 512}, SkYUVAInfo::Subsampling::k422);
122     codec_yuv(r, "images/mandrill_h2v1.jpg", &expectations);
123 
124     // Non-power of two dimensions
125     expectations = setExpectations({439, 154}, SkYUVAInfo::Subsampling::k420);
126     codec_yuv(r, "images/cropped_mandrill.jpg", &expectations);
127 
128     expectations = setExpectations({8, 8}, SkYUVAInfo::Subsampling::k420);
129     codec_yuv(r, "images/randPixels.jpg", &expectations);
130 
131     // Progressive images
132     expectations = setExpectations({512, 512}, SkYUVAInfo::Subsampling::k444);
133     codec_yuv(r, "images/brickwork-texture.jpg", &expectations);
134     codec_yuv(r, "images/brickwork_normal-map.jpg", &expectations);
135 
136     // A CMYK encoded image should fail.
137     codec_yuv(r, "images/CMYK.jpg", nullptr);
138     // A grayscale encoded image should fail.
139     codec_yuv(r, "images/grayscale.jpg", nullptr);
140     // A PNG should fail.
141     codec_yuv(r, "images/arrow.png", nullptr);
142 }
143 
decode_yuva(skiatest::Reporter * r,std::unique_ptr<SkStream> stream)144 SkYUVAPixmaps decode_yuva(skiatest::Reporter* r, std::unique_ptr<SkStream> stream) {
145     static constexpr auto kAllTypes = SkYUVAPixmapInfo::SupportedDataTypes::All();
146     SkYUVAPixmaps result;
147 
148     std::unique_ptr<SkCodec> codec(SkCodec::MakeFromStream(std::move(stream)));
149     REPORTER_ASSERT(r, codec);
150 
151     SkYUVAPixmapInfo yuvaPixmapInfo;
152     REPORTER_ASSERT(r, codec->queryYUVAInfo(kAllTypes, &yuvaPixmapInfo));
153     result = SkYUVAPixmaps::Allocate(yuvaPixmapInfo);
154     REPORTER_ASSERT(r, result.isValid());
155     REPORTER_ASSERT(r, SkCodec::kSuccess == codec->getYUVAPlanes(result));
156     return result;
157 }
158 
verify_same(skiatest::Reporter * r,const SkYUVAPixmaps a,const SkYUVAPixmaps & b)159 static void verify_same(skiatest::Reporter* r, const SkYUVAPixmaps a, const SkYUVAPixmaps& b) {
160     REPORTER_ASSERT(r, a.yuvaInfo() == b.yuvaInfo());
161     REPORTER_ASSERT(r, a.numPlanes() == b.numPlanes());
162     for (int plane = 0; plane < a.numPlanes(); ++plane) {
163         const SkPixmap& aPlane = a.plane(plane);
164         const SkPixmap& bPlane = b.plane(plane);
165         REPORTER_ASSERT(r, aPlane.computeByteSize() == bPlane.computeByteSize());
166         const uint8_t* aData = reinterpret_cast<const uint8_t*>(aPlane.addr());
167         const uint8_t* bData = reinterpret_cast<const uint8_t*>(bPlane.addr());
168         for (int row = 0; row < aPlane.height(); ++row) {
169             for (int col = 0; col < aPlane.width() * aPlane.info().bytesPerPixel(); ++col) {
170                 int32_t aByte = aData[col];
171                 int32_t bByte = bData[col];
172                 // Allow at most one bit of difference.
173                 REPORTER_ASSERT(r, std::abs(aByte - bByte) <= 1);
174             }
175             aData += aPlane.rowBytes();
176             bData += bPlane.rowBytes();
177         }
178     }
179 }
180 
DEF_TEST(Jpeg_YUV_Encode,r)181 DEF_TEST(Jpeg_YUV_Encode, r) {
182     const char* paths[] = {
183             "images/color_wheel.jpg",
184             "images/mandrill_512_q075.jpg",
185             "images/mandrill_h1v1.jpg",
186             "images/mandrill_h2v1.jpg",
187             "images/cropped_mandrill.jpg",
188             "images/randPixels.jpg",
189     };
190     for (const auto* path : paths) {
191         SkYUVAPixmaps decoded;
192         {
193             std::unique_ptr<SkStream> stream(GetResourceAsStream(path));
194             decoded = decode_yuva(r, std::move(stream));
195         }
196 
197         SkYUVAPixmaps roundtrip;
198         {
199             SkJpegEncoder::Options options;
200             SkDynamicMemoryWStream encodeStream;
201             REPORTER_ASSERT(r, SkJpegEncoder::Encode(&encodeStream, decoded, nullptr, options));
202             auto encodedData = encodeStream.detachAsData();
203             roundtrip = decode_yuva(r, SkMemoryStream::Make(encodedData));
204         }
205 
206         verify_same(r, decoded, roundtrip);
207     }
208 }
209 
210 // Be sure that the two matrices are inverses of each other
211 // (i.e. rgb2yuv and yuv2rgb
DEF_TEST(YUVMath,reporter)212 DEF_TEST(YUVMath, reporter) {
213     const SkYUVColorSpace spaces[] = {
214         kJPEG_SkYUVColorSpace,
215         kRec601_SkYUVColorSpace,
216         kRec709_SkYUVColorSpace,
217         kBT2020_SkYUVColorSpace,
218         kIdentity_SkYUVColorSpace,
219     };
220 
221     // Not sure what the theoretical precision we can hope for is, so pick a big value that
222     // passes (when I think we're correct).
223     const float tolerance = 1.0f/(1 << 18);
224 
225     for (auto cs : spaces) {
226         SkColorMatrix r2ym = SkColorMatrix::RGBtoYUV(cs),
227                       y2rm = SkColorMatrix::YUVtoRGB(cs);
228         r2ym.postConcat(y2rm);
229 
230         float tmp[20];
231         r2ym.getRowMajor(tmp);
232         for (int i = 0; i < 20; ++i) {
233             float expected = 0;
234             if (i % 6 == 0) {   // diagonal
235                 expected = 1;
236             }
237             REPORTER_ASSERT(reporter, SkScalarNearlyEqual(tmp[i], expected, tolerance));
238         }
239     }
240 }
241