| /* |
| * Copyright 2010 Google Inc. |
| * |
| * 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. |
| */ |
| // Author: jmaessen@google.com (Jan Maessen) |
| |
| // Unit tests for Image class used in rewriting. |
| |
| #include "net/instaweb/rewriter/public/image.h" |
| |
| #include <algorithm> |
| #include <cstdlib> |
| |
| #include "net/instaweb/rewriter/cached_result.pb.h" |
| #include "net/instaweb/rewriter/image_testing_peer.h" |
| #include "net/instaweb/rewriter/public/image_data_lookup.h" |
| #include "net/instaweb/rewriter/public/image_test_base.h" |
| #include "net/instaweb/rewriter/public/image_url_encoder.h" |
| #include "pagespeed/kernel/base/base64_util.h" |
| #include "pagespeed/kernel/base/basictypes.h" |
| #include "pagespeed/kernel/base/dynamic_annotations.h" // RunningOnValgrind |
| #include "pagespeed/kernel/base/function.h" |
| #include "pagespeed/kernel/base/gtest.h" |
| #include "pagespeed/kernel/base/mock_message_handler.h" |
| #include "pagespeed/kernel/base/mock_timer.h" |
| #include "pagespeed/kernel/base/scoped_ptr.h" |
| #include "pagespeed/kernel/base/statistics.h" |
| #include "pagespeed/kernel/base/statistics_template.h" |
| #include "pagespeed/kernel/base/string.h" |
| #include "pagespeed/kernel/base/string_util.h" |
| #include "pagespeed/kernel/base/thread_system.h" |
| #include "pagespeed/kernel/http/content_type.h" |
| #include "pagespeed/kernel/http/data_url.h" |
| #include "pagespeed/kernel/image/image_util.h" |
| #include "pagespeed/kernel/image/jpeg_optimizer_test_helper.h" |
| #include "pagespeed/kernel/image/jpeg_utils.h" |
| #include "pagespeed/kernel/image/read_image.h" |
| #include "pagespeed/kernel/image/test_utils.h" |
| #include "pagespeed/kernel/util/platform.h" |
| #include "pagespeed/kernel/util/simple_stats.h" |
| |
| using pagespeed_testing::image_compression::GetColorProfileMarker; |
| using pagespeed_testing::image_compression::GetExifDataMarker; |
| using pagespeed_testing::image_compression::GetJpegNumComponentsAndSamplingFactors; |
| using pagespeed_testing::image_compression::GetNumScansInJpeg; |
| using pagespeed_testing::image_compression::IsJpegSegmentPresent; |
| using pagespeed::image_compression::JpegUtils; |
| using pagespeed::image_compression::kMessagePatternAnimatedGif; |
| using pagespeed::image_compression::kMessagePatternPixelFormat; |
| using pagespeed::image_compression::kMessagePatternStats; |
| using pagespeed::image_compression::kMessagePatternUnexpectedEOF; |
| using pagespeed::image_compression::kMessagePatternWritingToWebp; |
| using pagespeed::image_compression::PixelFormat; |
| using pagespeed::image_compression::WEBP_NONE; |
| using pagespeed::image_compression::WEBP_LOSSY; |
| using pagespeed::image_compression::WEBP_LOSSLESS; |
| using pagespeed::image_compression::WEBP_ANIMATED; |
| |
| namespace net_instaweb { |
| namespace { |
| |
| const char kProgressiveHeader[] = "\xFF\xC2"; |
| const int kProgressiveHeaderStartIndex = 158; |
| const char kMessagePatternDataTruncated[] = "*data truncated*"; |
| const char kMessagePatternFailedToCreateWebp[] = "*Failed to create webp*"; |
| const char kMessagePatternFailedToEncodeWebp[] = "*Could not encode webp data*"; |
| const char kMessagePatternNoDimension[] = "*Couldn't find * dimensions*"; |
| const char kMessagePatternTimedOut[] = "*conversion timed out*"; |
| const char kMessagePatternFailedToDecoode[] = "*failed to decode the image*"; |
| |
| class ConversionVarChecker { |
| public: |
| explicit ConversionVarChecker(Image::CompressionOptions* options) |
| : thread_system_(Platform::CreateThreadSystem()), |
| simple_stats_(thread_system_.get()) { |
| webp_conversion_variables_.Get( |
| Image::ConversionVariables::FROM_GIF)->timeout_count = |
| simple_stats_.AddVariable("gif_webp_timeout"); |
| webp_conversion_variables_.Get( |
| Image::ConversionVariables::FROM_GIF)->success_ms = |
| simple_stats_.AddHistogram("gif_webp_success"); |
| webp_conversion_variables_.Get( |
| Image::ConversionVariables::FROM_GIF)->failure_ms = |
| simple_stats_.AddHistogram("gif_webp_failure"); |
| |
| webp_conversion_variables_.Get( |
| Image::ConversionVariables::FROM_PNG)->timeout_count = |
| simple_stats_.AddVariable("png_webp_timeout"); |
| webp_conversion_variables_.Get( |
| Image::ConversionVariables::FROM_PNG)->success_ms = |
| simple_stats_.AddHistogram("png_webp_success"); |
| webp_conversion_variables_.Get( |
| Image::ConversionVariables::FROM_PNG)->failure_ms = |
| simple_stats_.AddHistogram("png_webp_failure"); |
| |
| webp_conversion_variables_.Get( |
| Image::ConversionVariables::FROM_JPEG)->timeout_count = |
| simple_stats_.AddVariable("jpeg_webp_timeout"); |
| webp_conversion_variables_.Get( |
| Image::ConversionVariables::FROM_JPEG)->success_ms = |
| simple_stats_.AddHistogram("jpeg_webp_success"); |
| webp_conversion_variables_.Get( |
| Image::ConversionVariables::FROM_JPEG)->failure_ms = |
| simple_stats_.AddHistogram("jpeg_webp_failure"); |
| |
| webp_conversion_variables_.Get( |
| Image::ConversionVariables::NONOPAQUE)->timeout_count = |
| simple_stats_.AddVariable("webp_alpha_timeout"); |
| webp_conversion_variables_.Get( |
| Image::ConversionVariables::NONOPAQUE)->success_ms = |
| simple_stats_.AddHistogram("webp_alpha_success"); |
| webp_conversion_variables_.Get( |
| Image::ConversionVariables::NONOPAQUE)->failure_ms = |
| simple_stats_.AddHistogram("webp_alpha_failure"); |
| |
| webp_conversion_variables_.Get( |
| Image::ConversionVariables::OPAQUE)->timeout_count = |
| simple_stats_.AddVariable("webp_opaque_timeout"); |
| webp_conversion_variables_.Get( |
| Image::ConversionVariables::OPAQUE)->success_ms = |
| simple_stats_.AddHistogram("webp_opaque_success"); |
| webp_conversion_variables_.Get( |
| Image::ConversionVariables::OPAQUE)->failure_ms = |
| simple_stats_.AddHistogram("webp_opaque_failure"); |
| |
| webp_conversion_variables_.Get( |
| Image::ConversionVariables::FROM_GIF_ANIMATED)->timeout_count = |
| simple_stats_.AddVariable("gif_webp_animated_timeout"); |
| webp_conversion_variables_.Get( |
| Image::ConversionVariables::FROM_GIF_ANIMATED)->success_ms = |
| simple_stats_.AddHistogram("gif_webp_animated_success"); |
| webp_conversion_variables_.Get( |
| Image::ConversionVariables::FROM_GIF_ANIMATED)->failure_ms = |
| simple_stats_.AddHistogram("gif_webp_animated_failure"); |
| |
| options->webp_conversion_variables = &webp_conversion_variables_; |
| } |
| |
| void Test(int gif_webp_timeout, |
| int gif_webp_success, |
| int gif_webp_failure, |
| |
| int png_webp_timeout, |
| int png_webp_success, |
| int png_webp_failure, |
| |
| int jpeg_webp_timeout, |
| int jpeg_webp_success, |
| int jpeg_webp_failure, |
| |
| int gif_webp_animated_timeout, |
| int gif_webp_animated_success, |
| int gif_webp_animated_failure, |
| |
| bool opaque) { |
| EXPECT_EQ(gif_webp_timeout, |
| webp_conversion_variables_.Get( |
| Image::ConversionVariables::FROM_GIF)-> |
| timeout_count->Get()); |
| EXPECT_EQ(gif_webp_success, |
| webp_conversion_variables_.Get( |
| Image::ConversionVariables::FROM_GIF)-> |
| success_ms->Count()); |
| EXPECT_EQ(gif_webp_failure, |
| webp_conversion_variables_.Get( |
| Image::ConversionVariables::FROM_GIF)-> |
| failure_ms->Count()); |
| |
| EXPECT_EQ(png_webp_timeout, |
| webp_conversion_variables_.Get( |
| Image::ConversionVariables::FROM_PNG)-> |
| timeout_count->Get()); |
| EXPECT_EQ(png_webp_success, |
| webp_conversion_variables_.Get( |
| Image::ConversionVariables::FROM_PNG)-> |
| success_ms->Count()); |
| EXPECT_EQ(png_webp_failure, |
| webp_conversion_variables_.Get( |
| Image::ConversionVariables::FROM_PNG)-> |
| failure_ms->Count()); |
| |
| EXPECT_EQ(jpeg_webp_timeout, |
| webp_conversion_variables_.Get( |
| Image::ConversionVariables::FROM_JPEG)-> |
| timeout_count->Get()); |
| EXPECT_EQ(jpeg_webp_success, |
| webp_conversion_variables_.Get( |
| Image::ConversionVariables::FROM_JPEG)-> |
| success_ms->Count()); |
| EXPECT_EQ(jpeg_webp_failure, |
| webp_conversion_variables_.Get( |
| Image::ConversionVariables::FROM_JPEG)-> |
| failure_ms->Count()); |
| |
| EXPECT_EQ(gif_webp_animated_timeout, |
| webp_conversion_variables_.Get( |
| Image::ConversionVariables::FROM_GIF_ANIMATED)-> |
| timeout_count->Get()); |
| EXPECT_EQ(gif_webp_animated_success, |
| webp_conversion_variables_.Get( |
| Image::ConversionVariables::FROM_GIF_ANIMATED)-> |
| success_ms->Count()); |
| EXPECT_EQ(gif_webp_animated_failure, |
| webp_conversion_variables_.Get( |
| Image::ConversionVariables::FROM_GIF_ANIMATED)-> |
| failure_ms->Count()); |
| |
| int total_timeout = |
| gif_webp_timeout + |
| png_webp_timeout + |
| jpeg_webp_timeout + |
| gif_webp_animated_timeout; |
| int total_success = |
| gif_webp_success + |
| png_webp_success + |
| jpeg_webp_success + |
| gif_webp_animated_success; |
| int total_failure = |
| gif_webp_failure + |
| png_webp_failure + |
| jpeg_webp_failure + |
| gif_webp_animated_failure; |
| |
| Image::ConversionBySourceVariable* webp_transparency = |
| webp_conversion_variables_.Get( |
| (opaque ? |
| Image::ConversionVariables::OPAQUE : |
| Image::ConversionVariables::NONOPAQUE)); |
| |
| EXPECT_EQ(total_timeout, |
| webp_transparency->timeout_count->Get()); |
| EXPECT_EQ(total_success, |
| webp_transparency->success_ms->Count()); |
| EXPECT_EQ(total_failure, |
| webp_transparency->failure_ms->Count()); |
| } |
| |
| private: |
| scoped_ptr<ThreadSystem> thread_system_; |
| SimpleStats simple_stats_; |
| Image::ConversionVariables webp_conversion_variables_; |
| }; |
| |
| } // namespace |
| |
| class ImageTest : public ImageTestBase { |
| public: |
| ImageTest() : |
| options_(new Image::CompressionOptions()) { |
| } |
| |
| protected: |
| virtual void SetUp() { |
| message_handler_.AddPatternToSkipPrinting(kMessagePatternAnimatedGif); |
| message_handler_.AddPatternToSkipPrinting(kMessagePatternDataTruncated); |
| message_handler_.AddPatternToSkipPrinting( |
| kMessagePatternFailedToCreateWebp); |
| message_handler_.AddPatternToSkipPrinting(kMessagePatternFailedToDecoode); |
| message_handler_.AddPatternToSkipPrinting( |
| kMessagePatternFailedToEncodeWebp); |
| message_handler_.AddPatternToSkipPrinting(kMessagePatternNoDimension); |
| message_handler_.AddPatternToSkipPrinting(kMessagePatternPixelFormat); |
| message_handler_.AddPatternToSkipPrinting(kMessagePatternStats); |
| message_handler_.AddPatternToSkipPrinting(kMessagePatternTimedOut); |
| message_handler_.AddPatternToSkipPrinting(kMessagePatternUnexpectedEOF); |
| message_handler_.AddPatternToSkipPrinting(kMessagePatternWritingToWebp); |
| } |
| |
| GoogleString* GetOutputContents(Image* image) { |
| return &(image->output_contents_); |
| } |
| |
| void WriteToBuffer(const char* contents, GoogleString* str) { |
| *str = contents; |
| } |
| |
| void ExpectEmptyOutput(Image* image) { |
| EXPECT_FALSE(image->output_valid_); |
| EXPECT_TRUE(image->output_contents_.empty()); |
| } |
| |
| void ExpectContentType(ImageType image_type, Image* image) { |
| EXPECT_EQ(image_type, image->image_type_); |
| } |
| |
| void ExpectDimensions(ImageType image_type, int size, |
| int expected_width, int expected_height, |
| Image *image) { |
| EXPECT_EQ(size, image->input_size()); |
| EXPECT_EQ(image_type, image->image_type()); |
| ImageDim image_dim; |
| image_dim.Clear(); |
| image->Dimensions(&image_dim); |
| EXPECT_TRUE(ImageUrlEncoder::HasValidDimensions(image_dim)); |
| EXPECT_EQ(expected_width, image_dim.width()); |
| EXPECT_EQ(expected_height, image_dim.height()); |
| EXPECT_EQ(StringPrintf("%dx%dxZZ", image_dim.width(), image_dim.height()), |
| EncodeUrlAndDimensions("ZZ", image_dim)); |
| } |
| |
| void CheckInvalid(const GoogleString& name, const GoogleString& contents, |
| ImageType input_type, ImageType output_type, |
| bool progressive) { |
| ImagePtr image(ImageFromString(output_type, name, contents, progressive)); |
| EXPECT_EQ(contents.size(), image->input_size()); |
| EXPECT_EQ(input_type, image->image_type()); |
| ImageDim image_dim; |
| image_dim.Clear(); |
| image->Dimensions(&image_dim); |
| EXPECT_FALSE(ImageUrlEncoder::HasValidDimension(image_dim)); |
| EXPECT_FALSE(image_dim.has_width()); |
| EXPECT_FALSE(image_dim.has_height()); |
| EXPECT_EQ(contents.size(), image->output_size()); |
| EXPECT_EQ("xZZ", EncodeUrlAndDimensions("ZZ", image_dim)); |
| } |
| |
| bool CheckImageFromFile(const char* filename, |
| ImageType input_type, |
| ImageType output_type, |
| int min_bytes_to_type, |
| int min_bytes_to_dimensions, |
| int width, int height, |
| int size, bool optimizable) { |
| return CheckImageFromFile(filename, input_type, output_type, output_type, |
| input_type, min_bytes_to_type, |
| min_bytes_to_dimensions, width, height, size, |
| optimizable); |
| } |
| |
| bool CheckImageFromFile(const char* filename, |
| ImageType input_type, |
| ImageType intended_output_type, |
| ImageType actual_output_type, |
| ImageType type_for_truncated_image, |
| int min_bytes_to_type, |
| int min_bytes_to_dimensions, |
| int width, int height, |
| int size, bool optimizable) { |
| // Set options to convert to intended_output_type, but to allow for |
| // negative tests, don't clear any other options. |
| if (intended_output_type == IMAGE_WEBP) { |
| options_->preferred_webp = WEBP_LOSSY; |
| } else if (intended_output_type == IMAGE_WEBP_LOSSLESS_OR_ALPHA) { |
| options_->preferred_webp = WEBP_LOSSLESS; |
| } |
| switch (intended_output_type) { |
| case IMAGE_WEBP: |
| case IMAGE_WEBP_LOSSLESS_OR_ALPHA: |
| options_->convert_jpeg_to_webp = true; |
| FALLTHROUGH_INTENDED; |
| case IMAGE_JPEG: |
| options_->convert_png_to_jpeg = true; |
| FALLTHROUGH_INTENDED; |
| case IMAGE_PNG: |
| options_->convert_gif_to_png = true; |
| break; |
| default: |
| break; |
| } |
| |
| bool progressive = options_->progressive_jpeg; |
| int jpeg_quality = options_->jpeg_quality; |
| GoogleString contents; |
| ImagePtr image(ReadFromFileWithOptions( |
| filename, &contents, options_.release())); |
| ExpectDimensions(input_type, size, width, height, image.get()); |
| if (optimizable) { |
| EXPECT_GT(size, image->output_size()); |
| ExpectDimensions(actual_output_type, size, width, height, image.get()); |
| } else { |
| EXPECT_EQ(size, image->output_size()); |
| ExpectDimensions(input_type, size, width, height, image.get()); |
| } |
| |
| // Construct data url, then decode it and check for match. |
| CachedResult cached; |
| GoogleString data_url; |
| EXPECT_NE(IMAGE_UNKNOWN, image->image_type()); |
| StringPiece image_contents = image->Contents(); |
| |
| progressive &= ImageTestingPeer::ShouldConvertToProgressive(jpeg_quality, |
| image.get()); |
| if (progressive) { |
| EXPECT_STREQ(kProgressiveHeader, image_contents.substr( |
| kProgressiveHeaderStartIndex, strlen(kProgressiveHeader))); |
| } |
| |
| cached.set_inlined_data(image_contents.data(), image_contents.size()); |
| cached.set_inlined_image_type(static_cast<int>(image->image_type())); |
| DataUrl( |
| *Image::TypeToContentType( |
| static_cast<ImageType>(cached.inlined_image_type())), |
| BASE64, cached.inlined_data(), &data_url); |
| GoogleString data_header("data:"); |
| data_header.append(image->content_type()->mime_type()); |
| data_header.append(";base64,"); |
| EXPECT_EQ(data_header, data_url.substr(0, data_header.size())); |
| StringPiece encoded_contents( |
| data_url.data() + data_header.size(), |
| data_url.size() - data_header.size()); |
| GoogleString decoded_contents; |
| EXPECT_TRUE(Mime64Decode(encoded_contents, &decoded_contents)); |
| EXPECT_EQ(image->Contents(), decoded_contents); |
| |
| // Now truncate the file in various ways and make sure we still |
| // get partial data. |
| GoogleString dim_data(contents, 0, min_bytes_to_dimensions); |
| ImagePtr dim_image( |
| ImageFromString(intended_output_type, filename, dim_data, progressive)); |
| ExpectDimensions(input_type, min_bytes_to_dimensions, width, height, |
| dim_image.get()); |
| EXPECT_EQ(min_bytes_to_dimensions, dim_image->output_size()); |
| |
| GoogleString no_dim_data(contents, 0, min_bytes_to_dimensions - 1); |
| CheckInvalid(filename, no_dim_data, type_for_truncated_image, |
| intended_output_type, progressive); |
| GoogleString type_data(contents, 0, min_bytes_to_type); |
| CheckInvalid(filename, type_data, type_for_truncated_image, |
| intended_output_type, progressive); |
| GoogleString junk(contents, 0, min_bytes_to_type - 1); |
| CheckInvalid(filename, junk, IMAGE_UNKNOWN, IMAGE_UNKNOWN, |
| progressive); |
| return progressive; |
| } |
| |
| GoogleString EncodeUrlAndDimensions(const StringPiece& origin_url, |
| const ImageDim& dim) { |
| StringVector v; |
| v.push_back(origin_url.as_string()); |
| GoogleString out; |
| ResourceContext data; |
| *data.mutable_desired_image_dims() = dim; |
| encoder_.Encode(v, &data, &out); |
| return out; |
| } |
| |
| bool DecodeUrlAndDimensions(const StringPiece& encoded, |
| ImageDim* dim, |
| GoogleString* url) { |
| ResourceContext context; |
| StringVector urls; |
| bool result = encoder_.Decode(encoded, &urls, &context, &message_handler_); |
| if (result) { |
| EXPECT_EQ(1, urls.size()); |
| url->assign(urls.back()); |
| *dim = context.desired_image_dims(); |
| } |
| return result; |
| } |
| |
| void ExpectBadDim(const StringPiece& url) { |
| GoogleString origin_url; |
| ImageDim dim; |
| EXPECT_FALSE(DecodeUrlAndDimensions(url, &dim, &origin_url)); |
| EXPECT_FALSE(ImageUrlEncoder::HasValidDimension(dim)); |
| } |
| |
| void SetJpegRecompressionAndQuality(Image::CompressionOptions* options) { |
| options->jpeg_quality = 85; |
| options->recompress_jpeg = true; |
| } |
| |
| ImageUrlEncoder encoder_; |
| scoped_ptr<Image::CompressionOptions> options_; |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(ImageTest); |
| }; |
| |
| namespace { |
| |
| TEST_F(ImageTest, EmptyImageUnidentified) { |
| CheckInvalid("Empty string", "", IMAGE_UNKNOWN, IMAGE_UNKNOWN, |
| false); |
| } |
| |
| TEST_F(ImageTest, InputWebpTest) { |
| CheckImageFromFile( |
| kScenery, IMAGE_WEBP, IMAGE_WEBP, IMAGE_WEBP, IMAGE_UNKNOWN, |
| 29, // Min bytes to bother checking file type at all. |
| 30, |
| 550, 368, |
| 30320, false); |
| } |
| |
| |
| TEST_F(ImageTest, WebpLowResTest) { |
| // FYI: Takes ~20000 ms to run under Valgrind. |
| if (RunningOnValgrind()) { |
| return; |
| } |
| Image::CompressionOptions* options = new Image::CompressionOptions(); |
| options->recompress_webp = true; |
| options->preferred_webp = WEBP_LOSSY; |
| GoogleString contents; |
| ImagePtr image(ReadFromFileWithOptions(kScenery, &contents, options)); |
| int filesize = 30320; |
| image->SetTransformToLowRes(); |
| EXPECT_GT(filesize, image->output_size()); |
| } |
| |
| TEST_F(ImageTest, WebpLaLowResTest) { |
| // FYI: This test will also probably take very long to run under Valgrind. |
| if (RunningOnValgrind()) { |
| return; |
| } |
| Image::CompressionOptions* options = new Image::CompressionOptions(); |
| options->recompress_webp = true; |
| options->preferred_webp = WEBP_LOSSLESS; |
| GoogleString contents; |
| ImagePtr image(ReadFromFileWithOptions(kScenery, &contents, options)); |
| int filesize = 30320; |
| image->SetTransformToLowRes(); |
| EXPECT_GT(filesize, image->output_size()); |
| } |
| |
| TEST_F(ImageTest, PngTest) { |
| options_->recompress_png = true; |
| CheckImageFromFile( |
| kBikeCrash, IMAGE_PNG, IMAGE_PNG, |
| ImageHeaders::kPngHeaderLength, |
| ImageHeaders::kIHDRDataStart + ImageHeaders::kPngIntSize * 2, |
| 100, 100, |
| 26548, true); |
| } |
| |
| TEST_F(ImageTest, PngToWebpTest) { |
| // FYI: This test will also probably take very long to run under Valgrind. |
| if (RunningOnValgrind()) { |
| return; |
| } |
| ConversionVarChecker conversion_var_checker(options_.get()); |
| options_->webp_quality = 75; |
| CheckImageFromFile( |
| kBikeCrash, IMAGE_PNG, IMAGE_WEBP, |
| ImageHeaders::kPngHeaderLength, |
| ImageHeaders::kIHDRDataStart + ImageHeaders::kPngIntSize * 2, |
| 100, 100, |
| 26548, true); |
| conversion_var_checker.Test(0, 0, 0, // gif |
| 0, 1, 0, // png |
| 0, 0, 0, // jpeg |
| 0, 0, 0, // gif animated |
| true); |
| } |
| |
| TEST_F(ImageTest, PngToWebpFailToJpegDueToPreferredTest) { |
| // FYI: This test will also probably take very long to run under Valgrind. |
| if (RunningOnValgrind()) { |
| return; |
| } |
| ConversionVarChecker conversion_var_checker(options_.get()); |
| options_->preferred_webp = WEBP_NONE; |
| options_->webp_quality = 75; |
| options_->jpeg_quality = 85; |
| options_->convert_jpeg_to_webp = true; |
| CheckImageFromFile( |
| kBikeCrash, IMAGE_PNG, IMAGE_JPEG, |
| ImageHeaders::kPngHeaderLength, |
| ImageHeaders::kIHDRDataStart + ImageHeaders::kPngIntSize * 2, |
| 100, 100, |
| 26548, true); |
| conversion_var_checker.Test(0, 0, 0, // gif |
| 0, 0, 0, // png |
| 0, 0, 0, // jpeg |
| 0, 0, 0, // gif animated |
| true); |
| } |
| |
| TEST_F(ImageTest, PngToWebpLaTest) { |
| // FYI: This test will also probably take very long to run under Valgrind. |
| if (RunningOnValgrind()) { |
| return; |
| } |
| ConversionVarChecker conversion_var_checker(options_.get()); |
| options_->webp_quality = 75; |
| CheckImageFromFile( |
| kCuppa, IMAGE_PNG, IMAGE_WEBP_LOSSLESS_OR_ALPHA, |
| ImageHeaders::kPngHeaderLength, |
| ImageHeaders::kIHDRDataStart + ImageHeaders::kPngIntSize * 2, |
| 65, 70, |
| 1763, true); |
| conversion_var_checker.Test(0, 0, 0, // gif |
| 0, 1, 0, // png |
| 0, 0, 0, // jpeg |
| 0, 0, 0, // gif animated |
| true); |
| } |
| |
| TEST_F(ImageTest, PngAlphaFailToWebpLossyTest) { |
| // FYI: This test will also probably take very long to run under Valgrind. |
| if (RunningOnValgrind()) { |
| return; |
| } |
| Image::CompressionOptions* options = new Image::CompressionOptions; |
| ConversionVarChecker conversion_var_checker(options); |
| options->preferred_webp = WEBP_LOSSY; |
| options->allow_webp_alpha = false; |
| options->webp_quality = 75; |
| options->jpeg_quality = 85; |
| options->convert_png_to_jpeg = true; |
| options->convert_jpeg_to_webp = true; |
| EXPECT_EQ(0, options->conversions_attempted); |
| |
| GoogleString buffer; |
| ImagePtr image(ReadFromFileWithOptions(kCuppaTransparent, &buffer, options)); |
| image->output_size(); |
| EXPECT_EQ(ContentType::kPng, image->content_type()->type()); |
| |
| // "kCuppaTransparent" is a graphic. It should be compressed losslessly, |
| // but the configuration only allows lossy compression, so no compression |
| // will be attempted. |
| EXPECT_EQ(0, options->conversions_attempted); |
| conversion_var_checker.Test(0, 0, 0, // gif |
| 0, 0, 0, // png |
| 0, 0, 0, // jpeg |
| 0, 0, 0, // gif animated |
| true); |
| } |
| |
| TEST_F(ImageTest, PngAlphaToWebpLaTest) { |
| // FYI: This test will also probably take very long to run under Valgrind. |
| if (RunningOnValgrind()) { |
| return; |
| } |
| Image::CompressionOptions* options = new Image::CompressionOptions; |
| ConversionVarChecker conversion_var_checker(options); |
| options->preferred_webp = WEBP_LOSSLESS; |
| options->allow_webp_alpha = true; |
| options->convert_png_to_jpeg = true; |
| options->convert_jpeg_to_webp = true; |
| options->webp_quality = 75; |
| options->jpeg_quality = 85; |
| EXPECT_EQ(0, options->conversions_attempted); |
| |
| GoogleString buffer; |
| ImagePtr image(ReadFromFileWithOptions(kCuppaTransparent, &buffer, options)); |
| image->output_size(); |
| EXPECT_EQ(ContentType::kWebp, image->content_type()->type()); |
| EXPECT_EQ(1, options->conversions_attempted); |
| conversion_var_checker.Test(0, 0, 0, // gif |
| 0, 1, 0, // png |
| 0, 0, 0, // jpeg |
| 0, 0, 0, // gif animated |
| false); |
| } |
| |
| TEST_F(ImageTest, PngAlphaToWebpTestFailsBecauseTooManyTries) { |
| // FYI: This test will also probably take very long to run under Valgrind. |
| if (RunningOnValgrind()) { |
| return; |
| } |
| Image::CompressionOptions* options = new Image::CompressionOptions; |
| ConversionVarChecker conversion_var_checker(options); |
| options->preferred_webp = WEBP_LOSSLESS; |
| options->allow_webp_alpha = true; |
| options->convert_png_to_jpeg = true; |
| options->convert_jpeg_to_webp = true; |
| options->webp_quality = 75; |
| options->jpeg_quality = 85; |
| options->conversions_attempted = 2; |
| |
| GoogleString buffer; |
| ImagePtr image(ReadFromFileWithOptions(kCuppaTransparent, &buffer, options)); |
| image->output_size(); |
| EXPECT_EQ(ContentType::kPng, image->content_type()->type()); |
| EXPECT_EQ(2, options->conversions_attempted); |
| // There were already enough (2) attempts, so we shouldn't try any |
| // more conversions. |
| conversion_var_checker.Test(0, 0, 0, // gif |
| 0, 0, 0, // png |
| 0, 0, 0, // jpeg |
| 0, 0, 0, // gif animated |
| false); |
| } |
| |
| // This tests that we compress the alpha channel on the webp. If we |
| // don't on this image, it becomes larger than the original. |
| TEST_F(ImageTest, PngLargeAlphaToWebpLaTest) { |
| // FYI: This test will also probably take very long to run under Valgrind. |
| if (RunningOnValgrind()) { |
| return; |
| } |
| Image::CompressionOptions* options = new Image::CompressionOptions; |
| ConversionVarChecker conversion_var_checker(options); |
| options->preferred_webp = WEBP_LOSSLESS; |
| options->allow_webp_alpha = true; |
| options->convert_png_to_jpeg = true; |
| options->convert_jpeg_to_webp = true; |
| options->webp_quality = 75; |
| options->jpeg_quality = 85; |
| EXPECT_EQ(0, options->conversions_attempted); |
| |
| GoogleString buffer; |
| ImagePtr image(ReadFromFileWithOptions(kRedbrush, &buffer, options)); |
| EXPECT_GT(image->input_size(), image->output_size()); |
| EXPECT_EQ(ContentType::kWebp, image->content_type()->type()); |
| EXPECT_EQ(1, options->conversions_attempted); |
| conversion_var_checker.Test(0, 0, 0, // gif |
| 0, 1, 0, // png |
| 0, 0, 0, // jpeg |
| 0, 0, 0, // gif animated |
| false); |
| // TODO(vchudnov): Check that the pixels match. |
| } |
| |
| // Same image and settings that succeed in PngLargeAlphaToWebpTest, |
| // should fail when using a very short timeout. |
| TEST_F(ImageTest, PngLargeAlphaToWebpTimesOutToPngTest) { |
| // FYI: This test will also probably take very long to run under Valgrind. |
| if (RunningOnValgrind()) { |
| return; |
| } |
| Image::CompressionOptions* options = new Image::CompressionOptions; |
| ConversionVarChecker conversion_var_checker(options); |
| options->preferred_webp = WEBP_LOSSLESS; |
| options->allow_webp_alpha = true; |
| options->convert_png_to_jpeg = true; |
| options->convert_jpeg_to_webp = true; |
| options->webp_quality = 75; |
| options->jpeg_quality = 85; |
| options->webp_conversion_timeout_ms = 1; |
| EXPECT_EQ(0, options->conversions_attempted); |
| conversion_var_checker.Test(0, 0, 0, // gif |
| 0, 0, 0, // png |
| 0, 0, 0, // jpeg |
| 0, 0, 0, // gif animated |
| false); |
| |
| GoogleString buffer; |
| ImagePtr image(ReadFromFileWithOptions(kRedbrush, &buffer, options)); |
| timer_.SetTimeDeltaUs(1); // When setting deadline |
| timer_.SetTimeDeltaUs(1); // Before attempting webp lossless |
| timer_.SetTimeDeltaUs( // During conversion |
| 1000 * options->webp_conversion_timeout_ms + 1); |
| image->output_size(); |
| EXPECT_EQ(ContentType::kPng, image->content_type()->type()); |
| conversion_var_checker.Test(0, 0, 0, // gif |
| 1, 0, 0, // png |
| 0, 0, 0, // jpeg |
| 0, 0, 0, // gif animated |
| false); |
| |
| // One attempt for WebpP conversion, one attempt for the fall-back |
| // to PNG/JPEG. |
| EXPECT_EQ(2, options->conversions_attempted); |
| } |
| |
| // Same image and settings that succeed in PngLargeAlphaToWebpTest, |
| // should succeed if processing is really fast. |
| TEST_F(ImageTest, PngLargeAlphaToWebpDoesNotTimeOutTest) { |
| // FYI: This test will also probably take very long to run under Valgrind. |
| if (RunningOnValgrind()) { |
| return; |
| } |
| Image::CompressionOptions* options = new Image::CompressionOptions; |
| ConversionVarChecker conversion_var_checker(options); |
| options->preferred_webp = WEBP_LOSSLESS; |
| options->allow_webp_alpha = true; |
| options->convert_png_to_jpeg = true; |
| options->convert_jpeg_to_webp = true; |
| options->webp_quality = 75; |
| options->jpeg_quality = 85; |
| options->webp_conversion_timeout_ms = 1; |
| EXPECT_EQ(0, options->conversions_attempted); |
| conversion_var_checker.Test(0, 0, 0, // gif |
| 0, 0, 0, // png |
| 0, 0, 0, // jpeg |
| 0, 0, 0, // gif animated |
| false); |
| |
| GoogleString buffer; |
| ImagePtr image(ReadFromFileWithOptions(kRedbrush, &buffer, options)); |
| timer_.SetTimeDeltaUs(1); // When setting deadline |
| timer_.SetTimeDeltaUs(1); // Before attempting webp lossless |
| timer_.SetTimeDeltaUs( // During conversion |
| 1000 * options->webp_conversion_timeout_ms - 2); |
| image->output_size(); |
| EXPECT_EQ(ContentType::kWebp, image->content_type()->type()); |
| conversion_var_checker.Test(0, 0, 0, // gif |
| 0, 1, 0, // png |
| 0, 0, 0, // jpeg |
| 0, 0, 0, // gif animated |
| false); |
| |
| // One attempt for WebpP conversion. |
| EXPECT_EQ(1, options->conversions_attempted); |
| } |
| |
| TEST_F(ImageTest, PngToJpegTest) { |
| options_->jpeg_quality = 85; |
| CheckImageFromFile( |
| kBikeCrash, IMAGE_PNG, IMAGE_JPEG, |
| ImageHeaders::kPngHeaderLength, |
| ImageHeaders::kIHDRDataStart + ImageHeaders::kPngIntSize * 2, |
| 100, 100, |
| 26548, true); |
| } |
| |
| TEST_F(ImageTest, TooSmallToConvertPngToProgressiveJpegTest) { |
| options_->progressive_jpeg = true; |
| options_->jpeg_quality = 85; |
| bool progressive = CheckImageFromFile( |
| kBikeCrash, IMAGE_PNG, IMAGE_JPEG, |
| ImageHeaders::kPngHeaderLength, |
| ImageHeaders::kIHDRDataStart + ImageHeaders::kPngIntSize * 2, |
| 100, 100, |
| 26548, true); |
| EXPECT_FALSE(progressive); |
| } |
| |
| TEST_F(ImageTest, PngToProgressiveJpegTest) { |
| options_->progressive_jpeg = true; |
| options_->jpeg_quality = 85; |
| options_->progressive_jpeg_min_bytes = 100; // default is 10k. |
| bool progressive = CheckImageFromFile( |
| kBikeCrash, IMAGE_PNG, IMAGE_JPEG, |
| ImageHeaders::kPngHeaderLength, |
| ImageHeaders::kIHDRDataStart + ImageHeaders::kPngIntSize * 2, |
| 100, 100, |
| 26548, true); |
| EXPECT_TRUE(progressive); |
| } |
| |
| TEST_F(ImageTest, GifToPngTest) { |
| CheckImageFromFile( |
| kIronChef, IMAGE_GIF, IMAGE_PNG, |
| 8, // Min bytes to bother checking file type at all. |
| ImageHeaders::kGifDimStart + ImageHeaders::kGifIntSize * 2, |
| 192, 256, |
| 24941, true); |
| } |
| |
| TEST_F(ImageTest, GifToPngDisabledTest) { |
| Image::CompressionOptions* options = new Image::CompressionOptions; |
| options->convert_gif_to_png = false; |
| EXPECT_EQ(0, options->conversions_attempted); |
| |
| GoogleString buffer; |
| ImagePtr image(ReadFromFileWithOptions(kIronChef, &buffer, options)); |
| image->output_size(); |
| EXPECT_EQ(ContentType::kGif, image->content_type()->type()); |
| EXPECT_EQ(0, options->conversions_attempted); |
| } |
| |
| TEST_F(ImageTest, GifToJpegTest) { |
| options_->jpeg_quality = 85; |
| CheckImageFromFile( |
| kIronChef, IMAGE_GIF, IMAGE_JPEG, |
| 8, // Min bytes to bother checking file type at all. |
| ImageHeaders::kGifDimStart + ImageHeaders::kGifIntSize * 2, |
| 192, 256, |
| 24941, true); |
| } |
| |
| TEST_F(ImageTest, GifToWebpTest) { |
| // FYI: This test will also probably take very long to run under Valgrind. |
| if (RunningOnValgrind()) { |
| return; |
| } |
| ConversionVarChecker conversion_var_checker(options_.get()); |
| options_->webp_quality = 25; |
| CheckImageFromFile( |
| kIronChef, IMAGE_GIF, IMAGE_WEBP, |
| 8, // Min bytes to bother checking file type at all. |
| ImageHeaders::kGifDimStart + ImageHeaders::kGifIntSize * 2, |
| 192, 256, |
| 24941, true); |
| conversion_var_checker.Test(0, 1, 0, // gif |
| 0, 0, 0, // png |
| 0, 0, 0, // jpeg |
| 0, 0, 0, // gif animated |
| true); |
| } |
| |
| TEST_F(ImageTest, GifToWebpLaTest) { |
| // FYI: This test will also probably take very long to run under Valgrind. |
| if (RunningOnValgrind()) { |
| return; |
| } |
| ConversionVarChecker conversion_var_checker(options_.get()); |
| options_->webp_quality = 75; |
| CheckImageFromFile( |
| kTransparent, IMAGE_GIF, IMAGE_WEBP_LOSSLESS_OR_ALPHA, |
| 8, // Min bytes to bother checking file type at all. |
| ImageHeaders::kGifDimStart + ImageHeaders::kGifIntSize * 2, |
| 320, 320, |
| 55800, true); |
| conversion_var_checker.Test(0, 1, 0, // gif |
| 0, 0, 0, // png |
| 0, 0, 0, // jpeg |
| 0, 0, 0, // gif animated |
| false); |
| } |
| |
| TEST_F(ImageTest, AnimatedFilterNotEnabledTest) { |
| CheckImageFromFile( |
| kCradle, IMAGE_GIF, IMAGE_PNG, |
| 8, // Min bytes to bother checking file type at all. |
| ImageHeaders::kGifDimStart + ImageHeaders::kGifIntSize * 2, |
| 200, 150, |
| 583374, false); |
| } |
| |
| TEST_F(ImageTest, JpegTest) { |
| options_->recompress_jpeg = true; |
| CheckImageFromFile( |
| kPuzzle, IMAGE_JPEG, IMAGE_JPEG, |
| 8, // Min bytes to bother checking file type at all. |
| 6468, // Specific to this test |
| 1023, 766, |
| 241260, true); |
| } |
| |
| TEST_F(ImageTest, ProgressiveJpegTest) { |
| options_->recompress_jpeg = true; |
| options_->progressive_jpeg = true; |
| CheckImageFromFile( |
| kPuzzle, IMAGE_JPEG, IMAGE_JPEG, |
| 8, // Min bytes to bother checking file type at all. |
| 6468, // Specific to this test |
| 1023, 766, |
| 241260, true); |
| } |
| |
| TEST_F(ImageTest, NumProgressiveScansTest) { |
| Image::CompressionOptions* options = new Image::CompressionOptions(); |
| SetJpegRecompressionAndQuality(options); |
| options->progressive_jpeg = true; |
| options->jpeg_num_progressive_scans = 3; |
| |
| GoogleString buffer; |
| ImagePtr image(ReadFromFileWithOptions(kPuzzle, &buffer, options)); |
| EXPECT_GT(buffer.size(), image->output_size()); |
| EXPECT_EQ(3, GetNumScansInJpeg(image->Contents().as_string())); |
| } |
| |
| TEST_F(ImageTest, UseJpegLossyIfInputQualityIsLowTest) { |
| Image::CompressionOptions* options = new Image::CompressionOptions(); |
| SetJpegRecompressionAndQuality(options); |
| options->progressive_jpeg = true; |
| |
| GoogleString buffer; |
| // Input image quality is 50. |
| ImagePtr image(ReadFromFileWithOptions(kAppSegments, &buffer, options)); |
| EXPECT_GT(buffer.size(), image->output_size()); |
| EXPECT_EQ( |
| 50, JpegUtils::GetImageQualityFromImage(image->Contents().data(), |
| image->Contents().size(), |
| &message_handler_)); |
| |
| // When num progressive scans is set, we use lossy path. The compression |
| // quality is the minimum of the input and the configuration, i.e., 50. |
| options = new Image::CompressionOptions(); |
| SetJpegRecompressionAndQuality(options); |
| options->progressive_jpeg = true; |
| buffer.clear(); |
| options->jpeg_num_progressive_scans = 1; |
| image.reset(ReadFromFileWithOptions(kAppSegments, &buffer, options)); |
| EXPECT_GT(buffer.size(), image->output_size()); |
| EXPECT_EQ( |
| 50, JpegUtils::GetImageQualityFromImage(image->Contents().data(), |
| image->Contents().size(), |
| &message_handler_)); |
| |
| // Empty image will return -1 when we try to determine its quality. |
| options = new Image::CompressionOptions(); |
| SetJpegRecompressionAndQuality(options); |
| options->progressive_jpeg = true; |
| image.reset(NewImage("", "", GTestTempDir(), options, |
| &timer_, &message_handler_)); |
| EXPECT_EQ( |
| -1, JpegUtils::GetImageQualityFromImage(image->Contents().data(), |
| image->Contents().size(), |
| &message_handler_)); |
| } |
| |
| TEST_F(ImageTest, JpegRetainColorProfileTest) { |
| Image::CompressionOptions* options = new Image::CompressionOptions(); |
| SetJpegRecompressionAndQuality(options); |
| options->retain_color_profile = true; |
| |
| GoogleString buffer; |
| ImagePtr image(ReadFromFileWithOptions(kAppSegments, &buffer, options)); |
| EXPECT_TRUE(IsJpegSegmentPresent(buffer, GetColorProfileMarker())); |
| EXPECT_GT(buffer.size(), image->output_size()); |
| EXPECT_TRUE(IsJpegSegmentPresent(image->Contents().as_string(), |
| GetColorProfileMarker())); |
| // Try stripping the color profile information. |
| options = new Image::CompressionOptions(); |
| SetJpegRecompressionAndQuality(options); |
| options->retain_color_profile = false; |
| buffer.clear(); |
| image.reset(ReadFromFileWithOptions(kAppSegments, &buffer, options)); |
| EXPECT_TRUE(IsJpegSegmentPresent(buffer, GetColorProfileMarker())); |
| EXPECT_GT(buffer.size(), image->output_size()); |
| EXPECT_FALSE(IsJpegSegmentPresent(image->Contents().as_string(), |
| GetColorProfileMarker())); |
| } |
| |
| TEST_F(ImageTest, JpegRetainColorSamplingTest) { |
| int num_components, h_sampling_factor, v_sampling_factor; |
| Image::CompressionOptions* options = new Image::CompressionOptions(); |
| SetJpegRecompressionAndQuality(options); |
| options->retain_color_profile = false; |
| |
| GoogleString buffer; |
| // Input image color sampling is YUV 422. By defult we force YUV420. |
| ImagePtr image(ReadFromFileWithOptions(kPuzzle, &buffer, options)); |
| GetJpegNumComponentsAndSamplingFactors( |
| buffer, &num_components, &h_sampling_factor, &v_sampling_factor); |
| EXPECT_EQ(3, num_components); |
| EXPECT_EQ(2, h_sampling_factor); |
| EXPECT_EQ(1, v_sampling_factor); |
| EXPECT_GT(buffer.size(), image->output_size()); |
| GetJpegNumComponentsAndSamplingFactors( |
| image->Contents().as_string(), &num_components, &h_sampling_factor, |
| &v_sampling_factor); |
| EXPECT_EQ(3, num_components); |
| EXPECT_EQ(2, h_sampling_factor); |
| EXPECT_EQ(2, v_sampling_factor); |
| |
| // Try retaining the color sampling. |
| options = new Image::CompressionOptions(); |
| SetJpegRecompressionAndQuality(options); |
| options->retain_color_sampling = true; |
| buffer.clear(); |
| image.reset(ReadFromFileWithOptions(kPuzzle, &buffer, options)); |
| EXPECT_GT(buffer.size(), image->output_size()); |
| GetJpegNumComponentsAndSamplingFactors( |
| image->Contents().as_string(), &num_components, &h_sampling_factor, |
| &v_sampling_factor); |
| EXPECT_EQ(3, num_components); |
| EXPECT_EQ(2, h_sampling_factor); |
| EXPECT_EQ(1, v_sampling_factor); |
| } |
| |
| TEST_F(ImageTest, JpegRetainExifDataTest) { |
| Image::CompressionOptions* options = new Image::CompressionOptions(); |
| SetJpegRecompressionAndQuality(options); |
| options->retain_exif_data = true; |
| |
| GoogleString buffer; |
| ImagePtr image(ReadFromFileWithOptions(kAppSegments, &buffer, options)); |
| EXPECT_TRUE(IsJpegSegmentPresent(buffer, GetExifDataMarker())); |
| EXPECT_GT(buffer.size(), image->output_size()); |
| EXPECT_TRUE(IsJpegSegmentPresent(image->Contents().as_string(), |
| GetExifDataMarker())); |
| // Try stripping the color profile information. |
| options = new Image::CompressionOptions(); |
| SetJpegRecompressionAndQuality(options); |
| options->retain_exif_data = false; |
| buffer.clear(); |
| image.reset(ReadFromFileWithOptions(kAppSegments, &buffer, options)); |
| EXPECT_TRUE(IsJpegSegmentPresent(buffer, GetExifDataMarker())); |
| EXPECT_GT(buffer.size(), image->output_size()); |
| EXPECT_FALSE(IsJpegSegmentPresent(image->Contents().as_string(), |
| GetExifDataMarker())); |
| } |
| |
| TEST_F(ImageTest, WebpTest) { |
| // FYI: Takes ~70000 ms to run under Valgrind. |
| if (RunningOnValgrind()) { |
| return; |
| } |
| options_->webp_quality = 75; |
| CheckImageFromFile( |
| kPuzzle, IMAGE_JPEG, IMAGE_WEBP, |
| 8, // Min bytes to bother checking file type at all. |
| 6468, // Specific to this test |
| 1023, 766, |
| 241260, true); |
| } |
| |
| TEST_F(ImageTest, JpegToWebpTimesOutTest) { |
| Image::CompressionOptions* options = new Image::CompressionOptions; |
| ConversionVarChecker conversion_var_checker(options); |
| options->recompress_jpeg = true; |
| options->convert_jpeg_to_webp = true; |
| options->preferred_webp = WEBP_LOSSY; |
| options->webp_quality = 75; |
| options->webp_conversion_timeout_ms = 1; |
| timer_.SetTimeDeltaUs(1); // 1st increment of time, used for setting deadline |
| timer_.SetTimeDeltaUs(1); // 2nd increment of time, used for setting deadline |
| timer_.SetTimeDeltaUs( // During conversion |
| 1000 * options->webp_conversion_timeout_ms + 1); |
| |
| EXPECT_EQ(0, options->conversions_attempted); |
| conversion_var_checker.Test(0, 0, 0, // gif |
| 0, 0, 0, // png |
| 0, 0, 0, // jpeg |
| 0, 0, 0, // gif animated |
| true); |
| |
| GoogleString buffer; |
| ImagePtr image(ReadFromFileWithOptions(kPuzzle, &buffer, options)); |
| image->output_size(); |
| EXPECT_EQ(ContentType::kJpeg, image->content_type()->type()); |
| |
| EXPECT_EQ(2, options->conversions_attempted); |
| conversion_var_checker.Test(0, 0, 0, // gif |
| 0, 0, 0, // png |
| 1, 0, 0, // jpeg |
| 0, 0, 0, // gif animated |
| true); |
| } |
| |
| TEST_F(ImageTest, JpegToWebpDoesNotTimeOutTest) { |
| // FYI: This test will probably take very long to run under Valgrind. |
| if (RunningOnValgrind()) { |
| return; |
| } |
| Image::CompressionOptions* options = new Image::CompressionOptions; |
| ConversionVarChecker conversion_var_checker(options); |
| options->recompress_jpeg = true; |
| options->convert_jpeg_to_webp = true; |
| options->preferred_webp = WEBP_LOSSY; |
| options->webp_quality = 75; |
| options->webp_conversion_timeout_ms = 1; |
| timer_.SetTimeDeltaUs(1); // When setting deadline |
| timer_.SetTimeDeltaUs( // During conversion |
| 1000 * options->webp_conversion_timeout_ms - 1); |
| |
| EXPECT_EQ(0, options->conversions_attempted); |
| conversion_var_checker.Test(0, 0, 0, // gif |
| 0, 0, 0, // png |
| 0, 0, 0, // jpeg |
| 0, 0, 0, // gif animated |
| true); |
| |
| GoogleString buffer; |
| ImagePtr image(ReadFromFileWithOptions(kPuzzle, &buffer, options)); |
| image->output_size(); |
| EXPECT_EQ(ContentType::kWebp, image->content_type()->type()); |
| |
| EXPECT_EQ(1, options->conversions_attempted); |
| conversion_var_checker.Test(0, 0, 0, // gif |
| 0, 0, 0, // png |
| 0, 1, 0, // jpeg |
| 0, 0, 0, // gif animated |
| true); |
| } |
| |
| TEST_F(ImageTest, WebpNonLaFromJpgTest) { |
| // FYI: Takes ~70000 ms to run under Valgrind. |
| if (RunningOnValgrind()) { |
| return; |
| } |
| ConversionVarChecker conversion_var_checker(options_.get()); |
| options_->webp_quality = 75; |
| // Note that jpeg->webp cannot return a lossless webp. |
| CheckImageFromFile( |
| kPuzzle, IMAGE_JPEG, IMAGE_WEBP_LOSSLESS_OR_ALPHA, |
| IMAGE_WEBP, IMAGE_JPEG, |
| 8, // Min bytes to bother checking file type at all. |
| 6468, // Specific to this test |
| 1023, 766, |
| 241260, true); |
| conversion_var_checker.Test(0, 0, 0, // gif |
| 0, 0, 0, // png |
| 0, 1, 0, // jpeg |
| 0, 0, 0, // gif animated |
| true); |
| } |
| |
| TEST_F(ImageTest, DrawImage) { |
| Image::CompressionOptions* options = new Image::CompressionOptions(); |
| options->recompress_png = true; |
| GoogleString buf1; |
| ImagePtr image1(ReadFromFileWithOptions(kBikeCrash, &buf1, options)); |
| ImageDim image_dim1; |
| image1->Dimensions(&image_dim1); |
| |
| options = new Image::CompressionOptions(); |
| options->recompress_png = true; |
| GoogleString buf2; |
| ImagePtr image2(ReadFromFileWithOptions(kCuppa, &buf2, options)); |
| ImageDim image_dim2; |
| image2->Dimensions(&image_dim2); |
| |
| int width = std::max(image_dim1.width(), image_dim2.width()); |
| int height = image_dim1.height() + image_dim2.height(); |
| ASSERT_GT(width, 0); |
| ASSERT_GT(height, 0); |
| options = new Image::CompressionOptions(); |
| options->recompress_png = true; |
| ImagePtr canvas(BlankImageWithOptions(width, height, IMAGE_PNG, |
| GTestTempDir(), &timer_, |
| &message_handler_, options)); |
| EXPECT_TRUE(canvas->DrawImage(image1.get(), 0, 0)); |
| EXPECT_TRUE(canvas->DrawImage(image2.get(), 0, image_dim1.height())); |
| // The combined image should be bigger than either of the components, but |
| // smaller than their unoptimized sum. |
| EXPECT_GT(canvas->output_size(), image1->output_size()); |
| EXPECT_GT(canvas->output_size(), image2->output_size()); |
| EXPECT_GT(image1->input_size() + image2->input_size(), |
| canvas->output_size()); |
| } |
| |
| // Make sure that the image produced by 'DrawImage()' is accurate for every |
| // pixel. |
| TEST_F(ImageTest, DrawImageDetails) { |
| GoogleString buf1, buf2; |
| uint8_t* image1_pixels = NULL; |
| uint8_t* image2_pixels = NULL; |
| uint8_t* canvas_pixels = NULL; |
| PixelFormat image1_format, image2_format, canvas_format; |
| size_t image1_width, image2_width, canvas_width; |
| size_t image1_height, image2_height, canvas_height; |
| size_t image1_stride, image2_stride, canvas_stride; |
| Image::CompressionOptions* image1_options = new Image::CompressionOptions(); |
| Image::CompressionOptions* image2_options = new Image::CompressionOptions(); |
| Image::CompressionOptions* canvas_options = new Image::CompressionOptions(); |
| canvas_options->recompress_png = true; |
| |
| // 'kIronChef' is an RGB GIF image while 'kCuppaTransparent' is a grayscale |
| // transparent PNG image. |
| ImagePtr image1(ReadFromFileWithOptions(kIronChef, &buf1, image1_options)); |
| ImagePtr image2(ReadFromFileWithOptions(kCuppaTransparent, &buf2, |
| image2_options)); |
| |
| ASSERT_TRUE(ReadImage(pagespeed::image_compression::IMAGE_GIF, |
| buf1.data(), buf1.length(), |
| reinterpret_cast<void**>(&image1_pixels), |
| &image1_format, &image1_width, &image1_height, |
| &image1_stride, &message_handler_)); |
| |
| ASSERT_TRUE(ReadImage(pagespeed::image_compression::IMAGE_PNG, |
| buf2.data(), buf2.length(), |
| reinterpret_cast<void**>(&image2_pixels), |
| &image2_format, &image2_width, &image2_height, |
| &image2_stride, &message_handler_)); |
| |
| int width = std::max(image1_width, image2_width); |
| int height = image1_height + image2_height; |
| ImagePtr canvas(BlankImageWithOptions(width, height, IMAGE_PNG, |
| GTestTempDir(), &timer_, |
| &message_handler_, canvas_options)); |
| EXPECT_TRUE(canvas->DrawImage(image1.get(), 0, 0)); |
| EXPECT_TRUE(canvas->DrawImage(image2.get(), 0, image1_height)); |
| |
| ASSERT_TRUE(ReadImage(pagespeed::image_compression::IMAGE_PNG, |
| canvas->Contents().data(), canvas->Contents().length(), |
| reinterpret_cast<void**>(&canvas_pixels), |
| &canvas_format, &canvas_width, &canvas_height, |
| &canvas_stride, &message_handler_)); |
| |
| CompareImageRegions(image1_pixels, image1_format, image1_stride, 0, 0, |
| canvas_pixels, canvas_format, canvas_stride, 0, 0, |
| image1_width, image1_height, &message_handler_); |
| |
| CompareImageRegions(image2_pixels, image2_format, image2_stride, 0, 0, |
| canvas_pixels, canvas_format, canvas_stride, |
| 0, image1_height, image2_width, image2_height, |
| &message_handler_); |
| |
| free(image1_pixels); |
| free(image2_pixels); |
| free(canvas_pixels); |
| } |
| |
| TEST_F(ImageTest, BlankTransparentImage) { |
| int width = 1000, height = 1000; |
| Image::CompressionOptions* options = new Image::CompressionOptions(); |
| |
| options->use_transparent_for_blank_image = true; |
| ImagePtr blank(BlankImageWithOptions(width, height, IMAGE_PNG, GTestTempDir(), |
| &timer_, &message_handler_, options)); |
| bool loaded = blank->EnsureLoaded(false); |
| EXPECT_EQ(loaded, true); |
| EXPECT_GT(blank->Contents().size(), 0); |
| |
| ImageDim blank_dim; |
| blank->Dimensions(&blank_dim); |
| EXPECT_EQ(blank_dim.width(), width); |
| EXPECT_EQ(blank_dim.height(), height); |
| } |
| |
| TEST_F(ImageTest, ResizeTo) { |
| GoogleString buf; |
| ImagePtr image(ReadImageFromFile(IMAGE_JPEG, kPuzzle, &buf, false)); |
| |
| ImageDim new_dim; |
| new_dim.set_width(10); |
| new_dim.set_height(10); |
| image->ResizeTo(new_dim); |
| |
| ExpectEmptyOutput(image.get()); |
| ExpectContentType(IMAGE_JPEG, image.get()); |
| } |
| |
| TEST_F(ImageTest, CompressJpegUsingLossyOrLossless) { |
| Image::CompressionOptions* options = new Image::CompressionOptions(); |
| SetJpegRecompressionAndQuality(options); |
| GoogleString buffer; |
| |
| // Input image quality is 50. When jpeg_quality is set to -1, lossless |
| // will be used and the quality of the input image will be preserved. |
| options->jpeg_quality = -1; |
| ImagePtr image(ReadFromFileWithOptions(kAppSegments, &buffer, options)); |
| EXPECT_GT(buffer.size(), image->output_size()); |
| EXPECT_EQ( |
| 50, JpegUtils::GetImageQualityFromImage(image->Contents().data(), |
| image->Contents().size(), |
| &message_handler_)); |
| |
| // When jpeg_num_progressive_scans > 0, lossy will be used and the quality |
| // will be set to the minimum of input quality and jpeg_quality. |
| options = new Image::CompressionOptions(); |
| SetJpegRecompressionAndQuality(options); |
| options->jpeg_num_progressive_scans = 1; |
| options->jpeg_quality = 51; |
| buffer.clear(); |
| image.reset(ReadFromFileWithOptions(kAppSegments, &buffer, options)); |
| EXPECT_GT(buffer.size(), image->output_size()); |
| EXPECT_EQ( |
| 50, JpegUtils::GetImageQualityFromImage(image->Contents().data(), |
| image->Contents().size(), |
| &message_handler_)); |
| |
| // When jpeg_quality is less than input quality, lossy will be used and the |
| // output quality is the minimum of them. |
| options = new Image::CompressionOptions(); |
| SetJpegRecompressionAndQuality(options); |
| options->jpeg_quality = 49; |
| buffer.clear(); |
| image.reset(ReadFromFileWithOptions(kAppSegments, &buffer, options)); |
| EXPECT_GT(buffer.size(), image->output_size()); |
| EXPECT_EQ( |
| 49, JpegUtils::GetImageQualityFromImage(image->Contents().data(), |
| image->Contents().size(), |
| &message_handler_)); |
| } |
| |
| void SetBaseJpegOptions(Image::CompressionOptions* options) { |
| options->preferred_webp = WEBP_LOSSY; |
| options->allow_webp_alpha = true; |
| options->convert_gif_to_png = true; |
| options->convert_png_to_jpeg = true; |
| options->webp_quality = 75; |
| options->webp_animated_quality = 75; |
| options->jpeg_quality = 85; |
| } |
| |
| TEST_F(ImageTest, IgnoreTimeoutWhenFinishingWebp) { |
| // FYI: This test will also probably take very long to run under Valgrind. |
| if (RunningOnValgrind()) { |
| return; |
| } |
| |
| // Get the jpeg reference image |
| Image::CompressionOptions* jpeg_options = new Image::CompressionOptions; |
| SetBaseJpegOptions(jpeg_options); |
| |
| GoogleString jpeg_buffer; |
| ImagePtr jpeg_image(ReadFromFileWithOptions(kBikeCrash, |
| &jpeg_buffer, |
| jpeg_options)); |
| |
| jpeg_image->output_size(); |
| EXPECT_EQ(ContentType::kJpeg, jpeg_image->content_type()->type()); |
| |
| |
| // Get the webp reference image |
| Image::CompressionOptions* webp_options = new Image::CompressionOptions; |
| SetBaseJpegOptions(webp_options); |
| webp_options->convert_jpeg_to_webp = true; |
| webp_options->webp_conversion_timeout_ms = 1; |
| GoogleString webp_buffer; |
| ImagePtr webp_image(ReadFromFileWithOptions(kBikeCrash, |
| &webp_buffer, |
| webp_options)); |
| |
| webp_image->output_size(); |
| EXPECT_EQ(ContentType::kWebp, webp_image->content_type()->type()); |
| |
| |
| // Make sure that if the timeout occurs before the first byte is |
| // written, we do indeed time out. |
| Image::CompressionOptions* timed_out_webp_options = |
| new Image::CompressionOptions; |
| SetBaseJpegOptions(timed_out_webp_options); |
| timed_out_webp_options->convert_jpeg_to_webp = true; |
| timed_out_webp_options->webp_conversion_timeout_ms = 1; |
| |
| GoogleString timed_out_webp_buffer; |
| ImagePtr timed_out_webp_image( |
| ReadFromFileWithOptions(kBikeCrash, |
| &timed_out_webp_buffer, |
| timed_out_webp_options)); |
| timer_.SetTimeMs(10); |
| timer_.SetTimeDeltaUs(1); // When setting deadline |
| timer_.SetTimeDeltaUs(1); // Before attempting webp lossless |
| timer_.SetTimeDeltaUs(1); |
| timer_.SetTimeDeltaUs(2000); |
| |
| timed_out_webp_image->output_size(); |
| EXPECT_EQ(ContentType::kJpeg, timed_out_webp_image->content_type()->type()); |
| EXPECT_EQ(jpeg_image->Contents(), |
| timed_out_webp_image->Contents()); |
| |
| // Test that if we time out after the first output byte is emitted, we keep |
| // going with the webp output. |
| Image::CompressionOptions* almost_done_webp_options = |
| new Image::CompressionOptions; |
| SetBaseJpegOptions(almost_done_webp_options); |
| almost_done_webp_options->convert_jpeg_to_webp = true; |
| almost_done_webp_options->webp_conversion_timeout_ms = 1; |
| |
| const char* kSomeData = "some data"; |
| GoogleString almost_done_webp_buffer; |
| ImagePtr almost_done_webp_image( |
| ReadFromFileWithOptions(kBikeCrash, |
| &almost_done_webp_buffer, |
| almost_done_webp_options)); |
| timer_.SetTimeMs(20); |
| timer_.SetTimeDeltaUs(1); // When setting deadline |
| timer_.SetTimeDeltaUs(1); // Before attempting webp lossless |
| timer_.SetTimeDeltaUs(1); |
| timer_.SetTimeDeltaUs(1); |
| timer_.SetTimeDeltaUs(1); |
| timer_.SetTimeDeltaUs(1); |
| // We need to specify the template typenames explicitly below |
| // because the compiler can't decide whether to use this test class |
| // or its base class, ImageTest. |
| timer_.SetTimeDeltaUsWithCallback( |
| 2000, |
| MakeFunction< |
| ImageTest_IgnoreTimeoutWhenFinishingWebp_Test, |
| const char*, |
| GoogleString*>( |
| this, |
| &ImageTest_IgnoreTimeoutWhenFinishingWebp_Test::WriteToBuffer, |
| kSomeData, |
| GetOutputContents(almost_done_webp_image.get()))); |
| |
| almost_done_webp_image->output_size(); |
| EXPECT_EQ(ContentType::kWebp, |
| almost_done_webp_image->content_type()->type()); |
| GoogleString expected = kSomeData; |
| expected.append(webp_image->Contents().as_string()); |
| EXPECT_EQ(expected, |
| almost_done_webp_image->Contents()); |
| } |
| |
| TEST_F(ImageTest, AnimatedGifToWebpTest) { |
| // FYI: This test will also probably take very long to run under Valgrind. |
| if (RunningOnValgrind()) { |
| return; |
| } |
| ConversionVarChecker conversion_var_checker(options_.get()); |
| options_->webp_animated_quality = 25; |
| options_->allow_webp_animated = true; |
| options_->preferred_webp = WEBP_ANIMATED; |
| CheckImageFromFile( |
| kCradle, IMAGE_GIF, IMAGE_WEBP_ANIMATED, |
| 8, // Min bytes to bother checking file type at all. |
| ImageHeaders::kGifDimStart + ImageHeaders::kGifIntSize * 2, |
| 200, 150, |
| 583374, true); |
| conversion_var_checker.Test(0, 0, 0, // gif |
| 0, 0, 0, // png |
| 0, 0, 0, // jpeg |
| 0, 1, 0, // gif animated |
| true); |
| } |
| |
| } // namespace |
| } // namespace net_instaweb |