Leon Scroggins III | 326b989 | 2020-08-05 16:51:10 -0400 | [diff] [blame] | 1 | /* |
| 2 | * Copyright 2020 Google LLC |
| 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/core/SkTypes.h" |
| 9 | #ifdef SK_ENABLE_NDK_IMAGES |
| 10 | #include "include/core/SkColor.h" |
| 11 | #include "include/core/SkImageEncoder.h" |
| 12 | #include "include/core/SkImageGenerator.h" |
| 13 | #include "include/private/SkMalloc.h" |
| 14 | #include "src/images/SkImageEncoderPriv.h" |
| 15 | #include "tests/Test.h" |
| 16 | #include "tools/Resources.h" |
| 17 | #include "tools/ToolUtils.h" |
| 18 | |
| 19 | #include <stdint.h> |
| 20 | #include <vector> |
| 21 | |
| 22 | static const char* kPng = "png"; |
| 23 | static const char* kJpeg = "jpeg"; |
| 24 | static const char* kWebpLossless = "webp_lossless"; |
| 25 | static const char* kWebpLossy = "webp_lossy"; |
| 26 | |
| 27 | namespace { |
| 28 | static const struct { |
| 29 | SkEncodedImageFormat format; |
| 30 | int quality; |
| 31 | const char* name; |
| 32 | } gRecs[] = { |
| 33 | { SkEncodedImageFormat::kPNG, 100, kPng}, |
| 34 | { SkEncodedImageFormat::kJPEG, 100, kJpeg}, |
| 35 | { SkEncodedImageFormat::kWEBP, 100, kWebpLossless}, |
| 36 | { SkEncodedImageFormat::kWEBP, 80, kWebpLossy}, |
| 37 | }; |
| 38 | } |
| 39 | |
| 40 | static sk_sp<SkData> encode_ndk(const SkPixmap& pmap, SkEncodedImageFormat format, int quality) { |
| 41 | SkDynamicMemoryWStream stream; |
| 42 | return SkEncodeImageWithNDK(&stream, pmap, format, quality) ? stream.detachAsData() : nullptr; |
| 43 | } |
| 44 | |
| 45 | DEF_TEST(NdkEncode, r) { |
| 46 | for (auto ct : { kRGBA_8888_SkColorType, |
| 47 | kRGB_565_SkColorType, |
| 48 | kRGBA_F16_SkColorType }) { |
| 49 | SkBitmap bm; |
| 50 | bm.allocPixels(SkImageInfo::Make(10, 10, ct, kOpaque_SkAlphaType)); |
| 51 | bm.eraseColor(SK_ColorBLUE); |
| 52 | for (const auto& rec : gRecs) { |
| 53 | auto encoded = encode_ndk(bm.pixmap(), rec.format, rec.quality); |
| 54 | if (!encoded) { |
| 55 | ERRORF(r, "Failed to encode %s to %s\n", ToolUtils::colortype_name(ct), rec.name); |
| 56 | continue; |
| 57 | } |
| 58 | auto gen = SkImageGenerator::MakeFromEncoded(std::move(encoded)); |
| 59 | if (!gen) { |
| 60 | ERRORF(r, "Failed to decode from %s as %s\n", ToolUtils::colortype_name(ct), |
| 61 | rec.name); |
| 62 | continue; |
| 63 | } |
| 64 | |
| 65 | if (rec.name == kPng && bm.colorType() == kRGB_565_SkColorType) { |
| 66 | REPORTER_ASSERT(r, gen->getInfo().colorType() == kRGB_565_SkColorType); |
| 67 | } else { |
| 68 | REPORTER_ASSERT(r, gen->getInfo().colorType() == kN32_SkColorType); |
| 69 | } |
| 70 | |
| 71 | SkBitmap bm2; |
| 72 | bm2.allocPixels(bm.info()); |
| 73 | REPORTER_ASSERT(r, gen->getPixels(bm2.pixmap())); |
| 74 | |
| 75 | for (int x = 0; x < bm.width(); x++) |
| 76 | for (int y = 0; y < bm.height(); y++) { |
| 77 | SkColor orig = bm .getColor(x, y); |
| 78 | SkColor actual = bm2.getColor(x, y); |
| 79 | |
| 80 | REPORTER_ASSERT(r, SkColorGetA(orig) == SkColorGetA(actual)); |
| 81 | REPORTER_ASSERT(r, SkColorGetA(orig) == 0xFF); |
| 82 | |
| 83 | if (rec.name == kPng || rec.name == kWebpLossless) { |
| 84 | REPORTER_ASSERT(r, orig == actual); |
| 85 | } else { |
| 86 | int diffR = std::abs((int) SkColorGetR(orig) - (int) SkColorGetR(actual)); |
| 87 | int diffG = std::abs((int) SkColorGetG(orig) - (int) SkColorGetG(actual)); |
| 88 | int diffB = std::abs((int) SkColorGetB(orig) - (int) SkColorGetB(actual)); |
| 89 | REPORTER_ASSERT(r, diffR <= 2 && diffG <= 1 && diffB <= 1); |
| 90 | } |
| 91 | } |
| 92 | } |
| 93 | } |
| 94 | } |
| 95 | |
| 96 | DEF_TEST(NdkEncode_unsupportedFormats, r) { |
| 97 | for (auto ct : { kRGBA_8888_SkColorType, |
| 98 | kRGB_565_SkColorType, |
| 99 | kRGBA_F16_SkColorType }) { |
| 100 | SkBitmap bm; |
| 101 | bm.allocPixels(SkImageInfo::Make(10, 10, ct, kOpaque_SkAlphaType)); |
| 102 | bm.eraseColor(SK_ColorBLUE); |
| 103 | for (auto format : { SkEncodedImageFormat::kBMP, |
| 104 | SkEncodedImageFormat::kGIF, |
| 105 | SkEncodedImageFormat::kICO, |
| 106 | SkEncodedImageFormat::kWBMP, |
| 107 | SkEncodedImageFormat::kPKM, |
| 108 | SkEncodedImageFormat::kKTX, |
| 109 | SkEncodedImageFormat::kASTC, |
| 110 | SkEncodedImageFormat::kDNG, |
| 111 | SkEncodedImageFormat::kHEIF }) { |
| 112 | REPORTER_ASSERT(r, !encode_ndk(bm.pixmap(), format, 100)); |
| 113 | } |
| 114 | } |
| 115 | } |
| 116 | |
| 117 | DEF_TEST(NdkEncode_badQuality, r) { |
| 118 | for (auto ct : { kRGBA_8888_SkColorType, |
| 119 | kRGB_565_SkColorType, |
| 120 | kRGBA_F16_SkColorType }) { |
| 121 | SkBitmap bm; |
| 122 | bm.allocPixels(SkImageInfo::Make(10, 10, ct, kOpaque_SkAlphaType)); |
| 123 | bm.eraseColor(SK_ColorBLUE); |
| 124 | for (auto format : { SkEncodedImageFormat::kJPEG, |
| 125 | SkEncodedImageFormat::kPNG, |
| 126 | SkEncodedImageFormat::kWEBP }) { |
| 127 | for (int quality : {-1, -100, 101, 200}) { |
| 128 | REPORTER_ASSERT(r, !encode_ndk(bm.pixmap(), format, quality)); |
| 129 | } |
| 130 | } |
| 131 | } |
| 132 | } |
| 133 | |
| 134 | DEF_TEST(NdkEncode_nullPixels, r) { |
| 135 | for (auto info : { SkImageInfo::MakeUnknown(), |
| 136 | SkImageInfo::MakeN32Premul(10, 10), |
| 137 | SkImageInfo::MakeN32Premul(0, 0)}) { |
| 138 | SkPixmap pm(info, nullptr, info.minRowBytes()); |
| 139 | for (const auto& rec : gRecs) { |
| 140 | REPORTER_ASSERT(r, !encode_ndk(pm, rec.format, rec.quality)); |
| 141 | } |
| 142 | } |
| 143 | } |
| 144 | |
| 145 | DEF_TEST(NdkEncode_badInfo, r) { |
| 146 | // Allocate an arbitrary amount of memory. These infos don't have a natural |
| 147 | // amount to allocate, and the encoder shouldn't touch the memory anyway. |
| 148 | // But this allows us to verify that the bad info fails, even when the pixel |
| 149 | // pointer is not null. |
| 150 | void* pixels = sk_malloc_throw(1024); |
| 151 | std::vector<SkPixmap> pixmaps{ SkPixmap(SkImageInfo::MakeN32Premul(-10, 10), pixels, 1000), |
| 152 | SkPixmap(SkImageInfo::MakeN32Premul(10, -10), pixels, 200), |
| 153 | SkPixmap(SkImageInfo::MakeN32Premul(10, 10), pixels, 20), |
| 154 | SkPixmap(SkImageInfo::MakeN32Premul(10, 10), pixels, 41), |
| 155 | SkPixmap(SkImageInfo::MakeN32Premul(10, 10), pixels, 0), |
| 156 | SkPixmap(SkImageInfo::MakeN32Premul( 0, 0), pixels, 40)}; |
| 157 | if (sizeof(size_t) > sizeof(uint32_t)) { |
| 158 | pixmaps.emplace_back(SkImageInfo::MakeN32Premul(10, 10), pixels, |
| 159 | static_cast<size_t>(UINT32_MAX) + 1); |
| 160 | } |
| 161 | for (const auto& pm : pixmaps) { |
| 162 | for (const auto& rec : gRecs) { |
| 163 | REPORTER_ASSERT(r, !encode_ndk(pm, rec.format, rec.quality)); |
| 164 | } |
| 165 | } |
| 166 | free(pixels); |
| 167 | } |
| 168 | |
| 169 | DEF_TEST(NdkEncode_unsupportedColorTypes, r) { |
| 170 | for (SkColorType ct : { |
| 171 | kUnknown_SkColorType, |
| 172 | kAlpha_8_SkColorType, |
| 173 | kARGB_4444_SkColorType, |
| 174 | kRGB_888x_SkColorType, |
| 175 | kBGRA_8888_SkColorType, |
| 176 | kRGBA_1010102_SkColorType, |
| 177 | kBGRA_1010102_SkColorType, |
| 178 | kRGB_101010x_SkColorType, |
| 179 | kBGR_101010x_SkColorType, |
| 180 | kGray_8_SkColorType, |
| 181 | kRGBA_F16Norm_SkColorType, |
| 182 | kRGBA_F32_SkColorType, |
| 183 | kR8G8_unorm_SkColorType, |
| 184 | kA16_float_SkColorType, |
| 185 | kR16G16_float_SkColorType, |
| 186 | kA16_unorm_SkColorType, |
| 187 | kR16G16_unorm_SkColorType, |
| 188 | kR16G16B16A16_unorm_SkColorType, |
| 189 | }) { |
| 190 | auto info = SkImageInfo::Make(7, 13, ct, kOpaque_SkAlphaType, SkColorSpace::MakeSRGB()); |
| 191 | SkBitmap bm; |
| 192 | bm.allocPixels(info); |
| 193 | bm.eraseColor(SK_ColorGREEN); |
| 194 | for (const auto& rec : gRecs) { |
| 195 | REPORTER_ASSERT(r, !encode_ndk(bm.pixmap(), rec.format, rec.quality)); |
| 196 | } |
| 197 | if (!SkColorTypeIsAlwaysOpaque(ct)) { |
| 198 | for (auto at : { kPremul_SkAlphaType, kUnpremul_SkAlphaType}) { |
| 199 | info = info.makeAlphaType(at); |
| 200 | bm.allocPixels(info); |
| 201 | bm.eraseARGB(0x7F, 0xFF, 0xFF, 0xFF); |
| 202 | } |
| 203 | for (const auto& rec : gRecs) { |
| 204 | REPORTER_ASSERT(r, !encode_ndk(bm.pixmap(), rec.format, rec.quality)); |
| 205 | } |
| 206 | } |
| 207 | } |
| 208 | } |
| 209 | |
| 210 | DEF_TEST(NdkEncode_unsupportedAlphaTypes, r) { |
| 211 | for (auto ct : { kRGBA_8888_SkColorType, |
| 212 | kRGB_565_SkColorType, |
| 213 | kRGBA_F16_SkColorType }) { |
| 214 | for (auto at : { kUnknown_SkAlphaType, (SkAlphaType) -1}) { |
| 215 | auto info = SkImageInfo::Make(10, 10, ct, at); |
| 216 | size_t rowBytes = info.minRowBytes(); |
| 217 | void* pixels = sk_malloc_throw(info.computeByteSize(rowBytes)); |
| 218 | SkPixmap pm(info, pixels, rowBytes); |
| 219 | for (const auto& rec : gRecs) { |
| 220 | REPORTER_ASSERT(r, !encode_ndk(pm, rec.format, rec.quality)); |
| 221 | } |
| 222 | free(pixels); |
| 223 | } |
| 224 | } |
| 225 | } |
| 226 | |
| 227 | static constexpr skcms_TransferFunction k2Dot6 = {2.6f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}; |
| 228 | |
| 229 | static constexpr skcms_Matrix3x3 kDCIP3 = {{ |
| 230 | {0.486143, 0.323835, 0.154234}, |
| 231 | {0.226676, 0.710327, 0.0629966}, |
| 232 | {0.000800549, 0.0432385, 0.78275}, |
| 233 | }}; |
| 234 | |
| 235 | |
| 236 | static bool nearly_equal(float a, float b) { |
| 237 | return fabs(a - b) < .002f; |
| 238 | } |
| 239 | |
| 240 | static bool nearly_equal(const skcms_TransferFunction& x, const skcms_TransferFunction& y) { |
| 241 | return nearly_equal(x.g, y.g) |
| 242 | && nearly_equal(x.a, y.a) |
| 243 | && nearly_equal(x.b, y.b) |
| 244 | && nearly_equal(x.c, y.c) |
| 245 | && nearly_equal(x.d, y.d) |
| 246 | && nearly_equal(x.e, y.e) |
| 247 | && nearly_equal(x.f, y.f); |
| 248 | } |
| 249 | |
| 250 | static bool nearly_equal(const skcms_Matrix3x3& a, const skcms_Matrix3x3& b) { |
| 251 | for (int i = 0; i < 3; i++) |
| 252 | for (int j = 0; j < 3; j++) { |
| 253 | if (!nearly_equal(a.vals[i][j], b.vals[i][j])) return false; |
| 254 | } |
| 255 | return true; |
| 256 | } |
| 257 | |
| 258 | static bool nearly_equal(SkColorSpace* a, SkColorSpace* b) { |
| 259 | skcms_TransferFunction fnA, fnB; |
| 260 | skcms_Matrix3x3 gamutA, gamutB; |
| 261 | return a && b && a->isNumericalTransferFn(&fnA) && a->toXYZD50(&gamutA) |
| 262 | && b->isNumericalTransferFn(&fnB) && b->toXYZD50(&gamutB) |
| 263 | && nearly_equal(fnA, fnB) && nearly_equal(gamutA, gamutB); |
| 264 | } |
| 265 | |
| 266 | DEF_TEST(NdkEncode_ColorSpace, r) { |
| 267 | const struct { |
| 268 | sk_sp<SkColorSpace> cs; |
| 269 | const char* name; |
| 270 | } colorSpaces[] = { |
| 271 | { sk_sp<SkColorSpace>(nullptr), "null" }, |
| 272 | { SkColorSpace::MakeSRGB(), "srgb" }, |
| 273 | { SkColorSpace::MakeSRGBLinear(), "srgb-linear"}, |
| 274 | { SkColorSpace::MakeRGB(SkNamedTransferFn::kRec2020, SkNamedGamut::kSRGB), "bt709" }, |
| 275 | { SkColorSpace::MakeRGB(SkNamedTransferFn::kRec2020, SkNamedGamut::kRec2020), "rec2020" }, |
| 276 | { SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, SkNamedGamut::kDisplayP3), "p3" }, |
| 277 | { SkColorSpace::MakeRGB(SkNamedTransferFn::k2Dot2, SkNamedGamut::kAdobeRGB), "adobeRGB"}, |
| 278 | { SkColorSpace::MakeRGB(k2Dot6, kDCIP3), "dci-p3" }, |
| 279 | }; |
| 280 | for (const auto& colorSpace : colorSpaces) { |
| 281 | for (auto ct : { kRGBA_8888_SkColorType, kRGB_565_SkColorType, kRGBA_F16_SkColorType }) { |
| 282 | SkBitmap bm; |
| 283 | bm.allocPixels(SkImageInfo::Make(10, 10, ct, kOpaque_SkAlphaType, colorSpace.cs)); |
| 284 | bm.eraseColor(SK_ColorRED); |
| 285 | |
| 286 | for (const auto& rec : gRecs) { |
| 287 | auto encoded = encode_ndk(bm.pixmap(), rec.format, rec.quality); |
| 288 | REPORTER_ASSERT(r, encoded); |
| 289 | auto gen = SkImageGenerator::MakeFromEncoded(std::move(encoded)); |
| 290 | REPORTER_ASSERT(r, gen); |
| 291 | |
| 292 | auto expected = colorSpace.cs ? colorSpace.cs : SkColorSpace::MakeSRGB(); |
| 293 | auto* actual = gen->getInfo().colorSpace(); |
| 294 | if (!nearly_equal(actual, expected.get())) { |
| 295 | const char* name = "unknown"; |
| 296 | for (auto named : colorSpaces) { |
| 297 | if (nearly_equal(actual, named.cs.get())) { |
| 298 | name = named.name; |
| 299 | break; |
| 300 | } |
| 301 | } |
| 302 | |
| 303 | ERRORF(r, "Mismatch: expected: %s\tactual:%s", colorSpace.name, name); |
| 304 | } |
| 305 | } |
| 306 | } |
| 307 | } |
| 308 | } |
| 309 | |
| 310 | DEF_TEST(NdkEncode_unsupportedColorSpace, r) { |
| 311 | std::vector<sk_sp<SkColorSpace>> unsupportedCs; |
| 312 | for (auto gamut : { SkNamedGamut::kSRGB, SkNamedGamut::kAdobeRGB, SkNamedGamut::kDisplayP3, |
| 313 | SkNamedGamut::kRec2020, SkNamedGamut::kXYZ }) { |
| 314 | unsupportedCs.push_back(SkColorSpace::MakeRGB(SkNamedTransferFn::kPQ, gamut)); |
| 315 | unsupportedCs.push_back(SkColorSpace::MakeRGB(SkNamedTransferFn::kHLG, gamut)); |
| 316 | unsupportedCs.push_back(SkColorSpace::MakeRGB(k2Dot6, gamut)); |
| 317 | } |
| 318 | |
| 319 | for (auto gamut : { SkNamedGamut::kSRGB, SkNamedGamut::kDisplayP3, |
| 320 | SkNamedGamut::kRec2020, SkNamedGamut::kXYZ }) { |
| 321 | unsupportedCs.push_back(SkColorSpace::MakeRGB(SkNamedTransferFn::k2Dot2, gamut)); |
| 322 | } |
| 323 | |
| 324 | for (auto gamut : { SkNamedGamut::kAdobeRGB, SkNamedGamut::kDisplayP3, |
| 325 | SkNamedGamut::kXYZ }) { |
| 326 | unsupportedCs.push_back(SkColorSpace::MakeRGB(SkNamedTransferFn::kRec2020, gamut)); |
| 327 | } |
| 328 | |
| 329 | for (auto gamut : { SkNamedGamut::kAdobeRGB, SkNamedGamut::kDisplayP3, |
| 330 | SkNamedGamut::kRec2020, SkNamedGamut::kXYZ }) { |
| 331 | unsupportedCs.push_back(SkColorSpace::MakeRGB(SkNamedTransferFn::kLinear, gamut)); |
| 332 | } |
| 333 | |
| 334 | for (auto gamut : { SkNamedGamut::kAdobeRGB, |
| 335 | SkNamedGamut::kRec2020, SkNamedGamut::kXYZ }) { |
| 336 | unsupportedCs.push_back(SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, gamut)); |
| 337 | } |
| 338 | |
| 339 | for (auto fn : { SkNamedTransferFn::kSRGB, SkNamedTransferFn::k2Dot2, |
| 340 | SkNamedTransferFn::kLinear, SkNamedTransferFn::kRec2020 }) { |
| 341 | unsupportedCs.push_back(SkColorSpace::MakeRGB(fn, kDCIP3)); |
| 342 | } |
| 343 | |
| 344 | for (auto unsupported : unsupportedCs) { |
| 345 | for (auto ct : { kRGBA_8888_SkColorType, kRGB_565_SkColorType, kRGBA_F16_SkColorType }) { |
| 346 | SkBitmap bm; |
| 347 | bm.allocPixels(SkImageInfo::Make(10, 10, ct, kOpaque_SkAlphaType, unsupported)); |
| 348 | bm.eraseColor(SK_ColorBLUE); |
| 349 | |
| 350 | for (const auto& rec : gRecs) { |
| 351 | REPORTER_ASSERT(r, !encode_ndk(bm.pixmap(), rec.format, rec.quality)); |
| 352 | } |
| 353 | } |
| 354 | } |
| 355 | } |
| 356 | |
| 357 | #endif // SK_ENABLE_NDK_IMAGES |