2 * Copyright 2016 Google Inc.
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
10 #include "SkCommonFlags.h"
11 #include "SkImageEncoder.h"
15 #include "Resources.h"
18 #include <initializer_list>
21 static void write_bm(const char* name, const SkBitmap& bm) {
22 if (FLAGS_writePath.isEmpty()) {
26 SkString filename = SkOSPath::Join(FLAGS_writePath[0], name);
27 filename.appendf(".png");
28 SkFILEWStream file(filename.c_str());
29 if (!SkEncodeImage(&file, bm, SkEncodedImageFormat::kPNG, 100)) {
30 SkDebugf("failed to write '%s'\n", filename.c_str());
34 DEF_TEST(Codec_frames, r) {
35 #define kOpaque kOpaque_SkAlphaType
36 #define kUnpremul kUnpremul_SkAlphaType
40 // One less than fFramecount, since the first frame is always
42 std::vector<size_t> fRequiredFrames;
43 // Same, since the first frame should match getInfo.
44 std::vector<SkAlphaType> fAlphaTypes;
45 // The size of this one should match fFrameCount for animated, empty
47 std::vector<size_t> fDurations;
50 { "alphabetAnim.gif", 13,
51 { SkCodec::kNone, 0, 0, 0, 0, 5, 6, SkCodec::kNone,
52 SkCodec::kNone, SkCodec::kNone, 10, 11 },
53 { kUnpremul, kUnpremul, kUnpremul, kUnpremul, kUnpremul, kUnpremul,
54 kUnpremul, kUnpremul, kUnpremul, kOpaque, kOpaque, kUnpremul },
55 { 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100 },
57 { "randPixelsAnim2.gif", 4,
61 { kOpaque, kOpaque, kOpaque },
66 { "randPixelsAnim.gif", 13,
68 { SkCodec::kNone, 1, 2, 3, 4, 3, 6, 7, 7, 7, 9, 9 },
69 { kUnpremul, kUnpremul, kUnpremul, kUnpremul, kUnpremul, kUnpremul,
70 kUnpremul, kUnpremul, kUnpremul, kUnpremul, kUnpremul, kUnpremul },
72 { 0, 1000, 170, 40, 220, 7770, 90, 90, 90, 90, 90, 90, 90 },
75 { "box.gif", 1, {}, {}, {}, 0 },
76 { "color_wheel.gif", 1, {}, {}, {}, 0 },
77 { "test640x479.gif", 4, { 0, 1, 2 },
78 { kOpaque, kOpaque, kOpaque },
79 { 200, 200, 200, 200 },
80 SkCodec::kRepetitionCountInfinite },
81 { "colorTables.gif", 2, { 0 }, { kOpaque }, { 1000, 1000 }, 5 },
83 { "arrow.png", 1, {}, {}, {}, 0 },
84 { "google_chrome.ico", 1, {}, {}, {}, 0 },
85 { "brickwork-texture.jpg", 1, {}, {}, {}, 0 },
86 #if defined(SK_CODEC_DECODES_RAW) && (!defined(_WIN32))
87 { "dng_with_preview.dng", 1, {}, {}, {}, 0 },
89 { "mandrill.wbmp", 1, {}, {}, {}, 0 },
90 { "randPixels.bmp", 1, {}, {}, {}, 0 },
91 { "yellow_rose.webp", 1, {}, {}, {}, 0 },
96 for (const auto& rec : gRecs) {
97 std::unique_ptr<SkStream> stream(GetResourceAsStream(rec.fName));
99 // Useful error statement, but sometimes people run tests without
100 // resources, and they do not want to see these messages.
101 //ERRORF(r, "Missing resources? Could not find '%s'", rec.fName);
105 std::unique_ptr<SkCodec> codec(SkCodec::NewFromStream(stream.release()));
107 ERRORF(r, "Failed to create an SkCodec from '%s'", rec.fName);
111 const int repetitionCount = codec->getRepetitionCount();
112 if (repetitionCount != rec.fRepetitionCount) {
113 ERRORF(r, "%s repetition count does not match! expected: %i\tactual: %i",
114 rec.fName, rec.fRepetitionCount, repetitionCount);
117 const size_t expected = rec.fFrameCount;
118 const auto frameInfos = codec->getFrameInfo();
119 // getFrameInfo returns empty set for non-animated.
120 const size_t frameCount = frameInfos.size() == 0 ? 1 : frameInfos.size();
121 if (frameCount != expected) {
122 ERRORF(r, "'%s' expected frame count: %i\tactual: %i", rec.fName, expected, frameCount);
126 if (rec.fRequiredFrames.size() + 1 != expected) {
127 ERRORF(r, "'%s' has wrong number entries in fRequiredFrames; expected: %i\tactual: %i",
128 rec.fName, expected, rec.fRequiredFrames.size() + 1);
132 if (1 == frameCount) {
136 auto to_string = [](SkAlphaType type) {
138 case kUnpremul_SkAlphaType:
140 case kOpaque_SkAlphaType:
146 // From here on, we are only concerned with animated images.
147 REPORTER_ASSERT(r, frameInfos[0].fRequiredFrame == SkCodec::kNone);
148 REPORTER_ASSERT(r, frameInfos[0].fAlphaType == codec->getInfo().alphaType());
149 for (size_t i = 1; i < frameCount; i++) {
150 if (rec.fRequiredFrames[i-1] != frameInfos[i].fRequiredFrame) {
151 ERRORF(r, "%s's frame %i has wrong dependency! expected: %i\tactual: %i",
152 rec.fName, i, rec.fRequiredFrames[i-1], frameInfos[i].fRequiredFrame);
154 auto expectedAlpha = rec.fAlphaTypes[i-1];
155 auto alpha = frameInfos[i].fAlphaType;
156 if (expectedAlpha != alpha) {
157 ERRORF(r, "%s's frame %i has wrong alpha type! expected: %s\tactual: %s",
158 rec.fName, i, to_string(expectedAlpha), to_string(alpha));
162 // Compare decoding in two ways:
163 // 1. Provide the frame that a frame depends on, so the codec just has to blend.
164 // (in the array cachedFrames)
165 // 2. Do not provide the frame that a frame depends on, so the codec has to decode all the
166 // way back to a key-frame. (in a local variable uncachedFrame)
167 // The two should look the same.
168 std::vector<SkBitmap> cachedFrames(frameCount);
169 const auto& info = codec->getInfo().makeColorType(kN32_SkColorType);
171 auto decode = [&](SkBitmap* bm, bool cached, size_t index) {
172 bm->allocPixels(info);
174 // First copy the pixels from the cached frame
175 const size_t requiredFrame = frameInfos[index].fRequiredFrame;
176 if (requiredFrame != SkCodec::kNone) {
177 const bool success = cachedFrames[requiredFrame].copyTo(bm);
178 REPORTER_ASSERT(r, success);
181 SkCodec::Options opts;
182 opts.fFrameIndex = index;
183 opts.fHasPriorFrame = cached;
184 const SkCodec::Result result = codec->getPixels(info, bm->getPixels(), bm->rowBytes(),
185 &opts, nullptr, nullptr);
186 REPORTER_ASSERT(r, result == SkCodec::kSuccess);
189 for (size_t i = 0; i < frameCount; i++) {
190 SkBitmap& cachedFrame = cachedFrames[i];
191 decode(&cachedFrame, true, i);
192 SkBitmap uncachedFrame;
193 decode(&uncachedFrame, false, i);
195 // Now verify they're equal.
196 const size_t rowLen = info.bytesPerPixel() * info.width();
197 for (int y = 0; y < info.height(); y++) {
198 const void* cachedAddr = cachedFrame.getAddr(0, y);
199 SkASSERT(cachedAddr != nullptr);
200 const void* uncachedAddr = uncachedFrame.getAddr(0, y);
201 SkASSERT(uncachedAddr != nullptr);
202 const bool lineMatches = memcmp(cachedAddr, uncachedAddr, rowLen) == 0;
204 SkString name = SkStringPrintf("cached_%i", i);
205 write_bm(name.c_str(), cachedFrame);
206 name = SkStringPrintf("uncached_%i", i);
207 write_bm(name.c_str(), uncachedFrame);
208 ERRORF(r, "%s's frame %i is different depending on caching!", rec.fName, i);
214 if (rec.fDurations.size() != expected) {
215 ERRORF(r, "'%s' has wrong number entries in fDurations; expected: %i\tactual: %i",
216 rec.fName, expected, rec.fDurations.size());
220 for (size_t i = 0; i < frameCount; i++) {
221 if (rec.fDurations[i] != frameInfos[i].fDuration) {
222 ERRORF(r, "%s frame %i's durations do not match! expected: %i\tactual: %i",
223 rec.fName, i, rec.fDurations[i], frameInfos[i].fDuration);