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