blob: 14573bb999825380741e494b5fe99096d9a23029 [file] [log] [blame]
/*
* Copyright (C) 2013 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "platform/image-decoders/jpeg/JPEGImageDecoder.h"
#include <memory>
#include "platform/SharedBuffer.h"
#include "platform/image-decoders/ImageAnimation.h"
#include "platform/image-decoders/ImageDecoderTestHelpers.h"
#include "platform/wtf/typed_arrays/ArrayBuffer.h"
#include "public/platform/WebData.h"
#include "public/platform/WebSize.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace blink {
static const size_t kLargeEnoughSize = 1000 * 1000;
namespace {
std::unique_ptr<ImageDecoder> CreateJPEGDecoder(size_t max_decoded_bytes) {
return WTF::WrapUnique(new JPEGImageDecoder(
ImageDecoder::kAlphaNotPremultiplied, ColorBehavior::TransformToSRGB(),
max_decoded_bytes));
}
std::unique_ptr<ImageDecoder> CreateJPEGDecoder() {
return CreateJPEGDecoder(ImageDecoder::kNoDecodedImageByteLimit);
}
} // anonymous namespace
void Downsample(size_t max_decoded_bytes,
unsigned* output_width,
unsigned* output_height,
const char* image_file_path) {
scoped_refptr<SharedBuffer> data = ReadFile(image_file_path);
ASSERT_TRUE(data);
std::unique_ptr<ImageDecoder> decoder = CreateJPEGDecoder(max_decoded_bytes);
decoder->SetData(data.get(), true);
ImageFrame* frame = decoder->DecodeFrameBufferAtIndex(0);
ASSERT_TRUE(frame);
*output_width = frame->Bitmap().width();
*output_height = frame->Bitmap().height();
EXPECT_EQ(IntSize(*output_width, *output_height), decoder->DecodedSize());
}
void ReadYUV(size_t max_decoded_bytes,
unsigned* output_y_width,
unsigned* output_y_height,
unsigned* output_uv_width,
unsigned* output_uv_height,
const char* image_file_path) {
scoped_refptr<SharedBuffer> data = ReadFile(image_file_path);
ASSERT_TRUE(data);
std::unique_ptr<ImageDecoder> decoder = CreateJPEGDecoder(max_decoded_bytes);
decoder->SetData(data.get(), true);
// Setting a dummy ImagePlanes object signals to the decoder that we want to
// do YUV decoding.
std::unique_ptr<ImagePlanes> dummy_image_planes =
std::make_unique<ImagePlanes>();
decoder->SetImagePlanes(std::move(dummy_image_planes));
bool size_is_available = decoder->IsSizeAvailable();
ASSERT_TRUE(size_is_available);
IntSize size = decoder->DecodedSize();
IntSize y_size = decoder->DecodedYUVSize(0);
IntSize u_size = decoder->DecodedYUVSize(1);
IntSize v_size = decoder->DecodedYUVSize(2);
ASSERT_TRUE(size.Width() == y_size.Width());
ASSERT_TRUE(size.Height() == y_size.Height());
ASSERT_TRUE(u_size.Width() == v_size.Width());
ASSERT_TRUE(u_size.Height() == v_size.Height());
*output_y_width = y_size.Width();
*output_y_height = y_size.Height();
*output_uv_width = u_size.Width();
*output_uv_height = u_size.Height();
size_t row_bytes[3];
row_bytes[0] = decoder->DecodedYUVWidthBytes(0);
row_bytes[1] = decoder->DecodedYUVWidthBytes(1);
row_bytes[2] = decoder->DecodedYUVWidthBytes(2);
scoped_refptr<ArrayBuffer> buffer(ArrayBuffer::Create(
row_bytes[0] * y_size.Height() + row_bytes[1] * u_size.Height() +
row_bytes[2] * v_size.Height(),
1));
void* planes[3];
planes[0] = buffer->Data();
planes[1] = ((char*)planes[0]) + row_bytes[0] * y_size.Height();
planes[2] = ((char*)planes[1]) + row_bytes[1] * u_size.Height();
std::unique_ptr<ImagePlanes> image_planes =
std::make_unique<ImagePlanes>(planes, row_bytes);
decoder->SetImagePlanes(std::move(image_planes));
ASSERT_TRUE(decoder->DecodeToYUV());
}
// Tests failure on a too big image.
TEST(JPEGImageDecoderTest, tooBig) {
std::unique_ptr<ImageDecoder> decoder = CreateJPEGDecoder(100);
EXPECT_FALSE(decoder->SetSize(10000, 10000));
EXPECT_TRUE(decoder->Failed());
}
// Tests that the JPEG decoder can downsample image whose width and height are
// multiples of 8, to ensure we compute the correct DecodedSize and pass correct
// parameters to libjpeg to output the image with the expected size.
TEST(JPEGImageDecoderTest, downsampleImageSizeMultipleOf8) {
const char* jpeg_file = "/LayoutTests/images/resources/lenna.jpg"; // 256x256
unsigned output_width, output_height;
// 1/8 downsample.
Downsample(40 * 40 * 4, &output_width, &output_height, jpeg_file);
EXPECT_EQ(32u, output_width);
EXPECT_EQ(32u, output_height);
// 2/8 downsample.
Downsample(70 * 70 * 4, &output_width, &output_height, jpeg_file);
EXPECT_EQ(64u, output_width);
EXPECT_EQ(64u, output_height);
// 3/8 downsample.
Downsample(100 * 100 * 4, &output_width, &output_height, jpeg_file);
EXPECT_EQ(96u, output_width);
EXPECT_EQ(96u, output_height);
// 4/8 downsample.
Downsample(130 * 130 * 4, &output_width, &output_height, jpeg_file);
EXPECT_EQ(128u, output_width);
EXPECT_EQ(128u, output_height);
// 5/8 downsample.
Downsample(170 * 170 * 4, &output_width, &output_height, jpeg_file);
EXPECT_EQ(160u, output_width);
EXPECT_EQ(160u, output_height);
// 6/8 downsample.
Downsample(200 * 200 * 4, &output_width, &output_height, jpeg_file);
EXPECT_EQ(192u, output_width);
EXPECT_EQ(192u, output_height);
// 7/8 downsample.
Downsample(230 * 230 * 4, &output_width, &output_height, jpeg_file);
EXPECT_EQ(224u, output_width);
EXPECT_EQ(224u, output_height);
}
// Tests that JPEG decoder can downsample image whose width and height are not
// multiple of 8. Ensures that we round using the same algorithm as libjpeg.
TEST(JPEGImageDecoderTest, downsampleImageSizeNotMultipleOf8) {
const char* jpeg_file =
"/LayoutTests/images/resources/icc-v2-gbr.jpg"; // 275x207
unsigned output_width, output_height;
// 1/8 downsample.
Downsample(40 * 40 * 4, &output_width, &output_height, jpeg_file);
EXPECT_EQ(35u, output_width);
EXPECT_EQ(26u, output_height);
// 2/8 downsample.
Downsample(70 * 70 * 4, &output_width, &output_height, jpeg_file);
EXPECT_EQ(69u, output_width);
EXPECT_EQ(52u, output_height);
// 3/8 downsample.
Downsample(100 * 100 * 4, &output_width, &output_height, jpeg_file);
EXPECT_EQ(104u, output_width);
EXPECT_EQ(78u, output_height);
// 4/8 downsample.
Downsample(130 * 130 * 4, &output_width, &output_height, jpeg_file);
EXPECT_EQ(138u, output_width);
EXPECT_EQ(104u, output_height);
// 5/8 downsample.
Downsample(170 * 170 * 4, &output_width, &output_height, jpeg_file);
EXPECT_EQ(172u, output_width);
EXPECT_EQ(130u, output_height);
// 6/8 downsample.
Downsample(200 * 200 * 4, &output_width, &output_height, jpeg_file);
EXPECT_EQ(207u, output_width);
EXPECT_EQ(156u, output_height);
// 7/8 downsample.
Downsample(230 * 230 * 4, &output_width, &output_height, jpeg_file);
EXPECT_EQ(241u, output_width);
EXPECT_EQ(182u, output_height);
}
// Tests that upsampling is not allowed.
TEST(JPEGImageDecoderTest, upsample) {
const char* jpeg_file = "/LayoutTests/images/resources/lenna.jpg"; // 256x256
unsigned output_width, output_height;
Downsample(kLargeEnoughSize, &output_width, &output_height, jpeg_file);
EXPECT_EQ(256u, output_width);
EXPECT_EQ(256u, output_height);
}
TEST(JPEGImageDecoderTest, yuv) {
const char* jpeg_file =
"/LayoutTests/images/resources/lenna.jpg"; // 256x256, YUV 4:2:0
unsigned output_y_width, output_y_height, output_uv_width, output_uv_height;
ReadYUV(kLargeEnoughSize, &output_y_width, &output_y_height, &output_uv_width,
&output_uv_height, jpeg_file);
EXPECT_EQ(256u, output_y_width);
EXPECT_EQ(256u, output_y_height);
EXPECT_EQ(128u, output_uv_width);
EXPECT_EQ(128u, output_uv_height);
const char* jpeg_file_image_size_not_multiple_of8 =
"/LayoutTests/images/resources/cropped_mandrill.jpg"; // 439x154
ReadYUV(kLargeEnoughSize, &output_y_width, &output_y_height, &output_uv_width,
&output_uv_height, jpeg_file_image_size_not_multiple_of8);
EXPECT_EQ(439u, output_y_width);
EXPECT_EQ(154u, output_y_height);
EXPECT_EQ(220u, output_uv_width);
EXPECT_EQ(77u, output_uv_height);
// Make sure we revert to RGBA decoding when we're about to downscale,
// which can occur on memory-constrained android devices.
scoped_refptr<SharedBuffer> data = ReadFile(jpeg_file);
ASSERT_TRUE(data);
std::unique_ptr<ImageDecoder> decoder = CreateJPEGDecoder(230 * 230 * 4);
decoder->SetData(data.get(), true);
std::unique_ptr<ImagePlanes> image_planes = std::make_unique<ImagePlanes>();
decoder->SetImagePlanes(std::move(image_planes));
ASSERT_TRUE(decoder->IsSizeAvailable());
ASSERT_FALSE(decoder->CanDecodeToYUV());
}
TEST(JPEGImageDecoderTest,
byteByByteBaselineJPEGWithColorProfileAndRestartMarkers) {
TestByteByByteDecode(&CreateJPEGDecoder,
"/LayoutTests/images/resources/"
"small-square-with-colorspin-profile.jpg",
1u, kAnimationNone);
}
TEST(JPEGImageDecoderTest, byteByByteProgressiveJPEG) {
TestByteByByteDecode(&CreateJPEGDecoder,
"/LayoutTests/images/resources/bug106024.jpg", 1u,
kAnimationNone);
}
TEST(JPEGImageDecoderTest, byteByByteRGBJPEGWithAdobeMarkers) {
TestByteByByteDecode(
&CreateJPEGDecoder,
"/LayoutTests/images/resources/rgb-jpeg-with-adobe-marker-only.jpg", 1u,
kAnimationNone);
}
// This test verifies that calling SharedBuffer::MergeSegmentsIntoBuffer() does
// not break JPEG decoding at a critical point: in between a call to decode the
// size (when JPEGImageDecoder stops while it may still have input data to
// read) and a call to do a full decode.
TEST(JPEGImageDecoderTest, mergeBuffer) {
const char* jpeg_file = "/LayoutTests/images/resources/lenna.jpg";
TestMergeBuffer(&CreateJPEGDecoder, jpeg_file);
}
// This tests decoding a JPEG with many progressive scans. Decoding should
// fail, but not hang (crbug.com/642462).
TEST(JPEGImageDecoderTest, manyProgressiveScans) {
scoped_refptr<SharedBuffer> test_data =
ReadFile(kDecodersTestingDir, "many-progressive-scans.jpg");
ASSERT_TRUE(test_data.get());
std::unique_ptr<ImageDecoder> test_decoder = CreateJPEGDecoder();
test_decoder->SetData(test_data.get(), true);
EXPECT_EQ(1u, test_decoder->FrameCount());
ASSERT_TRUE(test_decoder->DecodeFrameBufferAtIndex(0));
EXPECT_TRUE(test_decoder->Failed());
}
TEST(JPEGImageDecoderTest, SupportedSizesSquare) {
const char* jpeg_file = "/LayoutTests/images/resources/lenna.jpg"; // 256x256
scoped_refptr<SharedBuffer> data = ReadFile(jpeg_file);
ASSERT_TRUE(data);
std::unique_ptr<ImageDecoder> decoder =
CreateJPEGDecoder(std::numeric_limits<int>::max());
decoder->SetData(data.get(), true);
// This will decode the size and needs to be called to avoid DCHECKs
ASSERT_TRUE(decoder->IsSizeAvailable());
std::vector<SkISize> expected_sizes = {
SkISize::Make(32, 32), SkISize::Make(64, 64), SkISize::Make(96, 96),
SkISize::Make(128, 128), SkISize::Make(160, 160), SkISize::Make(192, 192),
SkISize::Make(224, 224), SkISize::Make(256, 256)};
auto sizes = decoder->GetSupportedDecodeSizes();
ASSERT_EQ(expected_sizes.size(), sizes.size());
for (size_t i = 0; i < sizes.size(); ++i) {
EXPECT_TRUE(expected_sizes[i] == sizes[i])
<< "Expected " << expected_sizes[i].width() << "x"
<< expected_sizes[i].height() << ". Got " << sizes[i].width() << "x"
<< sizes[i].height();
}
}
TEST(JPEGImageDecoderTest, SupportedSizesRectangle) {
const char* jpeg_file =
"/LayoutTests/images/resources/icc-v2-gbr.jpg"; // 275x207
scoped_refptr<SharedBuffer> data = ReadFile(jpeg_file);
ASSERT_TRUE(data);
std::unique_ptr<ImageDecoder> decoder =
CreateJPEGDecoder(std::numeric_limits<int>::max());
decoder->SetData(data.get(), true);
// This will decode the size and needs to be called to avoid DCHECKs
ASSERT_TRUE(decoder->IsSizeAvailable());
std::vector<SkISize> expected_sizes = {
SkISize::Make(35, 26), SkISize::Make(69, 52), SkISize::Make(104, 78),
SkISize::Make(138, 104), SkISize::Make(172, 130), SkISize::Make(207, 156),
SkISize::Make(241, 182), SkISize::Make(275, 207)};
auto sizes = decoder->GetSupportedDecodeSizes();
ASSERT_EQ(expected_sizes.size(), sizes.size());
for (size_t i = 0; i < sizes.size(); ++i) {
EXPECT_TRUE(expected_sizes[i] == sizes[i])
<< "Expected " << expected_sizes[i].width() << "x"
<< expected_sizes[i].height() << ". Got " << sizes[i].width() << "x"
<< sizes[i].height();
}
}
TEST(JPEGImageDecoderTest, SupportedSizesTruncatedIfMemoryBound) {
const char* jpeg_file = "/LayoutTests/images/resources/lenna.jpg"; // 256x256
scoped_refptr<SharedBuffer> data = ReadFile(jpeg_file);
ASSERT_TRUE(data);
// Limit the memory so that 128 would be the largest size possible.
std::unique_ptr<ImageDecoder> decoder = CreateJPEGDecoder(130 * 130 * 4);
decoder->SetData(data.get(), true);
// This will decode the size and needs to be called to avoid DCHECKs
ASSERT_TRUE(decoder->IsSizeAvailable());
std::vector<SkISize> expected_sizes = {
SkISize::Make(32, 32), SkISize::Make(64, 64), SkISize::Make(96, 96),
SkISize::Make(128, 128)};
auto sizes = decoder->GetSupportedDecodeSizes();
ASSERT_EQ(expected_sizes.size(), sizes.size());
for (size_t i = 0; i < sizes.size(); ++i) {
EXPECT_TRUE(expected_sizes[i] == sizes[i])
<< "Expected " << expected_sizes[i].width() << "x"
<< expected_sizes[i].height() << ". Got " << sizes[i].width() << "x"
<< sizes[i].height();
}
}
} // namespace blink