blob: 6b233181ead61e662054750256afdb1363051fc0 [file] [log] [blame]
// Copyright 2011 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/gfx/codec/jpeg_codec.h"
#include <climits>
#include <memory>
#include <optional>
#include "base/notreached.h"
#include "third_party/skia/include/codec/SkJpegDecoder.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkImageInfo.h"
#include "third_party/skia/include/encode/SkJpegEncoder.h"
#include "ui/gfx/codec/vector_wstream.h"
namespace gfx {
// Encoder ---------------------------------------------------------------------
std::optional<std::vector<uint8_t>> JPEGCodec::Encode(
const SkPixmap& input,
int quality,
SkJpegEncoder::Downsample downsample,
const SkData* xmp_metadata) {
std::vector<uint8_t> output;
VectorWStream dst(&output);
SkJpegEncoder::Options options;
options.fQuality = quality;
options.fDownsample = downsample;
if (xmp_metadata) {
options.xmpMetadata = xmp_metadata;
}
if (!SkJpegEncoder::Encode(&dst, input, options)) {
return std::nullopt;
}
return output;
}
std::optional<std::vector<uint8_t>> JPEGCodec::Encode(const SkPixmap& input,
int quality) {
return Encode(input, quality, SkJpegEncoder::Downsample::k420);
}
std::optional<std::vector<uint8_t>> JPEGCodec::Encode(const SkBitmap& src,
int quality) {
SkPixmap pixmap;
if (!src.peekPixels(&pixmap)) {
return std::nullopt;
}
return JPEGCodec::Encode(pixmap, quality);
}
// Decoder --------------------------------------------------------------------
namespace {
struct PreparationOutput {
std::unique_ptr<SkCodec> codec;
SkImageInfo image_info;
};
std::optional<PreparationOutput> PrepareForJPEGDecode(
base::span<const uint8_t> input,
SkColorType color_type) {
PreparationOutput output;
// We only support 8-bit RGBA and BGRA color types.
CHECK(color_type == kRGBA_8888_SkColorType ||
color_type == kBGRA_8888_SkColorType)
<< "Invalid pixel format " << color_type;
// Parse the input stream with the JPEG decoder, yielding a SkCodec.
auto stream = std::make_unique<SkMemoryStream>(input.data(), input.size(),
/*copyData=*/false);
SkCodec::Result result;
output.codec = SkJpegDecoder::Decode(std::move(stream), &result);
if (!output.codec || result != SkCodec::kSuccess) {
return std::nullopt;
}
// Reject images that would exceed INT_MAX bytes.
SkISize size = output.codec->dimensions();
constexpr int kBytesPerPixel = 4;
if (size.area() >= (INT_MAX / kBytesPerPixel)) {
return std::nullopt;
}
// The fuzzer is able to make astronomically large bitmaps (30000x30000) from
// very small inputs. Images this large can take several seconds to decode. In
// a build instrumented for fuzzing, this time can balloon to over a minute.
// To avoid timeouts, we limit the fuzzer to 16 million pixels. We don't
// reject very wide or very tall images, as long as the image is reasonably
// small on the other axis.
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
constexpr int kFuzzerPixelLimit = 4000 * 4000;
if (size.area() >= kFuzzerPixelLimit) {
return std::nullopt;
}
#endif
// Create an SkImageInfo matching our JPEG's dimensions and color type.
output.image_info = SkImageInfo::Make(size, color_type, kOpaque_SkAlphaType);
return output;
}
} // namespace
bool JPEGCodec::Decode(const uint8_t* input,
size_t input_size,
SkColorType color_type,
std::vector<uint8_t>* output,
int* w,
int* h) {
std::optional<PreparationOutput> preparation_output = PrepareForJPEGDecode(
UNSAFE_BUFFERS(base::span(input, input_size)), color_type);
if (!preparation_output) {
return false;
}
*w = preparation_output->image_info.width();
*h = preparation_output->image_info.height();
// Decode the pixels into the `output` vector.
output->resize(preparation_output->image_info.computeMinByteSize());
SkCodec::Result result = preparation_output->codec->getPixels(
preparation_output->image_info, &output->front(),
preparation_output->image_info.minRowBytes());
return result == SkCodec::kSuccess;
}
// static
SkBitmap JPEGCodec::Decode(base::span<const uint8_t> input) {
constexpr SkColorType kFormat = // Parens around (0) solve dead-code warning.
(SK_R32_SHIFT == (0)) ? kRGBA_8888_SkColorType
: (SK_B32_SHIFT == (0)) ? kBGRA_8888_SkColorType
: kUnknown_SkColorType;
std::optional<PreparationOutput> preparation_output =
PrepareForJPEGDecode(input, kFormat);
if (!preparation_output) {
return SkBitmap();
}
// Allocate pixel storage for the decoded JPEG.
SkBitmap bitmap;
if (!bitmap.tryAllocN32Pixels(preparation_output->image_info.width(),
preparation_output->image_info.height())) {
return SkBitmap();
}
// Decode the image pixels directly onto an SkBitmap.
SkCodec::Result result =
preparation_output->codec->getPixels(bitmap.pixmap());
if (result == SkCodec::kSuccess) {
return bitmap;
} else {
return SkBitmap();
}
}
} // namespace gfx