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/ports/SkImageGeneratorNDK.h" 11#include "tests/Test.h" 12#include "tools/Resources.h" 13#include "tools/ToolUtils.h" 14 15#include <vector> 16 17static std::unique_ptr<SkImageGenerator> make_generator(const char* path, skiatest::Reporter* r) { 18 auto data = GetResourceAsData(path); 19 if (data) { 20 auto gen = SkImageGeneratorNDK::MakeFromEncodedNDK(std::move(data)); 21 if (gen) { 22 return gen; 23 } 24 ERRORF(r, "Failed to create NDK generator from %s\n", path); 25 } else { 26 // Silently fail so developers can skip using --resources 27 } 28 return nullptr; 29} 30 31DEF_TEST(NdkDecode, r) { 32 static const struct { 33 const char* fPath; 34 SkISize fSize; 35 } recs[] = { 36 {"images/CMYK.jpg", {642, 516}}, 37 {"images/arrow.png", {187, 312}}, 38 {"images/baby_tux.webp", {386, 395}}, 39 {"images/color_wheel.gif", {128, 128}}, 40 {"images/rle.bmp", {320, 240}}, 41 {"images/color_wheel.ico", {128, 128}}, 42 {"images/google_chrome.ico", {256, 256}}, 43 {"images/mandrill.wbmp", {512, 512}}, 44 }; 45 for (auto& rec : recs) { 46 auto gen = make_generator(rec.fPath, r); 47 if (!gen) continue; 48 49 const auto& info = gen->getInfo(); 50 REPORTER_ASSERT(r, info.dimensions() == rec.fSize); 51 52 SkBitmap bm; 53 bm.allocPixels(info); 54 REPORTER_ASSERT(r, gen->getPixels(bm.pixmap())); 55 56 REPORTER_ASSERT(r, info.alphaType() != kUnpremul_SkAlphaType); 57 auto unpremulInfo = info.makeAlphaType(kUnpremul_SkAlphaType); 58 bm.allocPixels(unpremulInfo); 59 REPORTER_ASSERT(r, gen->getPixels(bm.pixmap())); 60 } 61} 62 63DEF_TEST(NdkDecode_nullData, r) { 64 auto gen = SkImageGeneratorNDK::MakeFromEncodedNDK(nullptr); 65 REPORTER_ASSERT(r, !gen); 66} 67 68static constexpr skcms_TransferFunction k2Dot6 = {2.6f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}; 69 70static constexpr skcms_Matrix3x3 kDCIP3 = {{ 71 {0.486143, 0.323835, 0.154234}, 72 {0.226676, 0.710327, 0.0629966}, 73 {0.000800549, 0.0432385, 0.78275}, 74}}; 75 76DEF_TEST(NdkDecode_reportedColorSpace, r) { 77 for (sk_sp<SkColorSpace> cs : { 78 sk_sp<SkColorSpace>(nullptr), 79 SkColorSpace::MakeSRGB(), 80 SkColorSpace::MakeSRGBLinear(), 81 SkColorSpace::MakeRGB(SkNamedTransferFn::kRec2020, SkNamedGamut::kSRGB), 82 SkColorSpace::MakeRGB(SkNamedTransferFn::kRec2020, SkNamedGamut::kRec2020), 83 SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, SkNamedGamut::kDisplayP3), 84 SkColorSpace::MakeRGB(SkNamedTransferFn::k2Dot2, SkNamedGamut::kAdobeRGB), 85 SkColorSpace::MakeRGB(k2Dot6, kDCIP3), 86 }) { 87 SkBitmap bm; 88 bm.allocPixels(SkImageInfo::Make(10, 10, kRGBA_F16_SkColorType, kOpaque_SkAlphaType, cs)); 89 bm.eraseColor(SK_ColorBLUE); 90 91 for (auto format : { SkEncodedImageFormat::kPNG, 92 SkEncodedImageFormat::kJPEG, 93 SkEncodedImageFormat::kWEBP }) { 94 auto data = SkEncodeBitmap(bm, format, 80); 95 auto gen = SkImageGeneratorNDK::MakeFromEncodedNDK(std::move(data)); 96 if (!gen) { 97 ERRORF(r, "Failed to encode!"); 98 return; 99 } 100 101 if (!cs) cs = SkColorSpace::MakeSRGB(); 102 REPORTER_ASSERT(r, SkColorSpace::Equals(gen->getInfo().colorSpace(), cs.get())); 103 } 104 } 105} 106 107DEF_TEST(NdkDecode_ColorSpace, r) { 108 for (const char* path: { 109 "images/CMYK.jpg", 110 "images/arrow.png", 111 "images/baby_tux.webp", 112 "images/color_wheel.gif", 113 "images/rle.bmp", 114 "images/color_wheel.ico", 115 "images/google_chrome.ico", 116 "images/mandrill.wbmp", 117 }) { 118 auto gen = make_generator(path, r); 119 if (!gen) continue; 120 121 for (sk_sp<SkColorSpace> cs : { 122 sk_sp<SkColorSpace>(nullptr), 123 SkColorSpace::MakeSRGB(), 124 SkColorSpace::MakeSRGBLinear(), 125 SkColorSpace::MakeRGB(SkNamedTransferFn::kRec2020, SkNamedGamut::kSRGB), 126 SkColorSpace::MakeRGB(SkNamedTransferFn::kRec2020, SkNamedGamut::kRec2020), 127 SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, SkNamedGamut::kDisplayP3), 128 SkColorSpace::MakeRGB(SkNamedTransferFn::k2Dot2, SkNamedGamut::kAdobeRGB), 129 SkColorSpace::MakeRGB(k2Dot6, kDCIP3), 130 }) { 131 auto info = gen->getInfo().makeColorSpace(cs); 132 133 SkBitmap bm; 134 bm.allocPixels(info); 135 REPORTER_ASSERT(r, gen->getPixels(bm.pixmap())); 136 } 137 138 std::vector<sk_sp<SkColorSpace>> unsupportedCs; 139 for (auto gamut : { SkNamedGamut::kSRGB, SkNamedGamut::kAdobeRGB, SkNamedGamut::kDisplayP3, 140 SkNamedGamut::kRec2020, SkNamedGamut::kXYZ }) { 141 unsupportedCs.push_back(SkColorSpace::MakeRGB(SkNamedTransferFn::kPQ, gamut)); 142 unsupportedCs.push_back(SkColorSpace::MakeRGB(SkNamedTransferFn::kHLG, gamut)); 143 unsupportedCs.push_back(SkColorSpace::MakeRGB(k2Dot6, gamut)); 144 } 145 146 for (auto gamut : { SkNamedGamut::kSRGB, SkNamedGamut::kDisplayP3, 147 SkNamedGamut::kRec2020, SkNamedGamut::kXYZ }) { 148 unsupportedCs.push_back(SkColorSpace::MakeRGB(SkNamedTransferFn::k2Dot2, gamut)); 149 } 150 151 for (auto gamut : { SkNamedGamut::kAdobeRGB, SkNamedGamut::kDisplayP3, 152 SkNamedGamut::kXYZ }) { 153 unsupportedCs.push_back(SkColorSpace::MakeRGB(SkNamedTransferFn::kRec2020, gamut)); 154 } 155 156 for (auto gamut : { SkNamedGamut::kAdobeRGB, SkNamedGamut::kDisplayP3, 157 SkNamedGamut::kRec2020, SkNamedGamut::kXYZ }) { 158 unsupportedCs.push_back(SkColorSpace::MakeRGB(SkNamedTransferFn::kLinear, gamut)); 159 } 160 161 for (auto gamut : { SkNamedGamut::kAdobeRGB, 162 SkNamedGamut::kRec2020, SkNamedGamut::kXYZ }) { 163 unsupportedCs.push_back(SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, gamut)); 164 } 165 166 for (auto fn : { SkNamedTransferFn::kSRGB, SkNamedTransferFn::k2Dot2, 167 SkNamedTransferFn::kLinear, SkNamedTransferFn::kRec2020 }) { 168 unsupportedCs.push_back(SkColorSpace::MakeRGB(fn, kDCIP3)); 169 } 170 171 for (auto unsupported : unsupportedCs) { 172 auto info = gen->getInfo().makeColorSpace(unsupported); 173 174 SkBitmap bm; 175 bm.allocPixels(info); 176 REPORTER_ASSERT(r, !gen->getPixels(bm.pixmap())); 177 } 178 } 179} 180 181DEF_TEST(NdkDecode_reuseNoColorSpace, r) { 182 static const struct { 183 const char* fPath; 184 sk_sp<SkColorSpace> fCorrectedColorSpace; 185 bool fIsOpaque; 186 } recs[] = { 187 // AImageDecoder defaults to ADATASPACE_UNKNOWN for this image. 188 {"images/wide_gamut_yellow_224_224_64.jpeg", SkColorSpace::MakeSRGB(), true}, 189 // This image is SRGB, so convert to a different color space. 190 {"images/example_1.png", SkColorSpace::MakeRGB(SkNamedTransferFn::k2Dot2, 191 SkNamedGamut::kAdobeRGB), false}, 192 }; 193 for (auto& rec : recs) { 194 auto gen = make_generator(rec.fPath, r); 195 if (!gen) continue; 196 197 REPORTER_ASSERT(r, gen->getInfo().colorSpace()->isSRGB()); 198 REPORTER_ASSERT(r, gen->getInfo().isOpaque() == rec.fIsOpaque); 199 200 auto noColorCorrection = gen->getInfo().makeColorSpace(nullptr); 201 if (rec.fIsOpaque) { 202 // Use something other than the default color type to verify that the modified color 203 // type is used even when the color space is reset. 204 noColorCorrection = noColorCorrection.makeColorType(kRGB_565_SkColorType); 205 } 206 207 SkBitmap orig; 208 orig.allocPixels(noColorCorrection); 209 REPORTER_ASSERT(r, gen->getPixels(orig.pixmap())); 210 211 SkBitmap corrected; 212 corrected.allocPixels(noColorCorrection.makeColorSpace(rec.fCorrectedColorSpace)); 213 REPORTER_ASSERT(r, gen->getPixels(corrected.pixmap())); 214 215 REPORTER_ASSERT(r, !ToolUtils::equal_pixels(orig, corrected)); 216 217 SkBitmap reuse; 218 reuse.allocPixels(noColorCorrection); 219 REPORTER_ASSERT(r, gen->getPixels(reuse.pixmap())); 220 221 REPORTER_ASSERT(r, ToolUtils::equal_pixels(orig, reuse)); 222 } 223} 224 225// The NDK supports scaling up to arbitrary dimensions. Skia forces clients to do this in a 226// separate step, so the client is in charge of how to do the upscale. 227DEF_TEST(NdkDecode_noUpscale, r) { 228 for (const char* path: { 229 "images/CMYK.jpg", 230 "images/arrow.png", 231 "images/baby_tux.webp", 232 "images/color_wheel.gif", 233 "images/rle.bmp", 234 "images/color_wheel.ico", 235 "images/google_chrome.ico", 236 "images/mandrill.wbmp", 237 }) { 238 auto gen = make_generator(path, r); 239 if (!gen) continue; 240 241 const auto actualDimensions = gen->getInfo().dimensions(); 242 const int width = actualDimensions.width(); 243 const int height = actualDimensions.height(); 244 for (SkISize dims : { 245 SkISize{width*2, height*2}, 246 SkISize{width + 1, height + 1}, 247 }) { 248 auto info = gen->getInfo().makeDimensions(dims); 249 SkBitmap bm; 250 bm.allocPixels(info); 251 REPORTER_ASSERT(r, !gen->getPixels(bm.pixmap())); 252 } 253 } 254} 255 256// libwebp supports downscaling to an arbitrary scale factor, and this is supported by the NDK. 257DEF_TEST(NdkDecode_webpArbitraryDownscale, r) { 258 for (const char* path: { 259 "images/baby_tux.webp", 260 "images/yellow_rose.webp", 261 "images/webp-color-profile-lossless.webp", 262 }) { 263 auto gen = make_generator(path, r); 264 if (!gen) continue; 265 266 const auto actualDimensions = gen->getInfo().dimensions(); 267 const int width = actualDimensions.width(); 268 const int height = actualDimensions.height(); 269 for (SkISize dims : { 270 SkISize{width/2, height/2}, 271 SkISize{width/4, height/4}, 272 SkISize{width/7, height/7}, 273 SkISize{width - 1, height - 1}, 274 SkISize{1, 1}, 275 SkISize{5, 20} 276 }) { 277 auto info = gen->getInfo().makeDimensions(dims); 278 SkBitmap bm; 279 bm.allocPixels(info); 280 REPORTER_ASSERT(r, gen->getPixels(bm.pixmap())); 281 282 REPORTER_ASSERT(r, info.alphaType() != kUnpremul_SkAlphaType); 283 auto unpremulInfo = info.makeAlphaType(kUnpremul_SkAlphaType); 284 bm.allocPixels(unpremulInfo); 285 REPORTER_ASSERT(r, gen->getPixels(bm.pixmap())); 286 } 287 } 288} 289 290// libjpeg-turbo supports downscaling to some scale factors. 291DEF_TEST(NdkDecode_jpegDownscale, r) { 292 static const struct { 293 const char* fPath; 294 SkISize fSupportedSizes[4]; 295 } recs[] = { 296 {"images/CMYK.jpg", {{642,516},{321,258},{161,129},{81,65}}}, 297 {"images/dog.jpg", {{180,180},{90,90},{45,45},{23,23}}}, 298 {"images/grayscale.jpg", {{128,128},{64,64},{32,32},{16,16}}}, 299 {"images/brickwork-texture.jpg", {{512,512},{256,256},{128,128},{64,64}}}, 300 {"images/mandrill_h2v1.jpg", {{512,512},{256,256},{128,128},{64,64}}}, 301 {"images/ducky.jpg", {{489,537},{245,269},{123,135},{62,68}}}, 302 }; 303 for (auto& rec : recs) { 304 auto gen = make_generator(rec.fPath, r); 305 if (!gen) continue; 306 307 for (SkISize dims : rec.fSupportedSizes) { 308 auto info = gen->getInfo().makeDimensions(dims); 309 SkBitmap bm; 310 bm.allocPixels(info); 311 if (!gen->getPixels(bm.pixmap())) { 312 ERRORF(r, "failed to decode %s to {%i,%i}\n", rec.fPath, dims.width(), 313 dims.height()); 314 } 315 316 REPORTER_ASSERT(r, info.alphaType() != kUnpremul_SkAlphaType); 317 auto unpremulInfo = info.makeAlphaType(kUnpremul_SkAlphaType); 318 bm.allocPixels(unpremulInfo); 319 REPORTER_ASSERT(r, gen->getPixels(bm.pixmap())); 320 } 321 } 322} 323 324DEF_TEST(NdkDecode_reuseJpeg, r) { 325 auto gen = make_generator("images/CMYK.jpg", r); 326 if (!gen) return; 327 328 SkImageInfo info = gen->getInfo(); 329 SkBitmap orig; 330 orig.allocPixels(info); 331 REPORTER_ASSERT(r, gen->getPixels(orig.pixmap())); 332 333 info = info.makeWH(321, 258); 334 SkBitmap downscaled; 335 downscaled.allocPixels(info); 336 REPORTER_ASSERT(r, gen->getPixels(downscaled.pixmap())); 337 338 SkBitmap reuse; 339 reuse.allocPixels(gen->getInfo()); 340 REPORTER_ASSERT(r, gen->getPixels(reuse.pixmap())); 341 342 REPORTER_ASSERT(r, ToolUtils::equal_pixels(orig, reuse)); 343} 344 345// The NDK supports scaling down to arbitrary dimensions. Skia forces clients to do this in a 346// separate step, so the client is in charge of how to do the downscale. 347DEF_TEST(NdkDecode_noDownscale, r) { 348 for (const char* path: { 349 "images/arrow.png", 350 "images/color_wheel.gif", 351 "images/rle.bmp", 352 "images/color_wheel.ico", 353 "images/google_chrome.ico", 354 "images/mandrill.wbmp", 355 }) { 356 auto gen = make_generator(path, r); 357 if (!gen) continue; 358 359 const auto actualDimensions = gen->getInfo().dimensions(); 360 const int width = actualDimensions.width(); 361 const int height = actualDimensions.height(); 362 for (SkISize dims : { 363 SkISize{width/2, height/2}, 364 SkISize{width/3, height/3}, 365 SkISize{width/4, height/4}, 366 SkISize{width/8, height/8}, 367 SkISize{width - 1, height - 1}, 368 }) { 369 auto info = gen->getInfo().makeDimensions(dims); 370 SkBitmap bm; 371 bm.allocPixels(info); 372 REPORTER_ASSERT(r, !gen->getPixels(bm.pixmap())); 373 } 374 } 375} 376 377DEF_TEST(NdkDecode_Gray8, r) { 378 static const struct { 379 const char* fPath; 380 bool fGrayscale; 381 } recs[] = { 382 {"images/CMYK.jpg", false}, 383 {"images/arrow.png", false}, 384 {"images/baby_tux.webp", false}, 385 {"images/color_wheel.gif", false}, 386 {"images/rle.bmp", false}, 387 {"images/color_wheel.ico", false}, 388 {"images/google_chrome.ico", false}, 389 {"images/mandrill.wbmp", true}, 390 {"images/grayscale.jpg", true}, 391 {"images/grayscale.png", true}, 392 }; 393 for (auto& rec : recs) { 394 auto gen = make_generator(rec.fPath, r); 395 if (!gen) continue; 396 397 SkImageInfo info = gen->getInfo(); 398 if (rec.fGrayscale) { 399 REPORTER_ASSERT(r, info.colorType() == kGray_8_SkColorType); 400 REPORTER_ASSERT(r, info.alphaType() == kOpaque_SkAlphaType); 401 } else { 402 info = info.makeColorType(kGray_8_SkColorType); 403 } 404 SkBitmap bm; 405 bm.allocPixels(info); 406 bool success = gen->getPixels(bm.pixmap()); 407 if (success != rec.fGrayscale) { 408 ERRORF(r, "Expected decoding %s to Gray8 to %s. Actual: %s\n", rec.fPath, 409 (rec.fGrayscale ? "succeed" : "fail"), (success ? "succeed" : "fail")); 410 } 411 } 412} 413 414DEF_TEST(NdkDecode_Opaque_and_565, r) { 415 for (const char* path: { 416 "images/CMYK.jpg", 417 "images/dog.jpg", 418 "images/ducky.jpg", 419 "images/arrow.png", 420 "images/example_1.png", 421 "images/explosion_sprites.png", 422 "images/lut_identity.png", 423 "images/grayscale.png", 424 "images/baby_tux.webp", 425 "images/yellow_rose.webp", 426 "images/webp-color-profile-lossless.webp", 427 "images/colorTables.gif", 428 "images/color_wheel.gif", 429 "images/flightAnim.gif", 430 "images/randPixels.gif", 431 "images/rle.bmp", 432 "images/color_wheel.ico", 433 "images/google_chrome.ico", 434 "images/mandrill.wbmp", 435 }) { 436 auto gen = make_generator(path, r); 437 if (!gen) continue; 438 439 auto info = gen->getInfo().makeAlphaType(kOpaque_SkAlphaType); 440 SkBitmap bm; 441 bm.allocPixels(info); 442 bool success = gen->getPixels(bm.pixmap()); 443 REPORTER_ASSERT(r, success == gen->getInfo().isOpaque()); 444 445 info = info.makeColorType(kRGB_565_SkColorType); 446 bm.allocPixels(info); 447 success = gen->getPixels(bm.pixmap()); 448 REPORTER_ASSERT(r, success == gen->getInfo().isOpaque()); 449 } 450} 451 452DEF_TEST(NdkDecode_AlwaysSupportedColorTypes, r) { 453 for (const char* path: { 454 "images/CMYK.jpg", 455 "images/dog.jpg", 456 "images/ducky.jpg", 457 "images/arrow.png", 458 "images/example_1.png", 459 "images/explosion_sprites.png", 460 "images/lut_identity.png", 461 "images/grayscale.png", 462 "images/baby_tux.webp", 463 "images/yellow_rose.webp", 464 "images/webp-color-profile-lossless.webp", 465 "images/colorTables.gif", 466 "images/color_wheel.gif", 467 "images/flightAnim.gif", 468 "images/randPixels.gif", 469 "images/rle.bmp", 470 "images/color_wheel.ico", 471 "images/google_chrome.ico", 472 "images/mandrill.wbmp", 473 }) { 474 auto gen = make_generator(path, r); 475 if (!gen) continue; 476 477 auto info = gen->getInfo().makeColorType(kRGBA_F16_SkColorType); 478 SkBitmap bm; 479 bm.allocPixels(info); 480 REPORTER_ASSERT(r, gen->getPixels(bm.pixmap())); 481 482 // This also tests that we can reuse the same generator for a different 483 // color type. 484 info = info.makeColorType(kRGBA_8888_SkColorType); 485 bm.allocPixels(info); 486 REPORTER_ASSERT(r, gen->getPixels(bm.pixmap())); 487 } 488} 489 490DEF_TEST(NdkDecode_UnsupportedColorTypes, r) { 491 for (const char* path: { 492 "images/CMYK.jpg", 493 "images/dog.jpg", 494 "images/ducky.jpg", 495 "images/arrow.png", 496 "images/example_1.png", 497 "images/explosion_sprites.png", 498 "images/lut_identity.png", 499 "images/grayscale.png", 500 "images/baby_tux.webp", 501 "images/yellow_rose.webp", 502 "images/webp-color-profile-lossless.webp", 503 "images/colorTables.gif", 504 "images/color_wheel.gif", 505 "images/flightAnim.gif", 506 "images/randPixels.gif", 507 "images/rle.bmp", 508 "images/color_wheel.ico", 509 "images/google_chrome.ico", 510 "images/mandrill.wbmp", 511 }) { 512 auto gen = make_generator(path, r); 513 if (!gen) continue; 514 515 for (SkColorType ct : { 516 kUnknown_SkColorType, 517 kAlpha_8_SkColorType, 518 kARGB_4444_SkColorType, 519 kRGB_888x_SkColorType, 520 kBGRA_8888_SkColorType, 521 kRGBA_1010102_SkColorType, 522 kBGRA_1010102_SkColorType, 523 kRGB_101010x_SkColorType, 524 kBGR_101010x_SkColorType, 525 kRGBA_F16Norm_SkColorType, 526 kRGBA_F32_SkColorType, 527 kR8G8_unorm_SkColorType, 528 kA16_float_SkColorType, 529 kR16G16_float_SkColorType, 530 kA16_unorm_SkColorType, 531 kR16G16_unorm_SkColorType, 532 kR16G16B16A16_unorm_SkColorType, 533 }) { 534 auto info = gen->getInfo().makeColorType(ct); 535 SkBitmap bm; 536 bm.allocPixels(info); 537 if (gen->getPixels(bm.pixmap())) { 538 ERRORF(r, "Expected decoding %s to %i to fail!", path, ct); 539 } 540 } 541 } 542} 543#endif // SK_ENABLE_NDK_IMAGES 544