skia2/tests/CodecExactReadTest.cpp
Leon Scroggins III 995b467563 SkIcoCodec: Read the entire stream into data
Bug: 932080
Bug: b/142252770

An ICO file has a directory of images that are stored later in the file.
The directory contains the offset and size of the images. SkIcoCodec
uses these to create embedded SkPng/SkBmpCodecs. The old implementation
allocated a block of memory for each image and copied the stream into
those blocks so that the embedded SkCodecs could independently read
their encoded data.

Although SkIcoCodec checks for null, this still allows large (albeit
temporary - since we'll discard them if the stream does not contain
enough data to fill them) allocations and the potential for over-
commit.

Instead, read the entire stream into a contiguous buffer. If the stream
is already actually a buffer, just use that directly. In this case, the
new code will do less work. Otherwise, the memory we allocate is
limited by the size of the stream.

Note that this is a behavior change for a stream that contains two
consecutive ICOs, where the client expects to be able to read the second
one later. This was an issue for PNGs on Android (b/34073812), but I
suspect no one is relying on this behavior for ICO. Update Codec_end
test to remove the ICO test.

Alternatives to consider:
- only buffer the individual encoded images. This will allow us to
continue passing the Codec_end test.
- lazily read the embedded streams. Currently we read their start to
verify they are valid images (at least in the header) and read their
actual sizes and bit-depths, which could differ from that listed in
the directory. We use those to make a guess at the "best" image to use.
An image with mismatched sizes may now decode differently.

Change-Id: I30e5f6c8c2e5a0fa135348f61efe151a7f5d4756
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/277058
Auto-Submit: Leon Scroggins <scroggo@google.com>
Commit-Queue: Derek Sollenberger <djsollen@google.com>
Reviewed-by: Derek Sollenberger <djsollen@google.com>
2020-06-01 21:14:09 +00:00

94 lines
3.0 KiB
C++

/*
* Copyright 2017 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "include/codec/SkCodec.h"
#include "include/core/SkBitmap.h"
#include "include/core/SkData.h"
#include "include/core/SkImageInfo.h"
#include "include/core/SkRefCnt.h"
#include "include/core/SkStream.h"
#include "include/private/SkTemplates.h"
#include "tests/Test.h"
#include "tools/Resources.h"
#include <cstring>
#include <initializer_list>
#include <memory>
#include <utility>
namespace {
// This class wraps another SkStream. It does not own the underlying stream, so
// that the underlying stream can be reused starting from where the first
// client left off. This mimics Android's JavaInputStreamAdaptor.
class UnowningStream : public SkStream {
public:
explicit UnowningStream(SkStream* stream)
: fStream(stream)
{}
size_t read(void* buf, size_t bytes) override {
return fStream->read(buf, bytes);
}
bool rewind() override {
return fStream->rewind();
}
bool isAtEnd() const override {
return fStream->isAtEnd();
}
private:
SkStream* fStream; // Unowned.
};
} // namespace
// Test that some SkCodecs do not attempt to read input beyond the logical
// end of the data. Some other SkCodecs do, but some Android apps rely on not
// doing so for PNGs. Test on other formats that work.
DEF_TEST(Codec_end, r) {
for (const char* path : { "images/plane.png",
"images/yellow_rose.png",
"images/plane_interlaced.png",
"images/mandrill.wbmp",
"images/randPixels.bmp",
}) {
sk_sp<SkData> data = GetResourceAsData(path);
if (!data) {
continue;
}
const int kNumImages = 2;
const size_t size = data->size();
sk_sp<SkData> multiData = SkData::MakeUninitialized(size * kNumImages);
void* dst = multiData->writable_data();
for (int i = 0; i < kNumImages; i++) {
memcpy(SkTAddOffset<void>(dst, size * i), data->data(), size);
}
data.reset();
SkMemoryStream stream(std::move(multiData));
for (int i = 0; i < kNumImages; ++i) {
std::unique_ptr<SkCodec> codec(SkCodec::MakeFromStream(
std::make_unique<UnowningStream>(&stream)));
if (!codec) {
ERRORF(r, "Failed to create a codec from %s, iteration %i", path, i);
continue;
}
auto info = codec->getInfo().makeColorType(kN32_SkColorType);
SkBitmap bm;
bm.allocPixels(info);
auto result = codec->getPixels(bm.info(), bm.getPixels(), bm.rowBytes());
if (result != SkCodec::kSuccess) {
ERRORF(r, "Failed to getPixels from %s, iteration %i error %i", path, i, result);
continue;
}
}
}
}