blob: bf3beca3215a0138265370041248621b31b83bf6 [file] [log] [blame]
// Copyright 2015 The Cobalt Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "cobalt/loader/image/image_decoder.h"
#include <memory>
#include <string>
#include <vector>
#include "base/bind.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/optional.h"
#include "base/path_service.h"
#include "base/threading/thread.h"
#include "cobalt/base/cobalt_paths.h"
#include "cobalt/base/polymorphic_downcast.h"
#include "cobalt/loader/image/animated_webp_image.h"
#include "cobalt/loader/image/jpeg_image_decoder.h"
#include "cobalt/render_tree/resource_provider_stub.h"
#include "starboard/configuration.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace cobalt {
namespace loader {
namespace image {
namespace {
struct MockImageDecoderCallback {
void SuccessCallback(const scoped_refptr<Image>& value) { image = value; }
MOCK_METHOD1(LoadCompleteCallback,
void(const base::Optional<std::string>& message));
scoped_refptr<Image> image;
};
class MockImageDecoder : public Decoder {
public:
MockImageDecoder();
~MockImageDecoder() override {}
LoadResponseType OnResponseStarted(
Fetcher* fetcher,
const scoped_refptr<net::HttpResponseHeaders>& headers) override;
void DecodeChunk(const char* data, size_t size) override;
void Finish() override;
bool Suspend() override;
void Resume(render_tree::ResourceProvider* resource_provider) override;
scoped_refptr<Image> image();
void ExpectCallWithError(const base::Optional<std::string>& error);
protected:
render_tree::ResourceProviderStub resource_provider_;
::testing::StrictMock<MockImageDecoderCallback> image_decoder_callback_;
std::unique_ptr<Decoder> image_decoder_;
};
MockImageDecoder::MockImageDecoder() {
image_decoder_.reset(new ImageDecoder(
&resource_provider_,
base::Bind(&MockImageDecoderCallback::SuccessCallback,
base::Unretained(&image_decoder_callback_)),
base::Bind(&MockImageDecoderCallback::LoadCompleteCallback,
base::Unretained(&image_decoder_callback_))));
}
LoadResponseType MockImageDecoder::OnResponseStarted(
Fetcher* fetcher, const scoped_refptr<net::HttpResponseHeaders>& headers) {
return image_decoder_->OnResponseStarted(fetcher, headers);
}
void MockImageDecoder::DecodeChunk(const char* data, size_t size) {
image_decoder_->DecodeChunk(data, size);
}
void MockImageDecoder::Finish() { image_decoder_->Finish(); }
bool MockImageDecoder::Suspend() { return image_decoder_->Suspend(); }
void MockImageDecoder::Resume(
render_tree::ResourceProvider* resource_provider) {
image_decoder_->Resume(resource_provider);
}
scoped_refptr<Image> MockImageDecoder::image() {
return image_decoder_callback_.image;
}
void MockImageDecoder::ExpectCallWithError(
const base::Optional<std::string>& error) {
EXPECT_CALL(image_decoder_callback_, LoadCompleteCallback(error));
}
base::FilePath GetTestImagePath(const char* file_name) {
base::FilePath data_directory;
CHECK(base::PathService::Get(base::DIR_TEST_DATA, &data_directory));
return data_directory.Append(FILE_PATH_LITERAL("cobalt"))
.Append(FILE_PATH_LITERAL("loader"))
.Append(FILE_PATH_LITERAL("testdata"))
.Append(FILE_PATH_LITERAL(file_name));
}
std::vector<uint8> GetImageData(const base::FilePath& file_path) {
int64 size;
std::vector<uint8> image_data;
bool success = base::GetFileSize(file_path, &size);
CHECK(success) << "Could not get file size.";
CHECK_GT(size, 0);
image_data.resize(static_cast<size_t>(size));
int num_of_bytes =
base::ReadFile(file_path, reinterpret_cast<char*>(&image_data[0]),
static_cast<int>(size));
CHECK_EQ(num_of_bytes, static_cast<int>(image_data.size()))
<< "Could not read '" << file_path.value() << "'.";
return image_data;
}
// Check if pixels are the same as |test_color|.
::testing::AssertionResult CheckSameColor(const uint8* pixels, int width,
int height, uint32 test_color) {
// Iterate through each pixel testing that the value is the expected value.
for (int index = 0; index < width * height; ++index) {
uint32 current_color = static_cast<uint32>(
(pixels[0] << 24) | (pixels[1] << 16) | (pixels[2] << 8) | pixels[3]);
pixels += 4;
if (current_color != test_color) {
return ::testing::AssertionFailure()
<< "'current_color' should be " << test_color << " but is "
<< current_color;
}
}
return ::testing::AssertionSuccess();
}
// Check if color component in the plane are the same as |test_value|.
::testing::AssertionResult CheckSameValue(const uint8* pixels, int width,
int height, int pitch_in_bytes,
uint8 test_value) {
while (height > 0) {
for (int i = 0; i < width; ++i) {
if (pixels[i] != test_value) {
return ::testing::AssertionFailure()
<< "'pixels[" << i << "]' should be "
<< static_cast<int>(test_value) << " but is "
<< static_cast<int>(pixels[i]);
}
}
pixels += pitch_in_bytes;
--height;
}
return ::testing::AssertionSuccess();
}
// Check if all pixels in an image are the same as |test_color| if it is single
// plane image, or if it is the same as the y/u/v colors if it is multi plane
// image. Note that in the multi plane case, the function only supports
// kMultiPlaneImageFormatYUV3PlaneBT601FullRange for now..
::testing::AssertionResult CheckUniformColoredImage(
render_tree::ImageStub* image_data, uint32 test_color, uint8 y_color,
uint8 u_color, uint8 v_color) {
if (image_data->is_multi_plane_image()) {
auto raw_image_memory = image_data->GetRawImageMemory()->GetMemory();
auto descriptor = image_data->multi_plane_descriptor();
if (descriptor.image_format() !=
render_tree::kMultiPlaneImageFormatYUV3PlaneBT601FullRange) {
return ::testing::AssertionFailure()
<< "'image_format()' should be "
<< render_tree::kMultiPlaneImageFormatYUV3PlaneBT601FullRange
<< " but is " << descriptor.image_format();
}
if (descriptor.num_planes() != 3) {
return ::testing::AssertionFailure()
<< "'num_planes()' should be 3 but is "
<< descriptor.image_format();
}
auto result = CheckSameValue(
raw_image_memory + descriptor.GetPlaneOffset(0),
descriptor.GetPlaneDescriptor(0).size.width(),
descriptor.GetPlaneDescriptor(0).size.height(),
descriptor.GetPlaneDescriptor(0).pitch_in_bytes, y_color);
if (!result) {
return result;
}
result = CheckSameValue(raw_image_memory + descriptor.GetPlaneOffset(1),
descriptor.GetPlaneDescriptor(1).size.width(),
descriptor.GetPlaneDescriptor(1).size.height(),
descriptor.GetPlaneDescriptor(1).pitch_in_bytes,
u_color);
if (!result) {
return result;
}
return CheckSameValue(raw_image_memory + descriptor.GetPlaneOffset(2),
descriptor.GetPlaneDescriptor(2).size.width(),
descriptor.GetPlaneDescriptor(2).size.height(),
descriptor.GetPlaneDescriptor(2).pitch_in_bytes,
v_color);
}
math::Size size = image_data->GetSize();
uint8* pixels = image_data->GetImageData()->GetMemory();
return CheckSameColor(pixels, size.width(), size.height(), test_color);
}
} // namespace
// TODO: Test special images like the image has gAMA chunk information,
// pngs with 16 bit depth, and large pngs.
TEST(ImageDecoderTest, DecodeImageWithContentLength0) {
MockImageDecoder image_decoder;
image_decoder.ExpectCallWithError(
std::string("No content returned, but expected some."));
const char kImageWithContentLength0Headers[] = {
"HTTP/1.1 200 OK\0"
"Content-Length: 0\0"
"Content-Type: image/jpeg\0"
"Expires: Wed, 20 Apr 2016 19:33:44 GMT\0"
"Date: Wed, 20 Apr 2016 18:33:44 GMT\0"
"Server: UploadServer\0"
"Accept-Ranges: bytes\0"
"Cache-Control: public, max-age=3600\0"};
scoped_refptr<net::HttpResponseHeaders> headers(new net::HttpResponseHeaders(
std::string(kImageWithContentLength0Headers,
kImageWithContentLength0Headers +
sizeof(kImageWithContentLength0Headers))));
image_decoder.OnResponseStarted(NULL, headers);
image_decoder.Finish();
EXPECT_FALSE(image_decoder.image());
}
TEST(ImageDecoderTest, DecodeNonImageTypeWithContentLength0) {
MockImageDecoder image_decoder;
image_decoder.ExpectCallWithError(std::string(
"No content returned, but expected some. Not an image mime type."));
const char kHTMLWithContentLength0Headers[] = {
"HTTP/1.1 200 OK\0"
"Content-Length: 0\0"
"Content-Type: text/html\0"
"Expires: Wed, 20 Apr 2016 19:33:44 GMT\0"
"Date: Wed, 20 Apr 2016 18:33:44 GMT\0"
"Server: UploadServer\0"
"Accept-Ranges: bytes\0"
"Cache-Control: public, max-age=3600\0"};
scoped_refptr<net::HttpResponseHeaders> headers(new net::HttpResponseHeaders(
std::string(kHTMLWithContentLength0Headers,
kHTMLWithContentLength0Headers +
sizeof(kHTMLWithContentLength0Headers))));
image_decoder.OnResponseStarted(NULL, headers);
image_decoder.Finish();
EXPECT_FALSE(image_decoder.image());
}
TEST(ImageDecoderTest, DecodeNonImageType) {
MockImageDecoder image_decoder;
image_decoder.ExpectCallWithError(std::string("Not an image mime type."));
const char kHTMLHeaders[] = {
"HTTP/1.1 200 OK\0"
"Content-Type: text/html; charset=UTF-8\0"
"Expires: Wed, 20 Apr 2016 19:33:44 GMT\0"
"Date: Wed, 20 Apr 2016 18:33:44 GMT\0"
"Server: gws\0"
"Accept-Ranges: none\0"
"Cache-Control: private, max-age=0\0"};
scoped_refptr<net::HttpResponseHeaders> headers(new net::HttpResponseHeaders(
std::string(kHTMLHeaders, kHTMLHeaders + sizeof(kHTMLHeaders))));
const char kContent[] = {
"<!DOCTYPE html><html><head></head><body></body></html>"};
image_decoder.OnResponseStarted(NULL, headers);
image_decoder.DecodeChunk(kContent, sizeof(kContent));
image_decoder.Finish();
EXPECT_FALSE(image_decoder.image());
}
TEST(ImageDecoderTest, DecodeNoContentType) {
MockImageDecoder image_decoder;
image_decoder.ExpectCallWithError(std::string("Not an image mime type."));
const char kHTMLHeaders[] = {
"HTTP/1.1 200 OK\0"
"Expires: Wed, 20 Apr 2016 19:33:44 GMT\0"
"Date: Wed, 20 Apr 2016 18:33:44 GMT\0"
"Server: gws\0"
"Accept-Ranges: none\0"
"Cache-Control: private, max-age=0\0"};
scoped_refptr<net::HttpResponseHeaders> headers(new net::HttpResponseHeaders(
std::string(kHTMLHeaders, kHTMLHeaders + sizeof(kHTMLHeaders))));
const char kContent[] = {
"<!DOCTYPE html><html><head></head><body></body></html>"};
image_decoder.OnResponseStarted(NULL, headers);
image_decoder.DecodeChunk(kContent, sizeof(kContent));
image_decoder.Finish();
EXPECT_FALSE(image_decoder.image());
}
TEST(ImageDecoderTest, DecodeImageWithNoContent) {
MockImageDecoder image_decoder;
image_decoder.ExpectCallWithError(
std::string("No content returned. Not an image mime type."));
const char kHTMLWithNoContentHeaders[] = {
"HTTP/1.1 204 No Content\0"
"Expires: Tue, 27 Apr 1971 19:44:06 EST\0"
"Content-Length: 0\0"
"Content-Type: text/html; charset=utf-8\0"
"Date: Wed, 20 Apr 2016 18:33:44 GMT\0"
"Server: Ytfe_Worker\0"
"Accept-Ranges: bytes\0"
"Cache-Control: no-cache\0"};
scoped_refptr<net::HttpResponseHeaders> headers(
new net::HttpResponseHeaders(std::string(
kHTMLWithNoContentHeaders,
kHTMLWithNoContentHeaders + sizeof(kHTMLWithNoContentHeaders))));
image_decoder.OnResponseStarted(NULL, headers);
image_decoder.Finish();
EXPECT_FALSE(image_decoder.image());
}
TEST(ImageDecoderTest, DecodeImageWithLessThanHeaderBytes) {
MockImageDecoder image_decoder;
image_decoder.ExpectCallWithError(
std::string("No enough image data for header."));
const char kPartialWebPHeader[] = {"RIFF"};
image_decoder.DecodeChunk(kPartialWebPHeader, sizeof(kPartialWebPHeader));
image_decoder.Finish();
EXPECT_FALSE(image_decoder.image());
}
TEST(ImageDecoderTest, FailedToDecodeImage) {
MockImageDecoder image_decoder;
image_decoder.ExpectCallWithError(
std::string("PNGImageDecoder failed to decode image."));
const char kPartialPNGImage[] = {
"\x89\x50\x4E\x47\x0D\x0A\x1A\x0A\x00\x00\x00\x0D\x49\x48\x44\x52\x00\x00"
"\x02\x6C\x00\x00\x01\x2C"};
image_decoder.DecodeChunk(kPartialPNGImage, sizeof(kPartialPNGImage));
image_decoder.Finish();
EXPECT_FALSE(image_decoder.image());
}
TEST(ImageDecoderTest, UnsupportedImageFormat) {
MockImageDecoder image_decoder;
image_decoder.ExpectCallWithError(std::string("Unsupported image format."));
const char kPartialICOImage[] = {
"\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x01\x00"};
image_decoder.DecodeChunk(kPartialICOImage, sizeof(kPartialICOImage));
image_decoder.Finish();
EXPECT_FALSE(image_decoder.image());
}
// Test that we can properly decode the PNG image.
TEST(ImageDecoderTest, DecodePNGImage) {
MockImageDecoder image_decoder;
image_decoder.ExpectCallWithError(base::nullopt);
std::vector<uint8> image_data =
GetImageData(GetTestImagePath("non_interlaced_png.png"));
image_decoder.DecodeChunk(reinterpret_cast<char*>(&image_data[0]),
image_data.size());
image_decoder.Finish();
// All pixels in the PNG image should have premultiplied alpha RGBA
// values of (127, 0, 0, 127).
uint8 r = 127;
uint8 g = 0;
uint8 b = 0;
uint8 a = 127;
uint32 expected_color =
static_cast<uint32>((r << 24) | (g << 16) | (b << 8) | a);
StaticImage* static_image =
base::polymorphic_downcast<StaticImage*>(image_decoder.image().get());
ASSERT_TRUE(static_image);
render_tree::ImageStub* data =
base::polymorphic_downcast<render_tree::ImageStub*>(
static_image->image().get());
math::Size size = data->GetSize();
uint8* pixels = data->GetImageData()->GetMemory();
EXPECT_TRUE(
CheckSameColor(pixels, size.width(), size.height(), expected_color));
}
// Test that we can properly decode the PNG image with multiple chunks.
TEST(ImageDecoderTest, DecodePNGImageWithMultipleChunks) {
MockImageDecoder image_decoder;
image_decoder.ExpectCallWithError(base::nullopt);
std::vector<uint8> image_data =
GetImageData(GetTestImagePath("non_interlaced_png.png"));
image_decoder.DecodeChunk(reinterpret_cast<char*>(&image_data[0]), 4);
image_decoder.DecodeChunk(reinterpret_cast<char*>(&image_data[4]), 2);
image_decoder.DecodeChunk(reinterpret_cast<char*>(&image_data[6]), 94);
image_decoder.DecodeChunk(reinterpret_cast<char*>(&image_data[100]), 100);
image_decoder.DecodeChunk(reinterpret_cast<char*>(&image_data[200]),
image_data.size() - 200);
image_decoder.Finish();
// All pixels in the PNG image should have premultiplied alpha RGBA
// values of (127, 0, 0, 127).
uint8 r = 127;
uint8 g = 0;
uint8 b = 0;
uint8 a = 127;
uint32 expected_color =
static_cast<uint32>((r << 24) | (g << 16) | (b << 8) | a);
StaticImage* static_image =
base::polymorphic_downcast<StaticImage*>(image_decoder.image().get());
ASSERT_TRUE(static_image);
render_tree::ImageStub* data =
base::polymorphic_downcast<render_tree::ImageStub*>(
static_image->image().get());
math::Size size = data->GetSize();
uint8* pixels = data->GetImageData()->GetMemory();
EXPECT_TRUE(
CheckSameColor(pixels, size.width(), size.height(), expected_color));
}
// Test that we can properly decode the the interlaced PNG.
TEST(ImageDecoderTest, DecodeInterlacedPNGImage) {
MockImageDecoder image_decoder;
image_decoder.ExpectCallWithError(base::nullopt);
std::vector<uint8> image_data =
GetImageData(GetTestImagePath("interlaced_png.png"));
image_decoder.DecodeChunk(reinterpret_cast<char*>(&image_data[0]),
image_data.size());
image_decoder.Finish();
// All pixels in the PNG image should have premultiplied alpha RGBA
// values of (128, 88, 0, 255).
uint8 r = 128;
uint8 g = 88;
uint8 b = 0;
uint8 a = 255;
uint32 expected_color =
static_cast<uint32>((r << 24) | (g << 16) | (b << 8) | a);
StaticImage* static_image =
base::polymorphic_downcast<StaticImage*>(image_decoder.image().get());
ASSERT_TRUE(static_image);
render_tree::ImageStub* data =
base::polymorphic_downcast<render_tree::ImageStub*>(
static_image->image().get());
math::Size size = data->GetSize();
uint8* pixels = data->GetImageData()->GetMemory();
EXPECT_TRUE(
CheckSameColor(pixels, size.width(), size.height(), expected_color));
}
// Test that we can properly decode the interlaced PNG with multiple chunks.
TEST(ImageDecoderTest, DecodeInterlacedPNGImageWithMultipleChunks) {
MockImageDecoder image_decoder;
image_decoder.ExpectCallWithError(base::nullopt);
std::vector<uint8> image_data =
GetImageData(GetTestImagePath("interlaced_png.png"));
image_decoder.DecodeChunk(reinterpret_cast<char*>(&image_data[0]), 4);
image_decoder.DecodeChunk(reinterpret_cast<char*>(&image_data[4]), 2);
image_decoder.DecodeChunk(reinterpret_cast<char*>(&image_data[6]), 94);
image_decoder.DecodeChunk(reinterpret_cast<char*>(&image_data[100]), 100);
image_decoder.DecodeChunk(reinterpret_cast<char*>(&image_data[200]),
image_data.size() - 200);
image_decoder.Finish();
// All pixels in the PNG image should have premultiplied alpha RGBA
// values of (128, 88, 0, 255).
uint8 r = 128;
uint8 g = 88;
uint8 b = 0;
uint8 a = 255;
uint32 expected_color =
static_cast<uint32>((r << 24) | (g << 16) | (b << 8) | a);
StaticImage* static_image =
base::polymorphic_downcast<StaticImage*>(image_decoder.image().get());
ASSERT_TRUE(static_image);
render_tree::ImageStub* data =
base::polymorphic_downcast<render_tree::ImageStub*>(
static_image->image().get());
math::Size size = data->GetSize();
uint8* pixels = data->GetImageData()->GetMemory();
EXPECT_TRUE(
CheckSameColor(pixels, size.width(), size.height(), expected_color));
}
// Test that we can properly decode the JPEG image.
TEST(ImageDecoderTest, DecodeJPEGImage) {
MockImageDecoder image_decoder;
image_decoder.ExpectCallWithError(base::nullopt);
std::vector<uint8> image_data =
GetImageData(GetTestImagePath("baseline_jpeg.jpg"));
image_decoder.DecodeChunk(reinterpret_cast<char*>(&image_data[0]),
image_data.size());
image_decoder.Finish();
StaticImage* static_image =
base::polymorphic_downcast<StaticImage*>(image_decoder.image().get());
ASSERT_TRUE(static_image);
render_tree::ImageStub* data =
base::polymorphic_downcast<render_tree::ImageStub*>(
static_image->image().get());
// All pixels in the JPEG image should have RGBA values of (128, 88, 0, 255),
// or YUV values of (90, 77, 155).
uint8 r = 128, g = 88, b = 0, a = 255;
uint32 expected_color =
static_cast<uint32>((r << 24) | (g << 16) | (b << 8) | a);
EXPECT_TRUE(CheckUniformColoredImage(data, expected_color, 90, 77, 155));
}
// Test that we can properly decode the JPEG image with multiple chunks.
TEST(ImageDecoderTest, DecodeJPEGImageWithMultipleChunks) {
MockImageDecoder image_decoder;
image_decoder.ExpectCallWithError(base::nullopt);
std::vector<uint8> image_data =
GetImageData(GetTestImagePath("baseline_jpeg.jpg"));
image_decoder.DecodeChunk(reinterpret_cast<char*>(&image_data[0]), 2);
image_decoder.DecodeChunk(reinterpret_cast<char*>(&image_data[2]), 4);
image_decoder.DecodeChunk(reinterpret_cast<char*>(&image_data[6]), 94);
image_decoder.DecodeChunk(reinterpret_cast<char*>(&image_data[100]), 200);
image_decoder.DecodeChunk(reinterpret_cast<char*>(&image_data[300]),
image_data.size() - 300);
image_decoder.Finish();
StaticImage* static_image =
base::polymorphic_downcast<StaticImage*>(image_decoder.image().get());
ASSERT_TRUE(static_image);
render_tree::ImageStub* data =
base::polymorphic_downcast<render_tree::ImageStub*>(
static_image->image().get());
// All pixels in the JPEG image should have RGBA values of (128, 88, 0, 255),
// or YUV values of (90, 77, 155).
uint8 r = 128, g = 88, b = 0, a = 255;
uint32 expected_color =
static_cast<uint32>((r << 24) | (g << 16) | (b << 8) | a);
EXPECT_TRUE(CheckUniformColoredImage(data, expected_color, 90, 77, 155));
}
// Test that we can properly decode the progressive JPEG image.
TEST(ImageDecoderTest, DecodeProgressiveJPEGImage) {
MockImageDecoder image_decoder;
image_decoder.ExpectCallWithError(base::nullopt);
std::vector<uint8> image_data =
GetImageData(GetTestImagePath("progressive_jpeg.jpg"));
image_decoder.DecodeChunk(reinterpret_cast<char*>(&image_data[0]),
image_data.size());
image_decoder.Finish();
StaticImage* static_image =
base::polymorphic_downcast<StaticImage*>(image_decoder.image().get());
ASSERT_TRUE(static_image);
render_tree::ImageStub* data =
base::polymorphic_downcast<render_tree::ImageStub*>(
static_image->image().get());
// All pixels in the JPEG image should have RGBA values of (64, 32, 17, 255),
// or YUV values of (40, 115, 145).
uint8 r = 64, g = 32, b = 17, a = 255;
uint32 expected_rgba =
static_cast<uint32>((r << 24) | (g << 16) | (b << 8) | a);
EXPECT_TRUE(CheckUniformColoredImage(data, expected_rgba, 40, 115, 145));
}
// Test that we can properly decode the progressive JPEG with multiple chunks.
TEST(ImageDecoderTest, DecodeProgressiveJPEGImageWithMultipleChunks) {
MockImageDecoder image_decoder;
image_decoder.ExpectCallWithError(base::nullopt);
std::vector<uint8> image_data =
GetImageData(GetTestImagePath("progressive_jpeg.jpg"));
image_decoder.DecodeChunk(reinterpret_cast<char*>(&image_data[0]), 2);
image_decoder.DecodeChunk(reinterpret_cast<char*>(&image_data[2]), 4);
image_decoder.DecodeChunk(reinterpret_cast<char*>(&image_data[6]), 94);
image_decoder.DecodeChunk(reinterpret_cast<char*>(&image_data[100]), 200);
image_decoder.DecodeChunk(reinterpret_cast<char*>(&image_data[300]),
image_data.size() - 300);
image_decoder.Finish();
StaticImage* static_image =
base::polymorphic_downcast<StaticImage*>(image_decoder.image().get());
ASSERT_TRUE(static_image);
render_tree::ImageStub* data =
base::polymorphic_downcast<render_tree::ImageStub*>(
static_image->image().get());
// All pixels in the JPEG image should have RGBA values of (64, 32, 17, 255),
// or YUV values of (40, 115, 145).
uint8 r = 64, g = 32, b = 17, a = 255;
uint32 expected_rgba =
static_cast<uint32>((r << 24) | (g << 16) | (b << 8) | a);
EXPECT_TRUE(CheckUniformColoredImage(data, expected_rgba, 40, 115, 145));
}
// Test that we can properly decode the progressive JPEG image while forcing the
// output to be single plane.
TEST(ImageDecoderTest, DecodeProgressiveJPEGImageToSinglePlane) {
render_tree::ResourceProviderStub resource_provider;
const bool kAllowImageDecodingToMultiPlane = false;
JPEGImageDecoder jpeg_image_decoder(&resource_provider,
kAllowImageDecodingToMultiPlane);
std::vector<uint8> image_data =
GetImageData(GetTestImagePath("progressive_jpeg.jpg"));
jpeg_image_decoder.DecodeChunk(image_data.data(), image_data.size());
auto image = jpeg_image_decoder.FinishAndMaybeReturnImage();
StaticImage* static_image =
base::polymorphic_downcast<StaticImage*>(image.get());
ASSERT_TRUE(static_image);
render_tree::ImageStub* data =
base::polymorphic_downcast<render_tree::ImageStub*>(
static_image->image().get());
ASSERT_TRUE(!data->is_multi_plane_image());
// All pixels in the JPEG image should have RGBA values of (64, 32, 17, 255),
// or YUV values of (44, 115, 145).
uint8 r = 64, g = 32, b = 17, a = 255;
uint32 expected_rgba =
static_cast<uint32>((r << 24) | (g << 16) | (b << 8) | a);
EXPECT_TRUE(CheckUniformColoredImage(data, expected_rgba, 44, 115, 145));
}
// Test that we can properly decode the progressive JPEG with multiple chunks
// while forcing the output to be single plane.
TEST(ImageDecoderTest,
DecodeProgressiveJPEGImageWithMultipleChunksToSinglePlane) {
render_tree::ResourceProviderStub resource_provider;
const bool kAllowImageDecodingToMultiPlane = false;
JPEGImageDecoder jpeg_image_decoder(&resource_provider,
kAllowImageDecodingToMultiPlane);
std::vector<uint8> image_data =
GetImageData(GetTestImagePath("progressive_jpeg.jpg"));
jpeg_image_decoder.DecodeChunk(image_data.data(), 2);
jpeg_image_decoder.DecodeChunk(image_data.data() + 2, 4);
jpeg_image_decoder.DecodeChunk(image_data.data() + 6, 94);
jpeg_image_decoder.DecodeChunk(image_data.data() + 100, 200);
jpeg_image_decoder.DecodeChunk(image_data.data() + 300,
image_data.size() - 300);
auto image = jpeg_image_decoder.FinishAndMaybeReturnImage();
StaticImage* static_image =
base::polymorphic_downcast<StaticImage*>(image.get());
ASSERT_TRUE(static_image);
render_tree::ImageStub* data =
base::polymorphic_downcast<render_tree::ImageStub*>(
static_image->image().get());
ASSERT_TRUE(!data->is_multi_plane_image());
// All pixels in the JPEG image should have RGBA values of (64, 32, 17, 255),
// or YUV values of (44, 115, 145).
uint8 r = 64, g = 32, b = 17, a = 255;
uint32 expected_rgba =
static_cast<uint32>((r << 24) | (g << 16) | (b << 8) | a);
EXPECT_TRUE(CheckUniformColoredImage(data, expected_rgba, 44, 115, 145));
}
// Test that we can properly decode the WEBP image.
TEST(ImageDecoderTest, DecodeWEBPImage) {
MockImageDecoder image_decoder;
image_decoder.ExpectCallWithError(base::nullopt);
std::vector<uint8> image_data =
GetImageData(GetTestImagePath("webp_image.webp"));
image_decoder.DecodeChunk(reinterpret_cast<char*>(&image_data[0]),
image_data.size());
image_decoder.Finish();
// All pixels in the WEBP image should have RGBA values of (16, 8, 70, 70).
uint8 r = 16;
uint8 g = 8;
uint8 b = 70;
uint8 a = 70;
uint32 expected_color =
static_cast<uint32>((r << 24) | (g << 16) | (b << 8) | a);
StaticImage* static_image =
base::polymorphic_downcast<StaticImage*>(image_decoder.image().get());
ASSERT_TRUE(static_image);
render_tree::ImageStub* data =
base::polymorphic_downcast<render_tree::ImageStub*>(
static_image->image().get());
math::Size size = data->GetSize();
uint8* pixels = data->GetImageData()->GetMemory();
EXPECT_TRUE(
CheckSameColor(pixels, size.width(), size.height(), expected_color));
}
// Test that we can properly decode the WEBP image with multiple chunks.
TEST(ImageDecoderTest, DecodeWEBPImageWithMultipleChunks) {
MockImageDecoder image_decoder;
image_decoder.ExpectCallWithError(base::nullopt);
std::vector<uint8> image_data =
GetImageData(GetTestImagePath("webp_image.webp"));
image_decoder.DecodeChunk(reinterpret_cast<char*>(&image_data[0]), 2);
image_decoder.DecodeChunk(reinterpret_cast<char*>(&image_data[2]), 4);
image_decoder.DecodeChunk(reinterpret_cast<char*>(&image_data[6]), 94);
image_decoder.DecodeChunk(reinterpret_cast<char*>(&image_data[100]),
image_data.size() - 100);
image_decoder.Finish();
// All pixels in the WEBP image should have RGBA values of (16, 8, 70, 70).
uint8 r = 16;
uint8 g = 8;
uint8 b = 70;
uint8 a = 70;
uint32 expected_color =
static_cast<uint32>((r << 24) | (g << 16) | (b << 8) | a);
StaticImage* static_image =
base::polymorphic_downcast<StaticImage*>(image_decoder.image().get());
ASSERT_TRUE(static_image);
render_tree::ImageStub* data =
base::polymorphic_downcast<render_tree::ImageStub*>(
static_image->image().get());
math::Size size = data->GetSize();
uint8* pixels = data->GetImageData()->GetMemory();
EXPECT_TRUE(
CheckSameColor(pixels, size.width(), size.height(), expected_color));
}
// Test that we can properly decode animated WEBP image.
TEST(ImageDecoderTest, DecodeAnimatedWEBPImage) {
base::Thread thread("AnimatedWebP");
thread.Start();
MockImageDecoder image_decoder;
image_decoder.ExpectCallWithError(base::nullopt);
std::vector<uint8> image_data =
GetImageData(GetTestImagePath("vsauce_sm.webp"));
image_decoder.DecodeChunk(reinterpret_cast<char*>(&image_data[0]),
image_data.size());
image_decoder.Finish();
scoped_refptr<AnimatedWebPImage> animated_webp_image =
base::polymorphic_downcast<AnimatedWebPImage*>(
image_decoder.image().get());
ASSERT_TRUE(animated_webp_image);
animated_webp_image->Play(thread.task_runner());
animated_webp_image->Stop();
// The image should contain the whole undecoded data from the file.
EXPECT_EQ(4261474u, animated_webp_image->GetEstimatedSizeInBytes());
EXPECT_EQ(math::Size(480, 270), animated_webp_image->GetSize());
EXPECT_TRUE(animated_webp_image->IsOpaque());
}
// Test that we can properly decode animated WEBP image in multiple chunks.
TEST(ImageDecoderTest, DecodeAnimatedWEBPImageWithMultipleChunks) {
base::Thread thread("AnimatedWebP");
thread.Start();
MockImageDecoder image_decoder;
image_decoder.ExpectCallWithError(base::nullopt);
std::vector<uint8> image_data =
GetImageData(GetTestImagePath("vsauce_sm.webp"));
image_decoder.DecodeChunk(reinterpret_cast<char*>(&image_data[0]), 2);
image_decoder.DecodeChunk(reinterpret_cast<char*>(&image_data[2]), 4);
image_decoder.DecodeChunk(reinterpret_cast<char*>(&image_data[6]), 94);
image_decoder.DecodeChunk(reinterpret_cast<char*>(&image_data[100]),
image_data.size() - 100);
image_decoder.Finish();
scoped_refptr<AnimatedWebPImage> animated_webp_image =
base::polymorphic_downcast<AnimatedWebPImage*>(
image_decoder.image().get());
ASSERT_TRUE(animated_webp_image);
animated_webp_image->Play(thread.task_runner());
animated_webp_image->Stop();
// The image should contain the whole undecoded data from the file.
EXPECT_EQ(4261474u, animated_webp_image->GetEstimatedSizeInBytes());
EXPECT_EQ(math::Size(480, 270), animated_webp_image->GetSize());
EXPECT_TRUE(animated_webp_image->IsOpaque());
}
} // namespace image
} // namespace loader
} // namespace cobalt