| /* |
| * Copyright 2013 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: Huibao Lin |
| |
| #include "pagespeed/kernel/image/test_utils.h" |
| |
| #include <math.h> |
| #include <cstdlib> |
| #include "pagespeed/kernel/base/basictypes.h" |
| #include "pagespeed/kernel/base/gtest.h" |
| #include "pagespeed/kernel/base/mock_message_handler.h" |
| #include "pagespeed/kernel/base/null_mutex.h" |
| #include "pagespeed/kernel/base/scoped_ptr.h" |
| #include "pagespeed/kernel/base/stdio_file_system.h" |
| #include "pagespeed/kernel/base/string_util.h" |
| #include "pagespeed/kernel/base/string_writer.h" |
| #include "pagespeed/kernel/image/read_image.h" |
| #include "pagespeed/kernel/image/scanline_interface.h" |
| #include "pagespeed/kernel/image/scanline_utils.h" |
| |
| namespace net_instaweb { |
| class MessageHandler; |
| } |
| |
| namespace pagespeed { |
| |
| namespace { |
| |
| const double kMaxPSNR = 99.0; |
| const int kIndexAlpha = 3; |
| const uint8_t kAlphaTransparent = 0; |
| |
| // Definition of Peak-Signal-to-Noise-Ratio (PSNR): |
| // http://en.wikipedia.org/wiki/Peak_signal-to-noise_ratio |
| // |
| // The implementation is similar to |
| // third_party/libwebp/tests/check_psnr.cc. |
| // However, this implementation supports image with different number of |
| // channels. It also allows padding at the end of scanlines. |
| double ComputePSNR(const uint8_t* pixels1, const uint8_t* pixels2, |
| int width, int height, int num_channels, int stride) { |
| double error = 0.0; |
| for (int y = 0; y < height; ++y) { |
| for (int x = 0; x < width; ++x) { |
| for (int ch = 0; ch < num_channels; ++ch) { |
| int index = y * stride + (x * num_channels + ch); |
| double dif = static_cast<double>(pixels1[index]) - |
| static_cast<double>(pixels2[index]); |
| error += dif * dif; |
| } |
| } |
| } |
| error /= (height * width * num_channels); |
| return (error > 0.0) ? 10.0 * log10(255.0 * 255.0 / error) : kMaxPSNR; |
| } |
| |
| } // namespace |
| |
| namespace image_compression { |
| |
| using net_instaweb::MessageHandler; |
| |
| bool ReadFile(const GoogleString& file_name, |
| GoogleString* content) { |
| content->clear(); |
| net_instaweb::StdioFileSystem file_system; |
| net_instaweb::MockMessageHandler message_handler(new net_instaweb::NullMutex); |
| net_instaweb::StringWriter writer(content); |
| return(file_system.ReadFile(file_name.c_str(), &writer, &message_handler)); |
| } |
| |
| bool ReadTestFile(const GoogleString& path, |
| const char* name, |
| const char* extension, |
| GoogleString* content) { |
| content->clear(); |
| GoogleString file_name = net_instaweb::StrCat(net_instaweb::GTestSrcDir(), |
| kTestRootDir, path, name, ".", extension); |
| return ReadFile(file_name, content); |
| } |
| |
| bool ReadTestFileWithExt(const GoogleString& path, |
| const char* name_with_extension, |
| GoogleString* content) { |
| GoogleString file_name = net_instaweb::StrCat(net_instaweb::GTestSrcDir(), |
| kTestRootDir, path, name_with_extension); |
| return ReadFile(file_name, content); |
| } |
| |
| void DecodeAndCompareImages( |
| pagespeed::image_compression::ImageFormat image_format1, |
| const void* image_buffer1, |
| size_t buffer_length1, |
| pagespeed::image_compression::ImageFormat image_format2, |
| const void* image_buffer2, |
| size_t buffer_length2, |
| bool ignore_transparent_rgb, |
| MessageHandler* message_handler) { |
| DecodeAndCompareImagesByPSNR(image_format1, image_buffer1, buffer_length1, |
| image_format2, image_buffer2, buffer_length2, |
| kMaxPSNR, ignore_transparent_rgb, |
| message_handler); |
| } |
| |
| void DecodeAndCompareImagesByPSNR( |
| pagespeed::image_compression::ImageFormat image_format1, |
| const void* image_buffer1, |
| size_t buffer_length1, |
| pagespeed::image_compression::ImageFormat image_format2, |
| const void* image_buffer2, |
| size_t buffer_length2, |
| double min_psnr, |
| bool ignore_transparent_rgb, |
| MessageHandler* message_handler) { |
| uint8_t* pixels1 = NULL; |
| uint8_t* pixels2 = NULL; |
| PixelFormat pixel_format1, pixel_format2; |
| size_t width1, height1, stride1, width2, height2, stride2; |
| |
| // Decode the images. |
| ASSERT_TRUE(ReadImage(image_format1, image_buffer1, buffer_length1, |
| reinterpret_cast<void**>(&pixels1), |
| &pixel_format1, &width1, &height1, &stride1, |
| message_handler)); |
| ASSERT_TRUE(ReadImage(image_format2, image_buffer2, buffer_length2, |
| reinterpret_cast<void**>(&pixels2), |
| &pixel_format2, &width2, &height2, &stride2, |
| message_handler)); |
| int num_channels = GetNumChannelsFromPixelFormat(pixel_format1, |
| message_handler); |
| |
| // Verify that the pixel format and sizes are the same. |
| EXPECT_EQ(pixel_format1, pixel_format2); |
| EXPECT_EQ(width1, width2); |
| EXPECT_EQ(height1, height2); |
| EXPECT_EQ(stride1, stride2); |
| |
| if (min_psnr >= kMaxPSNR) { |
| // Verify that all of the pixels are exactly the same. |
| for (size_t y = 0; y < height1; ++y) { |
| for (size_t x = 0; x < width1; ++x) { |
| int ch = 0; |
| if (ignore_transparent_rgb && pixel_format1 == RGBA_8888 |
| && pixels1[y * stride1 + (x * num_channels + 3) == 0]) { |
| // Skip checking RGB when alpha is 0, still test alpha itself. |
| ch = 3; // Index of alpha channel in RGBA_8888. |
| } |
| for (; ch < num_channels; ++ch) { |
| int index = y * stride1 + (x * num_channels + ch); |
| EXPECT_EQ(pixels1[index], pixels2[index]) |
| << " y: " << y |
| << " x: " << x |
| << " ch: " << ch |
| << " index: " << index; |
| } |
| } |
| } |
| } else { |
| double psnr = ComputePSNR(pixels1, pixels2, width1, height1, num_channels, |
| stride1); |
| EXPECT_LE(min_psnr, psnr); |
| } |
| |
| free(pixels1); |
| free(pixels2); |
| } |
| |
| void CompareImageReaders(ScanlineReaderInterface* reader1, |
| ScanlineReaderInterface* reader2) { |
| ASSERT_NE(reinterpret_cast<ScanlineReaderInterface*>(NULL), reader1); |
| ASSERT_NE(reinterpret_cast<ScanlineReaderInterface*>(NULL), reader2); |
| ASSERT_EQ(reader1->GetPixelFormat(), reader2->GetPixelFormat()); |
| ASSERT_EQ(reader1->GetImageHeight(), reader2->GetImageHeight()); |
| ASSERT_EQ(reader1->GetImageWidth(), reader2->GetImageWidth()); |
| ASSERT_EQ(reader1->GetBytesPerScanline(), reader2->GetBytesPerScanline()); |
| |
| while (reader1->HasMoreScanLines() && reader2->HasMoreScanLines()) { |
| uint8_t* scanline1 = NULL; |
| uint8_t* scanline2 = NULL; |
| ASSERT_TRUE(reader1->ReadNextScanline( |
| reinterpret_cast<void**>(&scanline1))); |
| ASSERT_TRUE(reader2->ReadNextScanline( |
| reinterpret_cast<void**>(&scanline2))); |
| EXPECT_EQ(0, memcmp(scanline1, scanline2, reader1->GetBytesPerScanline())); |
| } |
| |
| // Make sure both readers have exhausted all of the scanlines. |
| EXPECT_FALSE(reader1->HasMoreScanLines()); |
| EXPECT_FALSE(reader2->HasMoreScanLines()); |
| } |
| |
| void CompareImageRegions(const uint8_t* image1, PixelFormat format1, |
| int bytes_per_row1, int col1, int row1, |
| const uint8_t* image2, PixelFormat format2, |
| int bytes_per_row2, int col2, int row2, |
| int num_cols, int num_rows, MessageHandler* handler) { |
| ASSERT_TRUE(format1 != UNSUPPORTED && format2 != UNSUPPORTED); |
| const int num_channels1 = |
| GetNumChannelsFromPixelFormat(format1, handler); |
| const int num_channels2 = |
| GetNumChannelsFromPixelFormat(format2, handler); |
| |
| PixelFormat format; |
| int num_channels; |
| if (num_channels1 >= num_channels2) { |
| format = format1; |
| num_channels = num_channels1; |
| } else { |
| format = format2; |
| num_channels = num_channels2; |
| } |
| int bytes_per_line = num_cols * num_channels; |
| |
| net_instaweb::scoped_array<uint8_t> line1(new uint8_t[bytes_per_line]); |
| net_instaweb::scoped_array<uint8_t> line2(new uint8_t[bytes_per_line]); |
| ASSERT_TRUE(line1 != NULL && line2 != NULL); |
| |
| image1 += row1 * bytes_per_row1; |
| image2 += row2 * bytes_per_row2; |
| for (int row = 0; row < num_rows; ++row) { |
| ASSERT_TRUE(ExpandPixelFormat(num_cols, format1, col1, image1, format, 0, |
| line1.get(), handler)); |
| ASSERT_TRUE(ExpandPixelFormat(num_cols, format2, col2, image2, format, 0, |
| line2.get(), handler)); |
| if (format != RGBA_8888) { |
| EXPECT_EQ(0, memcmp(line1.get(), line2.get(), bytes_per_line)); |
| } else { |
| uint8_t* pixel1 = line1.get(); |
| uint8_t* pixel2 = line2.get(); |
| for (int col = 0; col < num_cols; ++col) { |
| if (pixel1[kIndexAlpha] != kAlphaTransparent || |
| pixel2[kIndexAlpha] != kAlphaTransparent) { |
| EXPECT_EQ(0, memcmp(pixel1, pixel2, num_channels)); |
| } |
| pixel1 += num_channels; |
| pixel2 += num_channels; |
| } |
| } |
| |
| image1 += bytes_per_row1; |
| image2 += bytes_per_row2; |
| } |
| } |
| |
| void SynthesizeImage(int width, int height, int bytes_per_line, |
| int num_channels, const uint8_t* seed_value, |
| const int* delta_x, const int* delta_y, uint8_t* image) { |
| ASSERT_TRUE(image != NULL); |
| ASSERT_GT(width, 0); |
| ASSERT_GT(height, 0); |
| ASSERT_GE(bytes_per_line, width); |
| |
| net_instaweb::scoped_array<uint8_t> current_value(new uint8_t[num_channels]); |
| memcpy(current_value.get(), seed_value, |
| num_channels * sizeof(seed_value[0])); |
| |
| for (int y = 0; y < height; ++y) { |
| uint8_t* pixel = image + y * bytes_per_line; |
| for (int x = 0; x < width; ++x) { |
| for (int ch = 0; ch < num_channels; ++ch) { |
| pixel[ch] = current_value[ch]; |
| current_value[ch] += delta_x[ch]; |
| } |
| pixel += num_channels; |
| } |
| // Compute the value for the first pixel in the next line. The next line |
| // has values increased from those of the current line by delta_y. |
| for (int ch = 0; ch < num_channels; ++ch) { |
| current_value[ch] = image[y * bytes_per_line + ch] + delta_y[ch]; |
| } |
| } |
| } |
| |
| } // namespace image_compression |
| |
| } // namespace pagespeed |