Home | History | Annotate | Download | only in tests
      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