blob: 66a5bc055653d9c0a6ad3bdce7fb0052dd00356c [file] [log] [blame]
/*
* 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