1 /* 2 * Copyright 2018 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 "Resources.h" 9 #include "SkAndroidCodec.h" 10 #include "SkBitmap.h" 11 #include "SkCodec.h" 12 #include "SkCodecImageGenerator.h" 13 #include "SkColor.h" 14 #include "SkData.h" 15 #include "SkEncodedImageFormat.h" 16 #include "SkImageGenerator.h" 17 #include "SkImageInfo.h" 18 #include "SkPixmapPriv.h" 19 #include "SkRefCnt.h" 20 #include "SkSize.h" 21 #include "SkString.h" 22 #include "SkTypes.h" 23 #include "Test.h" 24 25 #include <algorithm> 26 #include <memory> 27 28 static SkISize times(const SkISize& size, float factor) { 29 return { (int) (size.width() * factor), (int) (size.height() * factor) }; 30 } 31 32 static SkISize plus(const SkISize& size, int term) { 33 return { size.width() + term, size.height() + term }; 34 } 35 36 static bool invalid(const SkISize& size) { 37 return size.width() < 1 || size.height() < 1; 38 } 39 40 DEF_TEST(AndroidCodec_computeSampleSize, r) { 41 if (GetResourcePath().isEmpty()) { 42 return; 43 } 44 for (const char* file : { "images/color_wheel.webp", 45 "images/ship.png", 46 "images/dog.jpg", 47 "images/color_wheel.gif", 48 "images/rle.bmp", 49 "images/google_chrome.ico", 50 "images/mandrill.wbmp", 51 #ifdef SK_CODEC_DECODES_RAW 52 "images/sample_1mp.dng", 53 #endif 54 }) { 55 auto data = GetResourceAsData(file); 56 if (!data) { 57 ERRORF(r, "Could not get %s", file); 58 continue; 59 } 60 61 auto codec = SkAndroidCodec::MakeFromCodec(SkCodec::MakeFromData(std::move(data))); 62 if (!codec) { 63 ERRORF(r, "Could not create codec for %s", file); 64 continue; 65 } 66 67 const auto dims = codec->getInfo().dimensions(); 68 const SkISize downscales[] = { 69 plus(dims, -1), 70 times(dims, .15f), 71 times(dims, .6f), 72 { (int32_t) (dims.width() * .25f), (int32_t) (dims.height() * .75f ) }, 73 { 1, 1 }, 74 { 1, 2 }, 75 { 2, 1 }, 76 { 0, -1 }, 77 { dims.width(), dims.height() - 1 }, 78 }; 79 for (SkISize size : downscales) { 80 const auto requested = size; 81 const int computedSampleSize = codec->computeSampleSize(&size); 82 REPORTER_ASSERT(r, size.width() >= 1 && size.height() >= 1); 83 if (codec->getEncodedFormat() == SkEncodedImageFormat::kWEBP) { 84 // WebP supports arbitrary down-scaling. 85 REPORTER_ASSERT(r, size == requested || invalid(requested)); 86 } else if (computedSampleSize == 1) { 87 REPORTER_ASSERT(r, size == dims); 88 } else { 89 REPORTER_ASSERT(r, computedSampleSize > 1); 90 if (size.width() >= dims.width() || size.height() >= dims.height()) { 91 ERRORF(r, "File %s's computed sample size (%i) is bigger than" 92 " original? original: %i x %i\tsampled: %i x %i", 93 file, computedSampleSize, dims.width(), dims.height(), 94 size.width(), size.height()); 95 } 96 REPORTER_ASSERT(r, size.width() >= requested.width() && 97 size.height() >= requested.height()); 98 REPORTER_ASSERT(r, size.width() < dims.width() && 99 size.height() < dims.height()); 100 } 101 } 102 103 const SkISize upscales[] = { 104 dims, plus(dims, 5), times(dims, 2), 105 }; 106 for (SkISize size : upscales) { 107 const int computedSampleSize = codec->computeSampleSize(&size); 108 REPORTER_ASSERT(r, computedSampleSize == 1); 109 REPORTER_ASSERT(r, dims == size); 110 } 111 112 // This mimics how Android's ImageDecoder uses SkAndroidCodec. A client 113 // can choose their dimensions based on calling getSampledDimensions, 114 // but the ImageDecoder API takes an arbitrary size. It then uses 115 // computeSampleSize to determine the best dimensions and sampleSize. 116 // It should return the same dimensions. the sampleSize may be different 117 // due to integer division. 118 for (int sampleSize : { 1, 2, 3, 4, 8, 16, 32 }) { 119 const SkISize sampledDims = codec->getSampledDimensions(sampleSize); 120 SkISize size = sampledDims; 121 const int computedSampleSize = codec->computeSampleSize(&size); 122 if (sampledDims != size) { 123 ERRORF(r, "File '%s'->getSampledDimensions(%i) yields computed" 124 " sample size of %i\n\tsampledDimensions: %i x %i\t" 125 "computed dimensions: %i x %i", 126 file, sampleSize, computedSampleSize, 127 sampledDims.width(), sampledDims.height(), 128 size.width(), size.height()); 129 } 130 } 131 } 132 } 133 134 DEF_TEST(AndroidCodec_wide, r) { 135 if (GetResourcePath().isEmpty()) { 136 return; 137 } 138 139 const char* path = "images/wide-gamut.png"; 140 auto data = GetResourceAsData(path); 141 if (!data) { 142 ERRORF(r, "Missing file %s", path); 143 return; 144 } 145 146 auto codec = SkAndroidCodec::MakeFromCodec(SkCodec::MakeFromData(std::move(data))); 147 if (!codec) { 148 ERRORF(r, "Failed to create codec from %s", path); 149 return; 150 } 151 152 auto info = codec->getInfo(); 153 auto cs = codec->computeOutputColorSpace(info.colorType(), nullptr); 154 if (!cs) { 155 ERRORF(r, "%s should have a color space", path); 156 return; 157 } 158 159 auto expected = SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, SkNamedGamut::kDCIP3); 160 REPORTER_ASSERT(r, SkColorSpace::Equals(cs.get(), expected.get())); 161 } 162 163 DEF_TEST(AndroidCodec_P3, r) { 164 if (GetResourcePath().isEmpty()) { 165 return; 166 } 167 168 const char* path = "images/purple-displayprofile.png"; 169 auto data = GetResourceAsData(path); 170 if (!data) { 171 ERRORF(r, "Missing file %s", path); 172 return; 173 } 174 175 auto codec = SkAndroidCodec::MakeFromCodec(SkCodec::MakeFromData(std::move(data))); 176 if (!codec) { 177 ERRORF(r, "Failed to create codec from %s", path); 178 return; 179 } 180 181 auto info = codec->getInfo(); 182 auto cs = codec->computeOutputColorSpace(info.colorType(), nullptr); 183 if (!cs) { 184 ERRORF(r, "%s should have a color space", path); 185 return; 186 } 187 188 REPORTER_ASSERT(r, !cs->isSRGB()); 189 REPORTER_ASSERT(r, cs->gammaCloseToSRGB()); 190 191 skcms_Matrix3x3 matrix; 192 cs->toXYZD50(&matrix); 193 194 static constexpr skcms_Matrix3x3 kExpected = {{ 195 { 0.426254272f, 0.369018555f, 0.168914795f }, 196 { 0.226013184f, 0.685974121f, 0.0880126953f }, 197 { 0.0116729736f, 0.0950927734f, 0.71812439f }, 198 }}; 199 REPORTER_ASSERT(r, 0 == memcmp(&matrix, &kExpected, sizeof(skcms_Matrix3x3))); 200 } 201 202 DEF_TEST(AndroidCodec_orientation, r) { 203 if (GetResourcePath().isEmpty()) { 204 return; 205 } 206 207 for (const char* ext : { "jpg", "webp" }) 208 for (char i = '1'; i <= '8'; ++i) { 209 SkString path = SkStringPrintf("images/orientation/%c.%s", i, ext); 210 auto data = GetResourceAsData(path.c_str()); 211 auto gen = SkCodecImageGenerator::MakeFromEncodedCodec(data); 212 if (!gen) { 213 ERRORF(r, "failed to decode %s", path.c_str()); 214 return; 215 } 216 217 // Dimensions after adjusting for the origin. 218 const SkISize expectedDims = { 100, 80 }; 219 220 // SkCodecImageGenerator automatically adjusts for the origin. 221 REPORTER_ASSERT(r, gen->getInfo().dimensions() == expectedDims); 222 223 auto androidCodec = SkAndroidCodec::MakeFromCodec(SkCodec::MakeFromData(data)); 224 if (!androidCodec) { 225 ERRORF(r, "failed to decode %s", path.c_str()); 226 return; 227 } 228 229 // SkAndroidCodec does not adjust for the origin by default. Dimensions may be reversed. 230 if (SkPixmapPriv::ShouldSwapWidthHeight(androidCodec->codec()->getOrigin())) { 231 auto swappedDims = SkPixmapPriv::SwapWidthHeight(androidCodec->getInfo()).dimensions(); 232 REPORTER_ASSERT(r, expectedDims == swappedDims); 233 } else { 234 REPORTER_ASSERT(r, expectedDims == androidCodec->getInfo().dimensions()); 235 } 236 237 // Passing kRespect adjusts for the origin. 238 androidCodec = SkAndroidCodec::MakeFromCodec(SkCodec::MakeFromData(std::move(data)), 239 SkAndroidCodec::ExifOrientationBehavior::kRespect); 240 auto info = androidCodec->getInfo(); 241 REPORTER_ASSERT(r, info.dimensions() == expectedDims); 242 243 SkBitmap fromGenerator; 244 fromGenerator.allocPixels(info); 245 REPORTER_ASSERT(r, gen->getPixels(info, fromGenerator.getPixels(), 246 fromGenerator.rowBytes())); 247 248 SkBitmap fromAndroidCodec; 249 fromAndroidCodec.allocPixels(info); 250 auto result = androidCodec->getPixels(info, fromAndroidCodec.getPixels(), 251 fromAndroidCodec.rowBytes()); 252 REPORTER_ASSERT(r, result == SkCodec::kSuccess); 253 254 for (int i = 0; i < info.width(); ++i) 255 for (int j = 0; j < info.height(); ++j) { 256 SkColor c1 = *fromGenerator .getAddr32(i, j); 257 SkColor c2 = *fromAndroidCodec.getAddr32(i, j); 258 if (c1 != c2) { 259 ERRORF(r, "Bitmaps for %s do not match starting at position %i, %i\n" 260 "\tfromGenerator: %x\tfromAndroidCodec: %x", path.c_str(), i, j, 261 c1, c2); 262 return; 263 } 264 } 265 } 266 } 267 268 DEF_TEST(AndroidCodec_sampledOrientation, r) { 269 if (GetResourcePath().isEmpty()) { 270 return; 271 } 272 273 // kRightTop_SkEncodedOrigin = 6, // Rotated 90 CW 274 auto path = "images/orientation/6.jpg"; 275 auto data = GetResourceAsData(path); 276 if (!data) { 277 ERRORF(r, "Failed to get resource %s", path); 278 return; 279 } 280 281 auto androidCodec = SkAndroidCodec::MakeFromCodec(SkCodec::MakeFromData(std::move(data)), 282 SkAndroidCodec::ExifOrientationBehavior::kRespect); 283 constexpr int sampleSize = 7; 284 auto sampledDims = androidCodec->getSampledDimensions(sampleSize); 285 286 SkAndroidCodec::AndroidOptions options; 287 options.fSampleSize = sampleSize; 288 289 SkBitmap bm; 290 auto info = androidCodec->getInfo().makeWH(sampledDims.width(), sampledDims.height()); 291 bm.allocPixels(info); 292 293 auto result = androidCodec->getAndroidPixels(info, bm.getPixels(), bm.rowBytes(), &options); 294 if (result != SkCodec::kSuccess) { 295 ERRORF(r, "got result \"%s\"\n", SkCodec::ResultToString(result)); 296 } 297 } 298