blob: dafc54b7ece4ed8c8817dcb290281af502e503a3 [file] [log] [blame]
/*
* Copyright 2015 Google Inc. 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 <string>
#include <vector>
#include "base/bind.h"
#include "base/file_path.h"
#include "base/file_util.h"
#include "base/path_service.h"
#include "cobalt/base/cobalt_paths.h"
#include "cobalt/base/polymorphic_downcast.h"
#include "cobalt/render_tree/resource_provider_stub.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<render_tree::Image>& value) {
image = value;
}
MOCK_METHOD1(FailureCallback, void(const std::string& message));
MOCK_METHOD1(ErrorCallback, void(const std::string& message));
scoped_refptr<render_tree::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<render_tree::Image> Image();
void ExpectCallWithFailure(const std::string& message);
void ExpectCallWithError(const std::string& message);
protected:
::testing::StrictMock<MockImageDecoderCallback> image_decoder_callback_;
render_tree::ResourceProviderStub resource_provider_;
scoped_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::FailureCallback,
base::Unretained(&image_decoder_callback_)),
base::Bind(&MockImageDecoderCallback::ErrorCallback,
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<render_tree::Image> MockImageDecoder::Image() {
return image_decoder_callback_.image;
}
void MockImageDecoder::ExpectCallWithFailure(const std::string& message) {
EXPECT_CALL(image_decoder_callback_, FailureCallback(message));
}
void MockImageDecoder::ExpectCallWithError(const std::string& message) {
EXPECT_CALL(image_decoder_callback_, ErrorCallback(message));
}
FilePath GetTestImagePath(const char* file_name) {
FilePath data_directory;
CHECK(PathService::Get(base::DIR_SOURCE_ROOT, &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 FilePath& file_path) {
int64 size;
std::vector<uint8> image_data;
bool success = file_util::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 =
file_util::ReadFile(file_path, reinterpret_cast<char*>(&image_data[0]),
static_cast<int>(size));
CHECK_EQ(num_of_bytes, image_data.size()) << "Could not read '"
<< file_path.value() << "'.";
return image_data;
}
// Check if pixels are the same as |test_color|.
bool 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 false;
}
}
return true;
}
} // 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.ExpectCallWithFailure(
"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.ExpectCallWithFailure(
"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.ExpectCallWithFailure("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.ExpectCallWithFailure("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.ExpectCallWithFailure(
"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("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("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("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;
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);
ASSERT_TRUE(image_decoder.Image());
render_tree::ImageStub* data =
base::polymorphic_downcast<render_tree::ImageStub*>(
image_decoder.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;
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);
ASSERT_TRUE(image_decoder.Image());
render_tree::ImageStub* data =
base::polymorphic_downcast<render_tree::ImageStub*>(
image_decoder.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;
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);
ASSERT_TRUE(image_decoder.Image());
render_tree::ImageStub* data =
base::polymorphic_downcast<render_tree::ImageStub*>(
image_decoder.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;
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);
ASSERT_TRUE(image_decoder.Image());
render_tree::ImageStub* data =
base::polymorphic_downcast<render_tree::ImageStub*>(
image_decoder.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;
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();
// All pixels in the JPEG image should have 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);
ASSERT_TRUE(image_decoder.Image());
render_tree::ImageStub* data =
base::polymorphic_downcast<render_tree::ImageStub*>(
image_decoder.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 with multiple chunks.
TEST(ImageDecoderTest, DecodeJPEGImageWithMultipleChunks) {
MockImageDecoder image_decoder;
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();
// All pixels in the JPEG image should have 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);
ASSERT_TRUE(image_decoder.Image());
render_tree::ImageStub* data =
base::polymorphic_downcast<render_tree::ImageStub*>(
image_decoder.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 progressive JPEG image.
TEST(ImageDecoderTest, DecodeProgressiveJPEGImage) {
MockImageDecoder image_decoder;
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();
// All pixels in the JPEG image should have RGBA values of (64, 32, 17, 255).
uint8 r = 64;
uint8 g = 32;
uint8 b = 17;
uint8 a = 255;
uint32 expected_color =
static_cast<uint32>((r << 24) | (g << 16) | (b << 8) | a);
ASSERT_TRUE(image_decoder.Image());
render_tree::ImageStub* data =
base::polymorphic_downcast<render_tree::ImageStub*>(
image_decoder.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 progressive JPEG with multiple chunks.
TEST(ImageDecoderTest, DecodeProgressiveJPEGImageWithMultipleChunks) {
MockImageDecoder image_decoder;
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();
// All pixels in the JPEG image should have RGBA values of (64, 32, 17, 255).
uint8 r = 64;
uint8 g = 32;
uint8 b = 17;
uint8 a = 255;
uint32 expected_color =
static_cast<uint32>((r << 24) | (g << 16) | (b << 8) | a);
ASSERT_TRUE(image_decoder.Image());
render_tree::ImageStub* data =
base::polymorphic_downcast<render_tree::ImageStub*>(
image_decoder.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.
TEST(ImageDecoderTest, DecodeWEBPImage) {
MockImageDecoder image_decoder;
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);
ASSERT_TRUE(image_decoder.Image());
render_tree::ImageStub* data =
base::polymorphic_downcast<render_tree::ImageStub*>(
image_decoder.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;
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);
ASSERT_TRUE(image_decoder.Image());
render_tree::ImageStub* data =
base::polymorphic_downcast<render_tree::ImageStub*>(
image_decoder.Image().get());
math::Size size = data->GetSize();
uint8* pixels = data->GetImageData()->GetMemory();
EXPECT_TRUE(
CheckSameColor(pixels, size.width(), size.height(), expected_color));
}
} // namespace image
} // namespace loader
} // namespace cobalt