| // 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 test"); |
| 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 test"); |
| 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 |