| OLD | NEW |
| 1 /* | 1 /* |
| 2 * Copyright 2016 Google Inc. | 2 * Copyright 2016 Google Inc. |
| 3 * | 3 * |
| 4 * Use of this source code is governed by a BSD-style license that can be | 4 * Use of this source code is governed by a BSD-style license that can be |
| 5 * found in the LICENSE file. | 5 * found in the LICENSE file. |
| 6 */ | 6 */ |
| 7 | 7 |
| 8 #include "SkBitmap.h" | 8 #include "SkBitmap.h" |
| 9 #include "SkCodec.h" | 9 #include "SkCodec.h" |
| 10 #include "SkData.h" | 10 #include "SkData.h" |
| (...skipping 20 matching lines...) Expand all Loading... |
| 31 SkAutoTDelete<SkCodec> codec(SkCodec::NewFromData(std::move(data))); | 31 SkAutoTDelete<SkCodec> codec(SkCodec::NewFromData(std::move(data))); |
| 32 if (!codec) { | 32 if (!codec) { |
| 33 return false; | 33 return false; |
| 34 } | 34 } |
| 35 | 35 |
| 36 const SkImageInfo info = standardize_info(codec); | 36 const SkImageInfo info = standardize_info(codec); |
| 37 dst->allocPixels(info); | 37 dst->allocPixels(info); |
| 38 return SkCodec::kSuccess == codec->getPixels(info, dst->getPixels(), dst->ro
wBytes()); | 38 return SkCodec::kSuccess == codec->getPixels(info, dst->getPixels(), dst->ro
wBytes()); |
| 39 } | 39 } |
| 40 | 40 |
| 41 static void compare_bitmaps(skiatest::Reporter* r, const SkBitmap& bm1, const Sk
Bitmap& bm2) { |
| 42 const SkImageInfo& info = bm1.info(); |
| 43 if (info != bm2.info()) { |
| 44 ERRORF(r, "Bitmaps have different image infos!"); |
| 45 return; |
| 46 } |
| 47 const size_t rowBytes = info.minRowBytes(); |
| 48 for (int i = 0; i < info.height(); i++) { |
| 49 REPORTER_ASSERT(r, !memcmp(bm1.getAddr(0, 0), bm2.getAddr(0, 0), rowByte
s)); |
| 50 } |
| 51 } |
| 52 |
| 41 /* | 53 /* |
| 42 * Represents a stream without all of its data. | 54 * Represents a stream without all of its data. |
| 43 */ | 55 */ |
| 44 class HaltingStream : public SkStream { | 56 class HaltingStream : public SkStream { |
| 45 public: | 57 public: |
| 46 HaltingStream(sk_sp<SkData> data) | 58 HaltingStream(sk_sp<SkData> data, size_t initialLimit) |
| 47 : fTotalSize(data->size()) | 59 : fTotalSize(data->size()) |
| 48 , fLimit(fTotalSize / 2) | 60 , fLimit(initialLimit) |
| 49 , fStream(std::move(data)) | 61 , fStream(std::move(data)) |
| 50 {} | 62 {} |
| 51 | 63 |
| 52 void addNewData() { | 64 void addNewData(size_t extra) { |
| 53 // Arbitrary size, but deliberately different from | 65 fLimit = SkTMin(fTotalSize, fLimit + extra); |
| 54 // the buffer size used by SkPngCodec. | |
| 55 fLimit = SkTMin(fTotalSize, fLimit + 1000); | |
| 56 } | 66 } |
| 57 | 67 |
| 58 size_t read(void* buffer, size_t size) override { | 68 size_t read(void* buffer, size_t size) override { |
| 59 if (fStream.getPosition() + size > fLimit) { | 69 if (fStream.getPosition() + size > fLimit) { |
| 60 size = fLimit - fStream.getPosition(); | 70 size = fLimit - fStream.getPosition(); |
| 61 } | 71 } |
| 62 | 72 |
| 63 return fStream.read(buffer, size); | 73 return fStream.read(buffer, size); |
| 64 } | 74 } |
| 65 | 75 |
| 66 bool isAtEnd() const override { | 76 bool isAtEnd() const override { |
| 67 return fStream.isAtEnd(); | 77 return fStream.isAtEnd(); |
| 68 } | 78 } |
| 69 | 79 |
| 70 bool hasPosition() const override { return true; } | 80 bool hasPosition() const override { return true; } |
| 71 size_t getPosition() const override { return fStream.getPosition(); } | 81 size_t getPosition() const override { return fStream.getPosition(); } |
| 72 bool rewind() override { return fStream.rewind(); } | 82 bool rewind() override { return fStream.rewind(); } |
| 73 bool move(long offset) override { return fStream.move(offset); } | 83 bool move(long offset) override { return fStream.move(offset); } |
| 74 | 84 |
| 85 bool isAllDataReceived() const { return fLimit == fTotalSize; } |
| 86 |
| 75 private: | 87 private: |
| 76 const size_t fTotalSize; | 88 const size_t fTotalSize; |
| 77 size_t fLimit; | 89 size_t fLimit; |
| 78 SkMemoryStream fStream; | 90 SkMemoryStream fStream; |
| 79 }; | 91 }; |
| 80 | 92 |
| 81 static void test_partial(skiatest::Reporter* r, const char* name) { | 93 static void test_partial(skiatest::Reporter* r, const char* name) { |
| 82 sk_sp<SkData> file = make_from_resource(name); | 94 sk_sp<SkData> file = make_from_resource(name); |
| 83 if (!file) { | 95 if (!file) { |
| 84 SkDebugf("missing resource %s\n", name); | 96 SkDebugf("missing resource %s\n", name); |
| 85 return; | 97 return; |
| 86 } | 98 } |
| 87 | 99 |
| 88 SkBitmap truth; | 100 SkBitmap truth; |
| 89 if (!create_truth(file, &truth)) { | 101 if (!create_truth(file, &truth)) { |
| 90 ERRORF(r, "Failed to decode %s\n", name); | 102 ERRORF(r, "Failed to decode %s\n", name); |
| 91 return; | 103 return; |
| 92 } | 104 } |
| 93 | 105 |
| 94 const size_t fileSize = file->size(); | |
| 95 | |
| 96 // Now decode part of the file | 106 // Now decode part of the file |
| 97 HaltingStream* stream = new HaltingStream(file); | 107 HaltingStream* stream = new HaltingStream(file, file->size() / 2); |
| 98 | 108 |
| 99 // Note that we cheat and hold on to a pointer to stream, though it is owned
by | 109 // Note that we cheat and hold on to a pointer to stream, though it is owned
by |
| 100 // partialCodec. | 110 // partialCodec. |
| 101 SkAutoTDelete<SkCodec> partialCodec(SkCodec::NewFromStream(stream)); | 111 SkAutoTDelete<SkCodec> partialCodec(SkCodec::NewFromStream(stream)); |
| 102 if (!partialCodec) { | 112 if (!partialCodec) { |
| 103 // Technically, this could be a small file where half the file is not | 113 // Technically, this could be a small file where half the file is not |
| 104 // enough. | 114 // enough. |
| 105 ERRORF(r, "Failed to create codec for %s", name); | 115 ERRORF(r, "Failed to create codec for %s", name); |
| 106 return; | 116 return; |
| 107 } | 117 } |
| 108 | 118 |
| 109 const SkImageInfo info = standardize_info(partialCodec); | 119 const SkImageInfo info = standardize_info(partialCodec); |
| 110 SkASSERT(info == truth.info()); | 120 SkASSERT(info == truth.info()); |
| 111 SkBitmap incremental; | 121 SkBitmap incremental; |
| 112 incremental.allocPixels(info); | 122 incremental.allocPixels(info); |
| 113 | 123 |
| 114 const SkCodec::Result startResult = partialCodec->startIncrementalDecode(inf
o, | 124 while (true) { |
| 115 incremental.getPixels(), incremental.rowBytes()); | 125 const SkCodec::Result startResult = partialCodec->startIncrementalDecode
(info, |
| 116 if (startResult != SkCodec::kSuccess) { | 126 incremental.getPixels(), incremental.rowBytes()); |
| 117 ERRORF(r, "Failed to start incremental decode\n"); | 127 if (startResult == SkCodec::kSuccess) { |
| 118 return; | 128 break; |
| 129 } |
| 130 |
| 131 if (stream->isAllDataReceived()) { |
| 132 ERRORF(r, "Failed to start incremental decode\n"); |
| 133 return; |
| 134 } |
| 135 |
| 136 // Append some data. The size is arbitrary, but deliberately different f
rom |
| 137 // the buffer size used by SkPngCodec. |
| 138 stream->addNewData(1000); |
| 119 } | 139 } |
| 120 | 140 |
| 121 while (true) { | 141 while (true) { |
| 122 const SkCodec::Result result = partialCodec->incrementalDecode(); | 142 const SkCodec::Result result = partialCodec->incrementalDecode(); |
| 123 | 143 |
| 124 if (stream->getPosition() == fileSize) { | 144 if (result == SkCodec::kSuccess) { |
| 125 REPORTER_ASSERT(r, result == SkCodec::kSuccess); | |
| 126 break; | 145 break; |
| 127 } | 146 } |
| 128 | 147 |
| 129 SkASSERT(stream->getPosition() < fileSize); | |
| 130 | |
| 131 REPORTER_ASSERT(r, result == SkCodec::kIncompleteInput); | 148 REPORTER_ASSERT(r, result == SkCodec::kIncompleteInput); |
| 132 | 149 |
| 133 // Append an arbitrary amount of data. | 150 if (stream->isAllDataReceived()) { |
| 134 stream->addNewData(); | 151 ERRORF(r, "Failed to completely decode %s", name); |
| 152 return; |
| 153 } |
| 154 |
| 155 // Append some data. The size is arbitrary, but deliberately different f
rom |
| 156 // the buffer size used by SkPngCodec. |
| 157 stream->addNewData(1000); |
| 135 } | 158 } |
| 136 | 159 |
| 137 // compare to original | 160 // compare to original |
| 138 for (int i = 0; i < info.height(); i++) { | 161 compare_bitmaps(r, truth, incremental); |
| 139 REPORTER_ASSERT(r, !memcmp(truth.getAddr(0, 0), incremental.getAddr(0, 0
), | |
| 140 info.minRowBytes())); | |
| 141 } | |
| 142 } | 162 } |
| 143 | 163 |
| 144 DEF_TEST(Codec_partial, r) { | 164 DEF_TEST(Codec_partial, r) { |
| 145 test_partial(r, "plane.png"); | 165 test_partial(r, "plane.png"); |
| 146 test_partial(r, "plane_interlaced.png"); | 166 test_partial(r, "plane_interlaced.png"); |
| 147 test_partial(r, "yellow_rose.png"); | 167 test_partial(r, "yellow_rose.png"); |
| 148 test_partial(r, "index8.png"); | 168 test_partial(r, "index8.png"); |
| 149 test_partial(r, "color_wheel.png"); | 169 test_partial(r, "color_wheel.png"); |
| 150 test_partial(r, "mandrill_256.png"); | 170 test_partial(r, "mandrill_256.png"); |
| 151 test_partial(r, "mandrill_32.png"); | 171 test_partial(r, "mandrill_32.png"); |
| 152 test_partial(r, "arrow.png"); | 172 test_partial(r, "arrow.png"); |
| 153 test_partial(r, "randPixels.png"); | 173 test_partial(r, "randPixels.png"); |
| 154 test_partial(r, "baby_tux.png"); | 174 test_partial(r, "baby_tux.png"); |
| 175 |
| 176 test_partial(r, "box.gif"); |
| 177 test_partial(r, "randPixels.gif"); |
| 178 test_partial(r, "color_wheel.gif"); |
| 179 } |
| 180 |
| 181 DEF_TEST(Codec_partialAnim, r) { |
| 182 auto path = "test640x479.gif"; |
| 183 sk_sp<SkData> file = make_from_resource(path); |
| 184 if (!file) { |
| 185 return; |
| 186 } |
| 187 |
| 188 // This stream will be owned by fullCodec, but we hang on to the pointer |
| 189 // to determine frame offsets. |
| 190 SkStream* stream = new SkMemoryStream(file); |
| 191 std::unique_ptr<SkCodec> fullCodec(SkCodec::NewFromStream(stream)); |
| 192 const auto info = standardize_info(fullCodec.get()); |
| 193 |
| 194 // frameByteCounts stores the number of bytes to decode a particular frame. |
| 195 // - [0] is the number of bytes for the header |
| 196 // - frames[i] requires frameByteCounts[i+1] bytes to decode |
| 197 std::vector<size_t> frameByteCounts; |
| 198 std::vector<SkBitmap> frames; |
| 199 size_t lastOffset = 0; |
| 200 for (size_t i = 0; true; i++) { |
| 201 frameByteCounts.push_back(stream->getPosition() - lastOffset); |
| 202 lastOffset = stream->getPosition(); |
| 203 |
| 204 SkBitmap frame; |
| 205 frame.allocPixels(info); |
| 206 |
| 207 SkCodec::Options opts; |
| 208 opts.fFrameIndex = i; |
| 209 const SkCodec::Result result = fullCodec->getPixels(info, frame.getPixel
s(), |
| 210 frame.rowBytes(), &opts, nullptr, nullptr); |
| 211 |
| 212 if (result == SkCodec::kIncompleteInput) { |
| 213 frameByteCounts.push_back(stream->getPosition() - lastOffset); |
| 214 |
| 215 // We need to distinguish between a partial frame and no more frames
. |
| 216 // getFrameInfo lets us do this, since it tells the number of frames |
| 217 // not considering whether they are complete. |
| 218 // FIXME: Should we use a different Result? |
| 219 if (fullCodec->getFrameInfo().size() > i) { |
| 220 // This is a partial frame. |
| 221 frames.push_back(frame); |
| 222 } |
| 223 break; |
| 224 } |
| 225 |
| 226 if (result != SkCodec::kSuccess) { |
| 227 ERRORF(r, "Failed to decode frame %i from %s", i, path); |
| 228 return; |
| 229 } |
| 230 |
| 231 frames.push_back(frame); |
| 232 } |
| 233 |
| 234 // Now decode frames partially, then completely, and compare to the original
. |
| 235 HaltingStream* haltingStream = new HaltingStream(file, frameByteCounts[0]); |
| 236 std::unique_ptr<SkCodec> partialCodec(SkCodec::NewFromStream(haltingStream))
; |
| 237 if (!partialCodec) { |
| 238 ERRORF(r, "Failed to create a partial codec from %s with %i bytes out of
%i", |
| 239 path, frameByteCounts[0], file->size()); |
| 240 return; |
| 241 } |
| 242 |
| 243 SkASSERT(frameByteCounts.size() > frames.size()); |
| 244 for (size_t i = 0; i < frames.size(); i++) { |
| 245 const size_t fullFrameBytes = frameByteCounts[i + 1]; |
| 246 const size_t firstHalf = fullFrameBytes / 2; |
| 247 const size_t secondHalf = fullFrameBytes - firstHalf; |
| 248 |
| 249 haltingStream->addNewData(firstHalf); |
| 250 |
| 251 SkBitmap frame; |
| 252 frame.allocPixels(info); |
| 253 |
| 254 SkCodec::Options opts; |
| 255 opts.fFrameIndex = i; |
| 256 SkCodec::Result result = partialCodec->startIncrementalDecode(info, |
| 257 frame.getPixels(), frame.rowBytes(), &opts); |
| 258 if (result != SkCodec::kSuccess) { |
| 259 ERRORF(r, "Failed to start incremental decode for %s on frame %i", |
| 260 path, i); |
| 261 return; |
| 262 } |
| 263 |
| 264 result = partialCodec->incrementalDecode(); |
| 265 REPORTER_ASSERT(r, SkCodec::kIncompleteInput == result); |
| 266 |
| 267 haltingStream->addNewData(secondHalf); |
| 268 result = partialCodec->incrementalDecode(); |
| 269 REPORTER_ASSERT(r, SkCodec::kSuccess == result); |
| 270 |
| 271 // allocPixels locked the pixels for frame, but frames[i] was copied |
| 272 // from another bitmap, and did not retain the locked status. |
| 273 SkAutoLockPixels alp(frames[i]); |
| 274 compare_bitmaps(r, frames[i], frame); |
| 275 } |
| 155 } | 276 } |
| 156 | 277 |
| 157 // Test that calling getPixels when an incremental decode has been | 278 // Test that calling getPixels when an incremental decode has been |
| 158 // started (but not finished) makes the next call to incrementalDecode | 279 // started (but not finished) makes the next call to incrementalDecode |
| 159 // require a call to startIncrementalDecode. | 280 // require a call to startIncrementalDecode. |
| 160 static void test_interleaved(skiatest::Reporter* r, const char* name) { | 281 static void test_interleaved(skiatest::Reporter* r, const char* name) { |
| 161 sk_sp<SkData> file = make_from_resource(name); | 282 sk_sp<SkData> file = make_from_resource(name); |
| 283 if (!file) { |
| 284 return; |
| 285 } |
| 286 const size_t halfSize = file->size() / 2; |
| 162 SkAutoTDelete<SkCodec> partialCodec(SkCodec::NewFromStream( | 287 SkAutoTDelete<SkCodec> partialCodec(SkCodec::NewFromStream( |
| 163 new HaltingStream(std::move(file)))); | 288 new HaltingStream(std::move(file), halfSize))); |
| 164 if (!partialCodec) { | 289 if (!partialCodec) { |
| 165 ERRORF(r, "Failed to create codec for %s", name); | 290 ERRORF(r, "Failed to create codec for %s", name); |
| 166 return; | 291 return; |
| 167 } | 292 } |
| 168 | 293 |
| 169 const SkImageInfo info = standardize_info(partialCodec); | 294 const SkImageInfo info = standardize_info(partialCodec); |
| 170 SkBitmap incremental; | 295 SkBitmap incremental; |
| 171 incremental.allocPixels(info); | 296 incremental.allocPixels(info); |
| 172 | 297 |
| 173 const SkCodec::Result startResult = partialCodec->startIncrementalDecode(inf
o, | 298 const SkCodec::Result startResult = partialCodec->startIncrementalDecode(inf
o, |
| (...skipping 12 matching lines...) Expand all Loading... |
| 186 REPORTER_ASSERT(r, result == SkCodec::kIncompleteInput); | 311 REPORTER_ASSERT(r, result == SkCodec::kIncompleteInput); |
| 187 | 312 |
| 188 // Now incremental decode will fail | 313 // Now incremental decode will fail |
| 189 result = partialCodec->incrementalDecode(); | 314 result = partialCodec->incrementalDecode(); |
| 190 REPORTER_ASSERT(r, result == SkCodec::kInvalidParameters); | 315 REPORTER_ASSERT(r, result == SkCodec::kInvalidParameters); |
| 191 } | 316 } |
| 192 | 317 |
| 193 DEF_TEST(Codec_rewind, r) { | 318 DEF_TEST(Codec_rewind, r) { |
| 194 test_interleaved(r, "plane.png"); | 319 test_interleaved(r, "plane.png"); |
| 195 test_interleaved(r, "plane_interlaced.png"); | 320 test_interleaved(r, "plane_interlaced.png"); |
| 321 test_interleaved(r, "box.gif"); |
| 196 } | 322 } |
| OLD | NEW |