// If moving libwebp out of skia source tree, path for webp headers must be // updated accordingly. Here, we enforce using local copy in webp sub-directory. #include"webp/decode.h"// NO_G3_REWRITE #include"webp/demux.h"// NO_G3_REWRITE #include"webp/mux_types.h"// NO_G3_REWRITE
bool SkWebpCodec::IsWebp(constvoid* buf, size_t bytesRead) { // WEBP starts with the following: // RIFFXXXXWEBPVP // Where XXXX is unspecified. constchar* bytes = static_cast<constchar*>(buf); return bytesRead >= 14 && !memcmp(bytes, "RIFF", 4) && !memcmp(&bytes[8], "WEBPVP", 6);
}
// Parse headers of RIFF container, and check for valid Webp (VP8) content. // Returns an SkWebpCodec on success
std::unique_ptr<SkCodec> SkWebpCodec::MakeFromStream(std::unique_ptr<SkStream> stream,
Result* result) {
SkASSERT(result); if (!stream) {
*result = SkCodec::kInvalidInput; return nullptr;
} // Webp demux needs a contiguous data buffer.
sk_sp<SkData> data = nullptr; if (stream->getMemoryBase()) { // It is safe to make without copy because we'll hold onto the stream.
data = SkData::MakeWithoutCopy(stream->getMemoryBase(), stream->getLength());
} else {
data = SkCopyStreamToData(stream.get());
// If we are forced to copy the stream to a data, we can go ahead and delete the stream.
stream.reset(nullptr);
}
// It's a little strange that the |demux| will outlive |webpData|, though it needs the // pointer in |webpData| to remain valid. This works because the pointer remains valid // until the SkData is freed.
WebPData webpData = { data->bytes(), data->size() };
WebPDemuxState state;
SkAutoTCallVProc<WebPDemuxer, WebPDemuxDelete> demux(WebPDemuxPartial(&webpData, &state)); switch (state) { case WEBP_DEMUX_PARSE_ERROR:
*result = kInvalidInput; return nullptr; case WEBP_DEMUX_PARSING_HEADER:
*result = kIncompleteInput; return nullptr; case WEBP_DEMUX_PARSED_HEADER: case WEBP_DEMUX_DONE:
SkASSERT(demux); break;
}
// Validate the image size that's about to be decoded.
{ const int64_t size = sk_64_mul(width, height); // now check that if we are 4-bytes per pixel, we also don't overflow if (!SkTFitsIn<int32_t>(size) || SkTo<int32_t>(size) > (0x7FFFFFFF >> 2)) {
*result = kInvalidInput; return nullptr;
}
}
std::unique_ptr<SkEncodedInfo::ICCProfile> profile = nullptr;
{
WebPChunkIterator chunkIterator;
SkAutoTCallVProc<WebPChunkIterator, WebPDemuxReleaseChunkIterator> autoCI(&chunkIterator); if (WebPDemuxGetChunk(demux, "ICCP", 1, &chunkIterator)) { // FIXME: I think this could be MakeWithoutCopy auto chunk = SkData::MakeWithCopy(chunkIterator.chunk.bytes, chunkIterator.chunk.size);
profile = SkEncodedInfo::ICCProfile::Make(std::move(chunk));
} if (profile && profile->profile()->data_color_space != skcms_Signature_RGB) {
profile = nullptr;
}
}
// Get the first frame and its "features" to determine the color and alpha types.
WebPIterator frame;
SkAutoTCallVProc<WebPIterator, WebPDemuxReleaseIterator> autoFrame(&frame); if (!WebPDemuxGetFrame(demux, 1, &frame)) {
*result = kIncompleteInput; return nullptr;
}
WebPBitstreamFeatures features; switch (WebPGetFeatures(frame.fragment.bytes, frame.fragment.size, &features)) { case VP8_STATUS_OK: break; case VP8_STATUS_SUSPENDED: case VP8_STATUS_NOT_ENOUGH_DATA:
*result = kIncompleteInput; return nullptr; default:
*result = kInvalidInput; return nullptr;
}
constbool hasAlpha = SkToBool(frame.has_alpha)
|| frame.width != width || frame.height != height;
SkEncodedInfo::Color color;
SkEncodedInfo::Alpha alpha; switch (features.format) { case 0: // This indicates a "mixed" format. We could see this for // animated webps (multiple fragments). // We could also guess kYUV here, but I think it makes more // sense to guess kBGRA which is likely closer to the final // output. Otherwise, we might end up converting // BGRA->YUVA->BGRA.
[[fallthrough]]; case 2: // This is the lossless format (BGRA). if (hasAlpha) {
color = SkEncodedInfo::kBGRA_Color;
alpha = SkEncodedInfo::kUnpremul_Alpha;
} else {
color = SkEncodedInfo::kBGRX_Color;
alpha = SkEncodedInfo::kOpaque_Alpha;
} break; case 1: // This is the lossy format (YUV). if (hasAlpha) {
color = SkEncodedInfo::kYUVA_Color;
alpha = SkEncodedInfo::kUnpremul_Alpha;
} else {
color = SkEncodedInfo::kYUV_Color;
alpha = SkEncodedInfo::kOpaque_Alpha;
} break; default:
*result = kInvalidInput; return nullptr;
}
bool SkWebpCodec::onGetValidSubset(SkIRect* desiredSubset) const { if (!desiredSubset) { returnfalse;
}
if (!this->bounds().contains(*desiredSubset)) { returnfalse;
}
// As stated below, libwebp snaps to even left and top. Make sure top and left are even, so we // decode this exact subset. // Leave right and bottom unmodified, so we suggest a slightly larger subset than requested.
desiredSubset->fLeft = (desiredSubset->fLeft >> 1) << 1;
desiredSubset->fTop = (desiredSubset->fTop >> 1) << 1; returntrue;
}
int SkWebpCodec::onGetRepetitionCount() { auto flags = WebPDemuxGetI(fDemux.get(), WEBP_FF_FORMAT_FLAGS); if (!(flags & ANIMATION_FLAG)) { return 0;
}
int loopCount = WebPDemuxGetI(fDemux.get(), WEBP_FF_LOOP_COUNT); if (0 == loopCount) { return kRepetitionCountInfinite;
}
loopCount--; return loopCount;
}
int SkWebpCodec::onGetFrameCount() { auto flags = WebPDemuxGetI(fDemux.get(), WEBP_FF_FORMAT_FLAGS); if (!(flags & ANIMATION_FLAG)) { return 1;
}
bool SkWebpCodec::onGetFrameInfo(int i, FrameInfo* frameInfo) const { if (i >= fFrameHolder.size()) { returnfalse;
}
const Frame* frame = fFrameHolder.frame(i); if (!frame) { returnfalse;
}
if (frameInfo) { // libwebp only reports fully received frames for an // animated image.
frame->fillIn(frameInfo, true);
}
returntrue;
}
staticbool is_8888(SkColorType colorType) { switch (colorType) { case kRGBA_8888_SkColorType: case kBGRA_8888_SkColorType: returntrue; default: returnfalse;
}
}
// Requires that the src input be unpremultiplied (or opaque). staticvoid blend_line(SkColorType dstCT, void* dst,
SkColorType srcCT, constvoid* src,
SkAlphaType dstAt, bool srcHasAlpha, int width) {
SkRasterPipeline_MemoryCtx dst_ctx = { dst, 0 },
src_ctx = { const_cast<void*>(src), 0 };
SkRasterPipeline_<256> p;
p.appendLoadDst(dstCT, &dst_ctx); if (kUnpremul_SkAlphaType == dstAt) {
p.append(SkRasterPipelineOp::premul_dst);
}
p.appendLoad(srcCT, &src_ctx); if (srcHasAlpha) {
p.append(SkRasterPipelineOp::premul);
}
p.append(SkRasterPipelineOp::srcover);
if (kUnpremul_SkAlphaType == dstAt) {
p.append(SkRasterPipelineOp::unpremul);
}
p.appendStore(dstCT, &dst_ctx);
p.run(0,0, width,1);
}
SkCodec::Result SkWebpCodec::onGetPixels(const SkImageInfo& dstInfo, void* dst, size_t rowBytes, const Options& options, int* rowsDecodedPtr) { constint index = options.fFrameIndex;
SkASSERT(0 == index || index < fFrameHolder.size());
SkASSERT(0 == index || !options.fSubset);
WebPDecoderConfig config; if (0 == WebPInitDecoderConfig(&config)) { // ABI mismatch. // FIXME: New enum for this? return kInvalidInput;
}
// Free any memory associated with the buffer. Must be called last, so we declare it first.
SkAutoTCallVProc<WebPDecBuffer, WebPFreeDecBuffer> autoFree(&(config.output));
WebPIterator frame;
SkAutoTCallVProc<WebPIterator, WebPDemuxReleaseIterator> autoFrame(&frame); // If this succeeded in onGetFrameCount(), it should succeed again here.
SkAssertResult(WebPDemuxGetFrame(fDemux, index + 1, &frame));
constbool independent = index == 0 ? true :
(fFrameHolder.frame(index)->getRequiredFrame() == kNoFrame); // Get the frameRect. libwebp will have already signaled an error if this is not fully // contained by the canvas. auto frameRect = SkIRect::MakeXYWH(frame.x_offset, frame.y_offset, frame.width, frame.height);
SkASSERT(this->bounds().contains(frameRect)); constbool frameIsSubset = frameRect != this->bounds(); if (independent && frameIsSubset) {
SkSampler::Fill(dstInfo, dst, rowBytes, options.fZeroInitialized);
}
int dstX = frameRect.x(); int dstY = frameRect.y(); int subsetWidth = frameRect.width(); int subsetHeight = frameRect.height(); if (options.fSubset) {
SkIRect subset = *options.fSubset;
SkASSERT(this->bounds().contains(subset));
SkASSERT(SkIsAlign2(subset.fLeft) && SkIsAlign2(subset.fTop));
SkASSERT(this->getValidSubset(&subset) && subset == *options.fSubset);
if (!SkIRect::Intersects(subset, frameRect)) { return kSuccess;
}
int minXOffset = std::min(dstX, subset.x()); int minYOffset = std::min(dstY, subset.y());
dstX -= minXOffset;
dstY -= minYOffset;
frameRect.offset(-minXOffset, -minYOffset);
subset.offset(-minXOffset, -minYOffset);
// Just like we require that the requested subset x and y offset are even, libwebp // guarantees that the frame x and y offset are even (it's actually impossible to specify // an odd frame offset). So we can still guarantee that the adjusted offsets are even.
SkASSERT(SkIsAlign2(subset.fLeft) && SkIsAlign2(subset.fTop));
// Ignore the frame size and offset when determining if scaling is necessary. int scaledWidth = subsetWidth; int scaledHeight = subsetHeight;
SkISize srcSize = options.fSubset ? options.fSubset->size() : this->dimensions(); if (srcSize != dstInfo.dimensions()) {
config.options.use_scaling = 1;
// We need to be conservative here and floor rather than round. // Otherwise, we may find ourselves decoding off the end of memory.
dstX = scaleX * dstX;
scaledWidth = scaleX * scaledWidth;
dstY = scaleY * dstY;
scaledHeight = scaleY * scaledHeight; if (0 == scaledWidth || 0 == scaledHeight) { return kSuccess;
}
} else {
scaledWidth = dstInfo.width();
scaledHeight = dstInfo.height();
}
auto webpInfo = dstInfo; if (!frame.has_alpha) {
webpInfo = webpInfo.makeAlphaType(kOpaque_SkAlphaType);
} elseif (this->colorXform() || blendWithPrevFrame) { // the colorXform and blend_line expect unpremul.
webpInfo = webpInfo.makeAlphaType(kUnpremul_SkAlphaType);
} if (this->colorXform()) { // Swizzling between RGBA and BGRA is zero cost in a color transform. So when we have a // color transform, we should decode to whatever is easiest for libwebp, and then let the // color transform swizzle if necessary. // Lossy webp is encoded as YUV (so RGBA and BGRA are the same cost). Lossless webp is // encoded as BGRA. This means decoding to BGRA is either faster or the same cost as RGBA.
webpInfo = webpInfo.makeColorType(kBGRA_8888_SkColorType);
}
SkBitmap webpDst; if ((this->colorXform() && !is_8888(dstInfo.colorType())) || blendWithPrevFrame) { // We will decode the entire image and then perform the color transform. libwebp // does not provide a row-by-row API. This is a shame particularly when we do not want // 8888, since we will need to create another image sized buffer.
webpDst.allocPixels(webpInfo);
} else { // libwebp can decode directly into the output memory.
webpDst.installPixels(webpInfo, dst, rowBytes);
}
if (blendWithPrevFrame) { // Xform into temporary bitmap big enough for one row.
tmp.allocPixels(dstInfo.makeWH(scaledWidth, 1));
xformDst = tmp.getPixels();
} else {
xformDst = dst;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.