blob: ed087838ccd5ff7aeb532b090a99a172851f75d4 [file] [log] [blame]
/*
* Copyright 2009 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.
*/
// Test that basic GifReader operations succeed or fail as expected.
// Note that read-in file contents are tested against golden RGBA
// files in png_optimizer_test.cc, not here.
// Author: Victor Chudnovsky
#include <vector>
#include "pagespeed/kernel/base/gtest.h"
#include "pagespeed/kernel/base/message_handler.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/string.h"
#include "pagespeed/kernel/base/string_util.h"
#include "pagespeed/kernel/image/gif_reader.h"
#include "pagespeed/kernel/image/gif_square.h"
#include "pagespeed/kernel/image/image_util.h"
#include "pagespeed/kernel/image/png_optimizer.h"
#include "pagespeed/kernel/image/scanline_interface_frame_adapter.h"
#include "pagespeed/kernel/image/scanline_utils.h"
#include "pagespeed/kernel/image/test_utils.h"
namespace pagespeed {
namespace image_compression {
// Friend adapter so we can instantiate GifFrameReader.
class TestGifFrameReader : public GifFrameReader {
public:
explicit TestGifFrameReader(MessageHandler* handler) :
GifFrameReader(handler) {}
};
} // namespace image_compression
} // namespace pagespeed
namespace {
extern "C" {
#ifdef USE_SYSTEM_LIBPNG
#include "png.h" // NOLINT
#else
#include "third_party/libpng/src/png.h"
#endif
} // extern "C"
using net_instaweb::MockMessageHandler;
using net_instaweb::NullMutex;
using pagespeed::image_compression::kAlphaOpaque;
using pagespeed::image_compression::kAlphaTransparent;
using pagespeed::image_compression::kGifTestDir;
using pagespeed::image_compression::kPngSuiteGifTestDir;
using pagespeed::image_compression::kPngSuiteTestDir;
using pagespeed::image_compression::kPngTestDir;
using pagespeed::image_compression::kValidGifImageCount;
using pagespeed::image_compression::kValidGifImages;
using pagespeed::image_compression::size_px;
using pagespeed::image_compression::FrameSpec;
using pagespeed::image_compression::FrameToScanlineReaderAdapter;
using pagespeed::image_compression::GifDisposalToFrameSpecDisposal;
using pagespeed::image_compression::GifFrameReader;
using pagespeed::image_compression::GifReader;
using pagespeed::image_compression::GifSquare;
using pagespeed::image_compression::ImageFormat;
using pagespeed::image_compression::ImageSpec;
using pagespeed::image_compression::IMAGE_GIF;
using pagespeed::image_compression::IMAGE_PNG;
using pagespeed::image_compression::MultipleFrameReader;
using pagespeed::image_compression::PackAsArgb;
using pagespeed::image_compression::PixelFormat;
using pagespeed::image_compression::PixelFormat;
using pagespeed::image_compression::PixelRgbaChannels;
using pagespeed::image_compression::PngReaderInterface;
using pagespeed::image_compression::ReadTestFile;
using pagespeed::image_compression::ReadFile;
using pagespeed::image_compression::RgbaChannels;
using pagespeed::image_compression::QUIRKS_CHROME;
using pagespeed::image_compression::QUIRKS_FIREFOX;
using pagespeed::image_compression::QUIRKS_NONE;
using pagespeed::image_compression::RGB_888;
using pagespeed::image_compression::RGBA_8888;
using pagespeed::image_compression::RGBA_ALPHA;
using pagespeed::image_compression::RGBA_BLUE;
using pagespeed::image_compression::RGBA_GREEN;
using pagespeed::image_compression::RGBA_NUM_CHANNELS;
using pagespeed::image_compression::RGBA_RED;
using pagespeed::image_compression::ScanlineStatus;
using pagespeed::image_compression::ScopedPngStruct;
using pagespeed::image_compression::TestGifFrameReader;
using pagespeed::image_compression::kMessagePatternFailedToOpen;
using pagespeed::image_compression::kMessagePatternFailedToRead;
using pagespeed::image_compression::kMessagePatternLibpngError;
using pagespeed::image_compression::kMessagePatternLibpngWarning;
using pagespeed::image_compression::kMessagePatternUnexpectedEOF;
const char *kValidOpaqueGifImages[] = {
"basi0g01",
"basi0g02",
"basi0g04",
"basi0g08",
"basi3p01",
"basi3p02",
"basi3p04",
"basi3p08",
"basn0g01",
"basn0g02",
"basn0g04",
"basn0g08",
"basn3p01",
"basn3p02",
"basn3p04",
"basn3p08",
};
const char *kValidTransparentGifImages[] = {
"tr-basi4a08",
"tr-basn4a08"
};
const size_t kValidOpaqueGifImageCount = arraysize(kValidOpaqueGifImages);
const size_t kValidTransparentGifImageCount =
arraysize(kValidTransparentGifImages);
const char kAnimatedGif[] = "animated";
const char kBadGif[] = "bad";
const char kCompletelyTransparentImage[] = "completely_transparent";
const char kFrameSmallerThanScreen[] = "frame_smaller_than_screen";
const char kInterlacedImage[] = "interlaced";
const char kRedConforming[] = "red_conforming";
const char kRedEmptyScreen[] = "red_empty_screen";
const char kRedUnusedBackground[] = "red_unused_invalid_background";
const char kTransparentGif[] = "transparent";
const char kZeroSizeAnimatedGif[] = "zero_size_animation";
const char kInvalidPixelLocalPaletteGif[] = "bad_pixel_local_palette";
const char kInvalidPixelGlobalPaletteGif[] = "bad_pixel_global_palette";
// Message to ignore.
const char kMessagePatternMultipleFrameGif[] =
"Multiple frame GIF is not supported.";
class GifReaderTest : public testing::Test {
public:
GifReaderTest()
: message_handler_(new NullMutex),
gif_reader_(new GifReader(&message_handler_)),
read_(ScopedPngStruct::READ, &message_handler_) {
}
protected:
virtual void SetUp() {
message_handler_.AddPatternToSkipPrinting(kMessagePatternLibpngError);
message_handler_.AddPatternToSkipPrinting(kMessagePatternLibpngWarning);
}
protected:
MockMessageHandler message_handler_;
net_instaweb::scoped_ptr<PngReaderInterface> gif_reader_;
ScopedPngStruct read_;
private:
DISALLOW_COPY_AND_ASSIGN(GifReaderTest);
};
TEST_F(GifReaderTest, LoadValidGifsWithoutTransforms) {
GoogleString in, out;
for (size_t i = 0; i < kValidOpaqueGifImageCount; i++) {
ReadTestFile(
kPngSuiteGifTestDir, kValidOpaqueGifImages[i], "gif", &in);
ASSERT_NE(static_cast<size_t>(0), in.length());
ASSERT_TRUE(gif_reader_->ReadPng(in, read_.png_ptr(), read_.info_ptr(),
PNG_TRANSFORM_IDENTITY))
<< kValidOpaqueGifImages[i];
ASSERT_TRUE(read_.reset());
}
for (size_t i = 0; i < kValidTransparentGifImageCount; i++) {
ReadTestFile(
kPngSuiteGifTestDir, kValidTransparentGifImages[i], "gif", &in);
ASSERT_NE(static_cast<size_t>(0), in.length());
ASSERT_TRUE(gif_reader_->ReadPng(in, read_.png_ptr(), read_.info_ptr(),
PNG_TRANSFORM_IDENTITY))
<< kValidTransparentGifImages[i];
ASSERT_TRUE(read_.reset());
}
ReadTestFile(kGifTestDir, "transparent", "gif", &in);
ASSERT_NE(static_cast<size_t>(0), in.length());
ASSERT_TRUE(gif_reader_->ReadPng(in, read_.png_ptr(), read_.info_ptr(),
PNG_TRANSFORM_IDENTITY));
}
TEST_F(GifReaderTest, ExpandColorMapForValidGifs) {
GoogleString in, out;
for (size_t i = 0; i < kValidOpaqueGifImageCount; i++) {
ReadTestFile(
kPngSuiteGifTestDir, kValidOpaqueGifImages[i], "gif", &in);
ASSERT_NE(static_cast<size_t>(0), in.length());
ASSERT_TRUE(gif_reader_->ReadPng(in, read_.png_ptr(), read_.info_ptr(),
PNG_TRANSFORM_EXPAND))
<< kValidOpaqueGifImages[i];
ASSERT_TRUE(read_.reset());
}
for (size_t i = 0; i < kValidTransparentGifImageCount; i++) {
ReadTestFile(
kPngSuiteGifTestDir, kValidTransparentGifImages[i], "gif", &in);
ASSERT_NE(static_cast<size_t>(0), in.length());
ASSERT_TRUE(gif_reader_->ReadPng(in, read_.png_ptr(), read_.info_ptr(),
PNG_TRANSFORM_EXPAND))
<< kValidTransparentGifImages[i];
ASSERT_TRUE(read_.reset());
}
ReadTestFile(kGifTestDir, "transparent", "gif", &in);
ASSERT_NE(static_cast<size_t>(0), in.length());
ASSERT_TRUE(gif_reader_->ReadPng(in, read_.png_ptr(), read_.info_ptr(),
PNG_TRANSFORM_EXPAND));
}
TEST_F(GifReaderTest, RequireOpaqueForValidGifs) {
GoogleString in;
for (size_t i = 0; i < kValidOpaqueGifImageCount; i++) {
ReadTestFile(
kPngSuiteGifTestDir, kValidOpaqueGifImages[i], "gif", &in);
ASSERT_NE(static_cast<size_t>(0), in.length());
ASSERT_TRUE(gif_reader_->ReadPng(in, read_.png_ptr(), read_.info_ptr(),
PNG_TRANSFORM_IDENTITY, true))
<< kValidOpaqueGifImages[i];
ASSERT_TRUE(read_.reset());
}
for (size_t i = 0; i < kValidTransparentGifImageCount; i++) {
ReadTestFile(
kPngSuiteGifTestDir, kValidTransparentGifImages[i], "gif", &in);
ASSERT_NE(static_cast<size_t>(0), in.length());
ASSERT_FALSE(gif_reader_->ReadPng(in, read_.png_ptr(), read_.info_ptr(),
PNG_TRANSFORM_IDENTITY, true))
<< kValidTransparentGifImages[i];
ASSERT_TRUE(read_.reset());
}
ReadTestFile(kGifTestDir, "transparent", "gif", &in);
ASSERT_NE(static_cast<size_t>(0), in.length());
ASSERT_FALSE(gif_reader_->ReadPng(in, read_.png_ptr(), read_.info_ptr(),
PNG_TRANSFORM_IDENTITY, true));
}
TEST_F(GifReaderTest, ExpandColormapAndRequireOpaqueForValidGifs) {
GoogleString in;
for (size_t i = 0; i < kValidOpaqueGifImageCount; i++) {
ReadTestFile(
kPngSuiteGifTestDir, kValidOpaqueGifImages[i], "gif", &in);
ASSERT_NE(static_cast<size_t>(0), in.length());
ASSERT_TRUE(gif_reader_->ReadPng(in, read_.png_ptr(), read_.info_ptr(),
PNG_TRANSFORM_EXPAND, true))
<< kValidOpaqueGifImages[i];
ASSERT_TRUE(read_.reset());
}
for (size_t i = 0; i < kValidTransparentGifImageCount; i++) {
ReadTestFile(
kPngSuiteGifTestDir, kValidTransparentGifImages[i], "gif", &in);
ASSERT_NE(static_cast<size_t>(0), in.length());
ASSERT_FALSE(gif_reader_->ReadPng(in, read_.png_ptr(), read_.info_ptr(),
PNG_TRANSFORM_EXPAND, true))
<< kValidTransparentGifImages[i];
ASSERT_TRUE(read_.reset());
}
ReadTestFile(kGifTestDir, "transparent", "gif", &in);
ASSERT_NE(static_cast<size_t>(0), in.length());
ASSERT_FALSE(gif_reader_->ReadPng(in, read_.png_ptr(), read_.info_ptr(),
PNG_TRANSFORM_EXPAND, true));
}
TEST_F(GifReaderTest, StripAlpha) {
GoogleString in;
png_uint_32 height;
png_uint_32 width;
int bit_depth;
int color_type;
png_bytep trans;
int num_trans;
png_color_16p trans_values;
ReadTestFile(kGifTestDir, "transparent", "gif", &in);
ASSERT_NE(static_cast<size_t>(0), in.length());
ASSERT_TRUE(gif_reader_->ReadPng(in, read_.png_ptr(), read_.info_ptr(),
PNG_TRANSFORM_STRIP_ALPHA, false));
png_get_IHDR(read_.png_ptr(), read_.info_ptr(),
&width, &height, &bit_depth, &color_type,
NULL, NULL, NULL);
ASSERT_TRUE((color_type & PNG_COLOR_MASK_ALPHA) == 0); // NOLINT
ASSERT_EQ(static_cast<unsigned int>(0),
png_get_tRNS(read_.png_ptr(),
read_.info_ptr(),
&trans,
&num_trans,
&trans_values));
read_.reset();
ASSERT_TRUE(gif_reader_->ReadPng(in, read_.png_ptr(), read_.info_ptr(),
PNG_TRANSFORM_STRIP_ALPHA |
PNG_TRANSFORM_EXPAND, false));
png_get_IHDR(read_.png_ptr(), read_.info_ptr(),
&width, &height, &bit_depth, &color_type,
NULL, NULL, NULL);
ASSERT_TRUE((color_type & PNG_COLOR_MASK_ALPHA) == 0); // NOLINT
ASSERT_EQ(static_cast<unsigned int>(0),
png_get_tRNS(read_.png_ptr(),
read_.info_ptr(),
&trans,
&num_trans,
&trans_values));
}
TEST_F(GifReaderTest, ExpandColormapOnZeroSizeCanvasAndCatchLibPngError) {
GoogleString in;
// This is a free image from
// <http://www.gifs.net/subcategory/40/0/20/Email>, with the canvas
// size information manually set to zero in order to trigger a
// libpng error.
ReadTestFile(kGifTestDir, "zero_size_animation", "gif", &in);
ASSERT_NE(static_cast<size_t>(0), in.length());
ASSERT_FALSE(gif_reader_->ReadPng(in, read_.png_ptr(), read_.info_ptr(),
PNG_TRANSFORM_EXPAND, true));
}
class GifScanlineReaderRawTest : public testing::Test {
public:
GifScanlineReaderRawTest()
: scanline_(NULL),
message_handler_(new NullMutex),
reader_(new TestGifFrameReader(&message_handler_)) {
}
bool Initialize(const char* file_name) {
if (!ReadTestFile(kGifTestDir, file_name, "gif", &input_image_)) {
PS_LOG_FATAL((&message_handler_), "Failed to read file: %s", file_name);
return false;
}
return reader_.Initialize(input_image_.c_str(), input_image_.length());
}
protected:
virtual void SetUp() {
message_handler_.AddPatternToSkipPrinting(kMessagePatternFailedToOpen);
message_handler_.AddPatternToSkipPrinting(kMessagePatternFailedToRead);
message_handler_.AddPatternToSkipPrinting(kMessagePatternMultipleFrameGif);
message_handler_.AddPatternToSkipPrinting(kMessagePatternUnexpectedEOF);
}
protected:
void* scanline_;
MockMessageHandler message_handler_;
FrameToScanlineReaderAdapter reader_;
GoogleString input_image_;
private:
DISALLOW_COPY_AND_ASSIGN(GifScanlineReaderRawTest);
};
TEST_F(GifScanlineReaderRawTest, CorruptHeader) {
ReadTestFile(kGifTestDir, kTransparentGif, "gif", &input_image_);
// Make GifRecordType invalid.
input_image_[781] = 0;
ASSERT_FALSE(reader_.Initialize(input_image_.c_str(),
input_image_.length()));
}
TEST_F(GifScanlineReaderRawTest, InitializeWithoutRead) {
ASSERT_TRUE(Initialize(kTransparentGif));
}
TEST_F(GifScanlineReaderRawTest, ReadOneRow) {
ASSERT_TRUE(Initialize(kTransparentGif));
EXPECT_TRUE(reader_.ReadNextScanline(&scanline_));
}
TEST_F(GifScanlineReaderRawTest, ReinitializeAfterOneRow) {
ASSERT_TRUE(Initialize(kTransparentGif));
EXPECT_TRUE(reader_.ReadNextScanline(&scanline_));
ASSERT_TRUE(Initialize(kInterlacedImage));
EXPECT_TRUE(reader_.ReadNextScanline(&scanline_));
}
TEST_F(GifScanlineReaderRawTest, ReInitializeAfterLastRow) {
ASSERT_TRUE(Initialize(kTransparentGif));
while (reader_.HasMoreScanLines()) {
EXPECT_TRUE(reader_.ReadNextScanline(&scanline_));
}
// After depleting the scanlines, any further call to
// ReadNextScanline leads to death in debugging mode, or a
// false in release mode.
#ifdef NDEBUG
EXPECT_FALSE(reader_.ReadNextScanline(&scanline_));
#else
EXPECT_DEATH(reader_.ReadNextScanline(&scanline_),
"The GIF image was not initialized or does not "
"have more scanlines.");
#endif
ASSERT_TRUE(Initialize(kInterlacedImage));
EXPECT_TRUE(reader_.ReadNextScanline(&scanline_));
}
// Animated GIF is not supported. Make sure GIF reader exits gracefully.
TEST_F(GifScanlineReaderRawTest, AnimatedGif) {
ASSERT_FALSE(Initialize(kAnimatedGif));
}
TEST_F(GifScanlineReaderRawTest, BadGif) {
ASSERT_FALSE(Initialize(kBadGif));
}
TEST_F(GifScanlineReaderRawTest, ZeroSizeGif) {
ASSERT_FALSE(Initialize(kZeroSizeAnimatedGif));
}
// Check the accuracy of the reader. Compare the decoded results with the gold
// data (".gif.rgba"). The test images include transparent and opaque ones.
TEST_F(GifScanlineReaderRawTest, ValidGifs) {
for (size_t i = 0; i < kValidGifImageCount; i++) {
GoogleString rgba_image, gif_image;
const char* file_name = kValidGifImages[i].filename;
ReadTestFile(kPngSuiteGifTestDir, file_name, "gif.rgba", &rgba_image);
ReadTestFile(kPngSuiteGifTestDir, file_name, "gif", &gif_image);
const uint8_t* reference_rgba =
reinterpret_cast<const uint8*>(rgba_image.data());
uint8* decoded_pixels = NULL;
ASSERT_TRUE(reader_.Initialize(gif_image.data(), gif_image.length()));
PixelFormat pixel_format = reader_.GetPixelFormat();
int width = reader_.GetImageWidth();
int height = reader_.GetImageHeight();
int bytes_per_row = reader_.GetBytesPerScanline();
int num_channels = GetNumChannelsFromPixelFormat(pixel_format,
&message_handler_);
EXPECT_EQ(kValidGifImages[i].width, width);
EXPECT_EQ(kValidGifImages[i].height, height);
if (kValidGifImages[i].transparency) {
EXPECT_EQ(pagespeed::image_compression::RGBA_8888, pixel_format);
EXPECT_EQ(4, num_channels);
} else {
EXPECT_EQ(pagespeed::image_compression::RGB_888, pixel_format);
EXPECT_EQ(3, num_channels);
}
EXPECT_EQ(width*num_channels, bytes_per_row);
// Decode and check the image a row at a time.
int row = 0;
while (reader_.HasMoreScanLines()) {
EXPECT_TRUE(reader_.ReadNextScanline(
reinterpret_cast<void**>(&decoded_pixels)));
for (int x = 0; x < width; ++x) {
int index_dec = x * num_channels;
int index_ref = (row * width + x) * 4;
// ASSERT_EQ is used in stead of EXPECT_EQ. Otherwise, the log will be
// spammed when an error happens.
ASSERT_EQ(0, memcmp(reference_rgba+index_ref, decoded_pixels+index_dec,
num_channels));
}
++row;
}
// Make sure both readers have exhausted all image rows.
EXPECT_EQ(height, row);
EXPECT_EQ(rgba_image.length(),
static_cast<size_t>(4 * height * width));
}
}
TEST_F(GifScanlineReaderRawTest, Interlaced) {
GoogleString png_image, gif_image;
ReadTestFile(kGifTestDir, kInterlacedImage, "png", &png_image);
ReadTestFile(kGifTestDir, kInterlacedImage, "gif", &gif_image);
DecodeAndCompareImages(IMAGE_PNG, png_image.c_str(), png_image.length(),
IMAGE_GIF, gif_image.c_str(), gif_image.length(),
false, // ignore_transparent_rgb
&message_handler_);
}
TEST_F(GifScanlineReaderRawTest, EmptyScreen) {
GoogleString png_image, gif_image;
ReadTestFile(kGifTestDir, kRedConforming, "png", &png_image);
ReadTestFile(kGifTestDir, kRedEmptyScreen, "gif", &gif_image);
DecodeAndCompareImages(IMAGE_PNG, png_image.c_str(), png_image.length(),
IMAGE_GIF, gif_image.c_str(), gif_image.length(),
false, // ignore_transparent_rgb
&message_handler_);
}
TEST_F(GifScanlineReaderRawTest, UnusedBackground) {
GoogleString png_image, gif_image;
ReadTestFile(kGifTestDir, kRedConforming, "png", &png_image);
ReadTestFile(kGifTestDir, kRedUnusedBackground, "gif", &gif_image);
DecodeAndCompareImages(IMAGE_PNG, png_image.c_str(), png_image.length(),
IMAGE_GIF, gif_image.c_str(), gif_image.length(),
false, // ignore_transparent_rgb
&message_handler_);
}
TEST_F(GifScanlineReaderRawTest, FrameSmallerThanImage) {
ASSERT_FALSE(Initialize(kFrameSmallerThanScreen));
EXPECT_EQ(1, message_handler_.MessagesOfType(net_instaweb::kInfo));
EXPECT_EQ(0, message_handler_.MessagesOfType(net_instaweb::kWarning));
EXPECT_EQ(0, message_handler_.MessagesOfType(net_instaweb::kError));
EXPECT_EQ(0, message_handler_.MessagesOfType(net_instaweb::kFatal));
}
TEST(GifReaderUtil, DisposalMethod) {
for (int i = -1; i < 4; ++i) {
FrameSpec::DisposalMethod actual_disposal =
GifDisposalToFrameSpecDisposal(i);
FrameSpec::DisposalMethod expected_disposal = FrameSpec::DISPOSAL_NONE;
switch (i) {
case 0:
// intentional fall-through
case 1:
expected_disposal = FrameSpec::DISPOSAL_NONE;
break;
case 2:
expected_disposal = FrameSpec::DISPOSAL_BACKGROUND;
break;
case 3:
expected_disposal = FrameSpec::DISPOSAL_RESTORE;
break;
}
EXPECT_EQ(expected_disposal, actual_disposal);
}
}
void CheckQuirksModeChangesToImageSpec(const FrameSpec& frame_spec,
const ImageSpec& original_spec,
const bool has_loop_count,
const ImageSpec& expected_noquirks_spec,
const ImageSpec& expected_firefox_spec,
const ImageSpec& expected_chrome_spec) {
ImageSpec noquirks_spec = original_spec;
ImageSpec firefox_spec = original_spec;
ImageSpec chrome_spec = original_spec;
GifFrameReader::ApplyQuirksModeToImage(QUIRKS_NONE, has_loop_count,
frame_spec, &noquirks_spec);
EXPECT_TRUE(noquirks_spec.Equals(expected_noquirks_spec));
GifFrameReader::ApplyQuirksModeToImage(QUIRKS_FIREFOX, has_loop_count,
frame_spec, &firefox_spec);
EXPECT_TRUE(firefox_spec.Equals(expected_firefox_spec))
<< "\nActual:\n" << firefox_spec.ToString()
<< "\nExpected:\n" << expected_firefox_spec.ToString();
GifFrameReader::ApplyQuirksModeToImage(QUIRKS_CHROME, has_loop_count,
frame_spec, &chrome_spec);
EXPECT_TRUE(chrome_spec.Equals(expected_chrome_spec))
<< "\nActual:\n" << chrome_spec.ToString()
<< "\nExpected:\n" << expected_chrome_spec.ToString();
}
void SetOpaqueBackground(PixelRgbaChannels rgba) {
static const PixelRgbaChannels kOpaqueBackground =
{0x80, 0x80, 0x80, kAlphaOpaque};
for (int channel = 0;
channel < static_cast<RgbaChannels>(channel);
++channel) {
rgba[channel] = kOpaqueBackground[channel];
}
}
TEST(ApplyQuirksModeToImage, TestFrameWidthLargerThanImageWidth) {
ImageSpec image_spec;
image_spec.width = 100;
image_spec.height = 100;
image_spec.num_frames = 2;
SetOpaqueBackground(image_spec.bg_color);
FrameSpec frame_spec;
frame_spec.width = 200;
frame_spec.height = 100;
frame_spec.top = 10;
frame_spec.left = 2;
ImageSpec expected_noquirks_spec = image_spec;
ImageSpec expected_firefox_spec = image_spec;
ImageSpec expected_chrome_spec = image_spec;
expected_chrome_spec.width = frame_spec.width;
expected_chrome_spec.height = frame_spec.height;
expected_chrome_spec.image_size_adjusted = true;
CheckQuirksModeChangesToImageSpec(frame_spec, image_spec, false,
expected_noquirks_spec,
expected_firefox_spec,
expected_chrome_spec);
image_spec.num_frames =
expected_noquirks_spec.num_frames =
expected_firefox_spec.num_frames =
expected_chrome_spec.num_frames = 1;
CheckQuirksModeChangesToImageSpec(frame_spec, image_spec, false,
expected_noquirks_spec,
expected_firefox_spec,
expected_chrome_spec);
}
TEST(ApplyQuirksModeToImage, TestFrameHeightLargerThanImageHeight) {
ImageSpec image_spec;
image_spec.width = 100;
image_spec.height = 100;
image_spec.num_frames = 2;
SetOpaqueBackground(image_spec.bg_color);
FrameSpec frame_spec;
frame_spec.width = 100;
frame_spec.height = 200;
frame_spec.top = 10;
frame_spec.left = 2;
ImageSpec expected_noquirks_spec = image_spec;
ImageSpec expected_firefox_spec = image_spec;
ImageSpec expected_chrome_spec = image_spec;
expected_chrome_spec.width = frame_spec.width;
expected_chrome_spec.height = frame_spec.height;
expected_chrome_spec.image_size_adjusted = true;
CheckQuirksModeChangesToImageSpec(frame_spec, image_spec, false,
expected_noquirks_spec,
expected_firefox_spec,
expected_chrome_spec);
image_spec.num_frames =
expected_noquirks_spec.num_frames =
expected_firefox_spec.num_frames =
expected_chrome_spec.num_frames = 1;
CheckQuirksModeChangesToImageSpec(frame_spec, image_spec, false,
expected_noquirks_spec,
expected_firefox_spec,
expected_chrome_spec);
}
TEST(ApplyQuirksModeToImage, TestFrameWidthSmallerThanImageWidth) {
ImageSpec image_spec;
image_spec.width = 100;
image_spec.height = 100;
image_spec.num_frames = 2;
SetOpaqueBackground(image_spec.bg_color);
FrameSpec frame_spec;
frame_spec.width = 50;
frame_spec.height = 100;
frame_spec.top = 10;
frame_spec.left = 2;
ImageSpec expected_noquirks_spec = image_spec;
ImageSpec expected_firefox_spec = image_spec;
ImageSpec expected_chrome_spec = image_spec;
CheckQuirksModeChangesToImageSpec(frame_spec, image_spec, false,
expected_noquirks_spec,
expected_firefox_spec,
expected_chrome_spec);
// With only one frame that is smaller than the image, the
// background color becomes fully transparent.
image_spec.num_frames =
expected_noquirks_spec.num_frames =
expected_firefox_spec.num_frames =
expected_chrome_spec.num_frames = 1;
expected_chrome_spec.bg_color[RGBA_ALPHA] = kAlphaTransparent;
expected_firefox_spec.bg_color[RGBA_ALPHA] = kAlphaTransparent;
CheckQuirksModeChangesToImageSpec(frame_spec, image_spec, false,
expected_noquirks_spec,
expected_firefox_spec,
expected_chrome_spec);
}
TEST(ApplyQuirksModeToImage, TestFrameHeightSmallerThanImageHeight) {
ImageSpec image_spec;
image_spec.width = 100;
image_spec.height = 100;
image_spec.num_frames = 2;
SetOpaqueBackground(image_spec.bg_color);
FrameSpec frame_spec;
frame_spec.width = 100;
frame_spec.height = 50;
frame_spec.top = 10;
frame_spec.left = 2;
ImageSpec expected_noquirks_spec = image_spec;
ImageSpec expected_firefox_spec = image_spec;
ImageSpec expected_chrome_spec = image_spec;
CheckQuirksModeChangesToImageSpec(frame_spec, image_spec, false,
expected_noquirks_spec,
expected_firefox_spec,
expected_chrome_spec);
// With only one frame that is smaller than the image, the
// background color becomes fully transparent.
image_spec.num_frames =
expected_noquirks_spec.num_frames =
expected_firefox_spec.num_frames =
expected_chrome_spec.num_frames = 1;
expected_chrome_spec.bg_color[RGBA_ALPHA] = kAlphaTransparent;
expected_firefox_spec.bg_color[RGBA_ALPHA] = kAlphaTransparent;
CheckQuirksModeChangesToImageSpec(frame_spec, image_spec, false,
expected_noquirks_spec,
expected_firefox_spec,
expected_chrome_spec);
}
TEST(ApplyQuirksModeToImage, TestFrameHeightSmallerWidthLargerThanImage) {
ImageSpec image_spec;
image_spec.width = 100;
image_spec.height = 100;
image_spec.num_frames = 2;
SetOpaqueBackground(image_spec.bg_color);
FrameSpec frame_spec;
frame_spec.width = 150;
frame_spec.height = 50;
frame_spec.top = 10;
frame_spec.left = 2;
ImageSpec expected_noquirks_spec = image_spec;
ImageSpec expected_firefox_spec = image_spec;
ImageSpec expected_chrome_spec = image_spec;
expected_chrome_spec.width = frame_spec.width;
expected_chrome_spec.height = frame_spec.height;
expected_chrome_spec.image_size_adjusted = true;
CheckQuirksModeChangesToImageSpec(frame_spec, image_spec, false,
expected_noquirks_spec,
expected_firefox_spec,
expected_chrome_spec);
// With only one frame that is smaller than the image, the
// background color becomes fully transparent.
image_spec.num_frames =
expected_noquirks_spec.num_frames =
expected_firefox_spec.num_frames =
expected_chrome_spec.num_frames = 1;
expected_chrome_spec.bg_color[RGBA_ALPHA] = kAlphaTransparent;
expected_firefox_spec.bg_color[RGBA_ALPHA] = kAlphaTransparent;
CheckQuirksModeChangesToImageSpec(frame_spec, image_spec, false,
expected_noquirks_spec,
expected_firefox_spec,
expected_chrome_spec);
}
TEST(ApplyQuirksModeToImage, TestLoopCount) {
ImageSpec image_spec;
FrameSpec frame_spec;
image_spec.width = 100;
image_spec.height = 100;
image_spec.loop_count = 3;
frame_spec.width = 100;
frame_spec.height = 100;
frame_spec.top = 0;
frame_spec.left = 0;
ImageSpec expected_noquirks_spec = image_spec;
ImageSpec expected_firefox_spec = image_spec;
ImageSpec expected_chrome_spec = image_spec;
CheckQuirksModeChangesToImageSpec(frame_spec, image_spec, false,
expected_noquirks_spec,
expected_firefox_spec,
expected_chrome_spec);
expected_chrome_spec.loop_count = image_spec.loop_count + 1;
CheckQuirksModeChangesToImageSpec(frame_spec, image_spec, true,
expected_noquirks_spec,
expected_firefox_spec,
expected_chrome_spec);
}
TEST(ApplyQuirksModeToImage, TestNoop) {
ImageSpec image_spec;
FrameSpec frame_spec;
image_spec.width = 100;
image_spec.height = 100;
frame_spec.width = 50;
frame_spec.height = 50;
frame_spec.top = 10;
frame_spec.left = 2;
ImageSpec expected_noquirks_spec = image_spec;
ImageSpec expected_firefox_spec = image_spec;
ImageSpec expected_chrome_spec = image_spec;
CheckQuirksModeChangesToImageSpec(frame_spec, image_spec, false,
expected_noquirks_spec,
expected_firefox_spec,
expected_chrome_spec);
}
void CheckQuirksModeChangesToFirstFrameSpec(
const ImageSpec& image_spec,
const FrameSpec& original_spec,
const FrameSpec& expected_noquirks_spec,
const FrameSpec& expected_firefox_spec,
const FrameSpec& expected_chrome_spec) {
FrameSpec noquirks_spec = original_spec;
FrameSpec firefox_spec = original_spec;
FrameSpec chrome_spec = original_spec;
ImageSpec image = image_spec;
GifFrameReader::ApplyQuirksModeToImage(QUIRKS_NONE, false, noquirks_spec,
&image);
GifFrameReader::ApplyQuirksModeToFirstFrame(QUIRKS_NONE,
image, &noquirks_spec);
EXPECT_TRUE(noquirks_spec.Equals(expected_noquirks_spec))
<< " Got: " << noquirks_spec.ToString()
<< "\n Expected: " << expected_noquirks_spec.ToString();
image = image_spec;
GifFrameReader::ApplyQuirksModeToImage(QUIRKS_FIREFOX, false, firefox_spec,
&image);
GifFrameReader::ApplyQuirksModeToFirstFrame(QUIRKS_FIREFOX,
image, &firefox_spec);
EXPECT_TRUE(firefox_spec.Equals(expected_firefox_spec))
<< " Got: " << firefox_spec.ToString()
<< "\n Expected: " << expected_firefox_spec.ToString();
image = image_spec;
GifFrameReader::ApplyQuirksModeToImage(QUIRKS_CHROME, false, chrome_spec,
&image);
GifFrameReader::ApplyQuirksModeToFirstFrame(QUIRKS_CHROME,
image, &chrome_spec);
EXPECT_TRUE(chrome_spec.Equals(expected_chrome_spec))
<< " Got: " << chrome_spec.ToString()
<< "\n Expected: " << expected_chrome_spec.ToString();
}
TEST(ApplyQuirksModeToFirstFrame, TestWidth) {
ImageSpec image_spec;
FrameSpec frame_spec;
image_spec.width = 100;
image_spec.height = 100;
frame_spec.width = 200;
frame_spec.height = 50;
frame_spec.top = 10;
frame_spec.left = 2;
FrameSpec expected_noquirks_spec = frame_spec;
FrameSpec expected_firefox_spec = frame_spec;
FrameSpec expected_chrome_spec = frame_spec;
expected_firefox_spec.top = 0;
expected_firefox_spec.left = 0;
expected_firefox_spec.width = 0;
expected_firefox_spec.height = 0;
expected_chrome_spec.top = 0;
expected_chrome_spec.left = 0;
CheckQuirksModeChangesToFirstFrameSpec(image_spec, frame_spec,
expected_noquirks_spec,
expected_firefox_spec,
expected_chrome_spec);
}
TEST(ApplyQuirksModeToFirstFrame, TestHeight) {
ImageSpec image_spec;
FrameSpec frame_spec;
image_spec.width = 100;
image_spec.height = 100;
frame_spec.width = 50;
frame_spec.height = 200;
frame_spec.top = 10;
frame_spec.left = 2;
FrameSpec expected_noquirks_spec = frame_spec;
FrameSpec expected_firefox_spec = frame_spec;
FrameSpec expected_chrome_spec = frame_spec;
expected_firefox_spec.top = 0;
expected_firefox_spec.left = 0;
expected_firefox_spec.width = 0;
expected_firefox_spec.height = 0;
expected_chrome_spec.top = 0;
expected_chrome_spec.left = 0;
CheckQuirksModeChangesToFirstFrameSpec(image_spec, frame_spec,
expected_noquirks_spec,
expected_firefox_spec,
expected_chrome_spec);
}
TEST(ApplyQuirksModeToFirstFrame, TestZeroWidthFrame) {
ImageSpec image_spec;
FrameSpec frame_spec;
image_spec.width = 100;
image_spec.height = 120;
frame_spec.width = 0;
frame_spec.height = 50;
frame_spec.top = 10;
frame_spec.left = 2;
FrameSpec expected_noquirks_spec = frame_spec;
FrameSpec expected_chrome_spec = frame_spec;
FrameSpec expected_firefox_spec = frame_spec;
expected_chrome_spec.height = image_spec.height;
expected_chrome_spec.width = image_spec.width;
expected_chrome_spec.top = 0;
expected_chrome_spec.left = 0;
expected_firefox_spec.top = 0;
expected_firefox_spec.left = 0;
expected_firefox_spec.width = 0;
expected_firefox_spec.height = 0;
CheckQuirksModeChangesToFirstFrameSpec(image_spec, frame_spec,
expected_noquirks_spec,
expected_firefox_spec,
expected_chrome_spec);
}
TEST(ApplyQuirksModeToFirstFrame, TestZeroHeightFrame) {
ImageSpec image_spec;
FrameSpec frame_spec;
image_spec.width = 100;
image_spec.height = 120;
frame_spec.width = 50;
frame_spec.height = 0;
frame_spec.top = 10;
frame_spec.left = 2;
FrameSpec expected_noquirks_spec = frame_spec;
FrameSpec expected_chrome_spec = frame_spec;
FrameSpec expected_firefox_spec = frame_spec;
expected_chrome_spec.height = image_spec.height;
expected_chrome_spec.width = image_spec.width;
expected_chrome_spec.top = 0;
expected_chrome_spec.left = 0;
expected_firefox_spec.top = 0;
expected_firefox_spec.left = 0;
expected_firefox_spec.width = 0;
expected_firefox_spec.height = 0;
CheckQuirksModeChangesToFirstFrameSpec(image_spec, frame_spec,
expected_noquirks_spec,
expected_firefox_spec,
expected_chrome_spec);
}
TEST(ApplyQuirksModeToFirstFrame, TestNoopChrome) {
ImageSpec image_spec;
FrameSpec frame_spec;
image_spec.width = 100;
image_spec.height = 120;
frame_spec.width = 60;
frame_spec.height = 50;
frame_spec.top = 10;
frame_spec.left = 2;
FrameSpec expected_noquirks_spec = frame_spec;
FrameSpec expected_firefox_spec = frame_spec;
FrameSpec expected_chrome_spec = frame_spec;
expected_firefox_spec.top = 0;
expected_firefox_spec.left = 0;
expected_firefox_spec.width = 0;
expected_firefox_spec.height = 0;
CheckQuirksModeChangesToFirstFrameSpec(image_spec, frame_spec,
expected_noquirks_spec,
expected_firefox_spec,
expected_chrome_spec);
}
TEST(ApplyQuirksModeToFirstFrame, TestNoop) {
ImageSpec image_spec;
FrameSpec frame_spec;
image_spec.width = 100;
image_spec.height = 120;
frame_spec.width = 100;
frame_spec.height = 120;
frame_spec.top = 0;
frame_spec.left = 0;
FrameSpec expected_noquirks_spec = frame_spec;
FrameSpec expected_firefox_spec = frame_spec;
FrameSpec expected_chrome_spec = frame_spec;
CheckQuirksModeChangesToFirstFrameSpec(image_spec, frame_spec,
expected_noquirks_spec,
expected_firefox_spec,
expected_chrome_spec);
}
class GifAnimationTest : public testing::Test {
public:
struct Image {
size_px width;
size_px height;
int bg_color_idx;
size_t loop_count;
};
struct Frame {
size_px width;
size_px height;
bool interlace;
int delay_cs;
int disposal;
const GifColorType* colormap;
int num_colors;
int transparent_idx;
int square_color_idx;
size_px top;
size_px left;
};
GifAnimationTest()
: scanline_(NULL),
message_handler_(new NullMutex),
gif_(true, &message_handler_),
reader_(new TestGifFrameReader(&message_handler_)),
read_all_scanlines_(true) {
}
void SynthesizeImage(const char* filename, const Image& image) {
// Note that these images are synthesized with QUIRKS_NONE.
filename_ = net_instaweb::StrCat(net_instaweb::GTestTempDir(),
"/", filename, ".gif");
EXPECT_TRUE(gif_.Open(filename_));
PS_LOG_INFO((&message_handler_), "Generating image: %s", filename_.c_str());
gif_.PrepareScreen(true, image.width, image.height, kColorMap, kNumColors,
image.bg_color_idx, image.loop_count);
for (std::vector<Frame>::iterator frame = synth_frames_.begin();
frame != synth_frames_.end();
++frame) {
EXPECT_TRUE(gif_.PutImage(
frame->left, frame->top, frame->width, frame->height,
frame->colormap, frame->num_colors,
frame->square_color_idx, frame->transparent_idx,
frame->interlace, frame->delay_cs, frame->disposal));
}
EXPECT_TRUE(gif_.Close());
}
void ReadImage(const Image& image) {
if (!ReadFile(filename_, &input_image_)) {
PS_LOG_FATAL((&message_handler_),
"Failed to read file: %s", filename_.c_str());
return;
}
EXPECT_TRUE(reader_->Initialize(input_image_.c_str(),
input_image_.length()).Success());
ScanlineStatus status;
ImageSpec image_spec;
EXPECT_TRUE(reader_->GetImageSpec(&image_spec, &status));
EXPECT_EQ(image.width, image_spec.width);
EXPECT_EQ(image.height, image_spec.height);
EXPECT_EQ((image.loop_count == GifSquare::kNoLoopCountSpecified ?
1 : image.loop_count),
image_spec.loop_count);
EXPECT_EQ(synth_frames_.size(), image_spec.num_frames);
EXPECT_FALSE(image_spec.use_bg_color);
EXPECT_EQ(kColorMap[image.bg_color_idx].Red,
image_spec.bg_color[RGBA_RED]);
EXPECT_EQ(kColorMap[image.bg_color_idx].Green,
image_spec.bg_color[RGBA_GREEN]);
EXPECT_EQ(kColorMap[image.bg_color_idx].Blue,
image_spec.bg_color[RGBA_BLUE]);
EXPECT_EQ(kAlphaOpaque, image_spec.bg_color[RGBA_ALPHA]);
for (std::vector<Frame>::iterator set_frame = synth_frames_.begin();
set_frame != synth_frames_.end();
++set_frame) {
FrameSpec frame_spec;
EXPECT_TRUE(reader_->HasMoreFrames());
EXPECT_TRUE(reader_->PrepareNextFrame(&status));
EXPECT_TRUE(reader_->GetFrameSpec(&frame_spec, &status));
EXPECT_EQ(set_frame->delay_cs < 0 ?
0 : set_frame->delay_cs * 10, frame_spec.duration_ms);
EXPECT_EQ(set_frame->width, frame_spec.width);
EXPECT_EQ(set_frame->height, frame_spec.height);
EXPECT_EQ(set_frame->top, frame_spec.top);
EXPECT_EQ(set_frame->left, frame_spec.left);
EXPECT_EQ(set_frame->interlace, frame_spec.hint_progressive);
EXPECT_EQ(set_frame->transparent_idx >= 0 ?
RGBA_8888 : RGB_888, frame_spec.pixel_format);
FrameSpec::DisposalMethod frame_disposal =
GifDisposalToFrameSpecDisposal(set_frame->disposal);
EXPECT_EQ((frame_disposal == FrameSpec::DISPOSAL_UNKNOWN ?
FrameSpec::DISPOSAL_NONE : frame_disposal),
frame_spec.disposal);
const GifColorType* cmap = (set_frame->colormap == NULL ?
kColorMap : set_frame->colormap);
const int bytes_per_pixel = GetBytesPerPixel(frame_spec.pixel_format);
static const int kRgbBytes = 3;
if (read_all_scanlines_) {
for (int row = 0; row < frame_spec.height; ++row) {
EXPECT_TRUE(reader_->HasMoreScanlines());
const uint8_t* scanline;
ScanlineStatus status =
reader_->ReadNextScanline(
reinterpret_cast<const void **>(&scanline));
EXPECT_TRUE(status.Success());
for (int col = 0; col < frame_spec.width; ++col) {
// Since we're comparing pixel by pixel, correlated errors
// would generate huge output if we use EXPECT rather than
// ASSERT below.
if (frame_spec.pixel_format == RGBA_8888 &&
(set_frame->transparent_idx == set_frame->square_color_idx)) {
// This pixel is fully transparent so other channels do not
// matter.
ASSERT_EQ(kAlphaTransparent,
*(scanline + col * bytes_per_pixel + kRgbBytes));
const uint8 black_pixel[3] = {0, 0, 0};
ASSERT_EQ(0, memcmp(scanline + col * bytes_per_pixel, black_pixel,
kRgbBytes));
} else {
// This pixel must be opaque. Check all color channels.
ASSERT_EQ(0, memcmp(scanline + col * bytes_per_pixel,
cmap + set_frame->square_color_idx,
kRgbBytes));
if (frame_spec.pixel_format == RGBA_8888) {
ASSERT_EQ(kAlphaOpaque,
*(scanline + col * bytes_per_pixel + kRgbBytes));
}
}
}
}
EXPECT_FALSE(reader_->HasMoreScanlines());
}
}
EXPECT_FALSE(reader_->HasMoreFrames());
}
Image DefineImage() {
Image image;
image.width = 100;
image.height = 100;
image.bg_color_idx = 3;
image.loop_count = GifSquare::kNoLoopCountSpecified;
return image;
}
void SynthesizeAndRead(const char* name, const Image& image) {
SynthesizeImage(name, image);
ReadImage(image);
}
protected:
virtual void SetUp() {
message_handler_.AddPatternToSkipPrinting(kMessagePatternFailedToOpen);
message_handler_.AddPatternToSkipPrinting(kMessagePatternFailedToRead);
message_handler_.AddPatternToSkipPrinting(kMessagePatternMultipleFrameGif);
message_handler_.AddPatternToSkipPrinting(kMessagePatternUnexpectedEOF);
}
protected:
void* scanline_;
MockMessageHandler message_handler_;
GifSquare gif_;
net_instaweb::scoped_ptr<MultipleFrameReader> reader_;
bool read_all_scanlines_;
GoogleString filename_;
GoogleString input_image_;
std::vector<Frame> synth_frames_;
static const int kNumColors;
static const GifColorType kColorMap[];
static const GifColorType kAlternateColorMap[];
private:
DISALLOW_COPY_AND_ASSIGN(GifAnimationTest);
};
const int GifAnimationTest::kNumColors = 8;
const GifColorType GifAnimationTest::kColorMap[kNumColors] = {
GifSquare::kGifWhite,
GifSquare::kGifBlack,
GifSquare::kGifRed,
GifSquare::kGifGreen,
GifSquare::kGifBlue,
GifSquare::kGifYellow,
GifSquare::kGifGray,
GifSquare::kGifGray,
};
const GifColorType GifAnimationTest::kAlternateColorMap[kNumColors] = {
GifSquare::kGifBlue,
GifSquare::kGifRed,
GifSquare::kGifYellow,
GifSquare::kGifGreen,
GifSquare::kGifWhite,
GifSquare::kGifBlack,
GifSquare::kGifGray,
GifSquare::kGifGray,
};
// Note that the various test cases with "FallingOffImage" generate
// images that, when viewed on Chrome and Firefox, exhibit the quirky
// behavior that is captured in ImageSpec and FrameSpec when the
// appropriate QuirksMode is used. The test cases below use
// QUIRKS_NONE to test behavior to the spec.
// Non-animated, non-interlaced, only global colormap, varying disposals.
TEST_F(GifAnimationTest, ReadSingleFrameOpaque) {
const Frame frame = {10, 10, false, 0, 0, NULL, 0, -1, 2, 10, 10};
synth_frames_.push_back(frame);
SynthesizeAndRead("single_frame_opaque", DefineImage());
}
TEST_F(GifAnimationTest, ReadSingleFrameTransparency) {
const Frame frame = {10, 10, false, 0, 1, NULL, 0, 4, 2, 10, 10};
synth_frames_.push_back(frame);
SynthesizeAndRead("single_frame_transparency", DefineImage());
}
TEST_F(GifAnimationTest, ReadSingleFrameOpaqueFallingOffImage) {
const Frame frame = {10, 10, false, 0, 2, NULL, 0, -1, 2, 95, 95};
synth_frames_.push_back(frame);
SynthesizeAndRead("single_frame_opaque_falling_off_image", DefineImage());
}
TEST_F(GifAnimationTest, ReadSingleFrameOpaqueLargeFallingOffImage) {
const Frame frame = {250, 250, false, 0, 2, NULL, 0, -1, 2, 95, 95};
synth_frames_.push_back(frame);
SynthesizeAndRead("single_frame_opaque_large_falling_off_image",
DefineImage());
}
TEST_F(GifAnimationTest, ReadSingleFrameTransparencyFallingOffImage) {
const Frame frame = {10, 10, false, 0, 3, NULL, 0, 4, 2, 95, 95};
synth_frames_.push_back(frame);
SynthesizeAndRead("single_frame_transparent_falling_off_image",
DefineImage());
}
TEST_F(GifAnimationTest, ReadSingleFrameTransparencyFallingOffImageAtOrigin) {
const Frame frame = {250, 250, false, 0, 3, NULL, 0, 4, 2, 0, 0};
synth_frames_.push_back(frame);
SynthesizeAndRead("single_frame_transparent_falling_off_image_at_origin",
DefineImage());
}
TEST_F(GifAnimationTest, ReadSingleFrameOpaqueInZeroSizeImage) {
Image image = DefineImage();
image.width = 0;
image.height = 0;
const Frame frame = {10, 10, false, 0, 1, NULL, 0, 4, 2, 10, 10};
synth_frames_.push_back(frame);
SynthesizeAndRead("single_frame_opaque_in_zero_size_image", image);
}
TEST_F(GifAnimationTest, ReadSingleFrameOpaqueInZeroSizeImageAtOrigin) {
Image image = DefineImage();
image.width = 0;
image.height = 0;
const Frame frame = {10, 10, false, 0, 1, NULL, 0, 4, 2, 0, 0};
synth_frames_.push_back(frame);
SynthesizeAndRead("single_frame_opaque_in_zero_size_image_at_origin", image);
}
// Non-animated, interlaced, only global colormap, varying disposals.
TEST_F(GifAnimationTest, ReadSingleFrameInterlacedOpaque) {
const Frame frame = {10, 10, true, 0, 4, NULL, 0, -1, 2, 10, 10};
synth_frames_.push_back(frame);
SynthesizeAndRead("single_frame_interlaced_opaque", DefineImage());
}
TEST_F(GifAnimationTest, ReadSingleFrameInterlacedTransparency) {
const Frame frame = {10, 10, true, 0, 0, NULL, 0, 4, 2, 10, 10};
synth_frames_.push_back(frame);
SynthesizeAndRead("single_frame_interlaced_transparency", DefineImage());
}
TEST_F(GifAnimationTest,
ReadSingleFrameInterlacedOpaqueFallingOffImage) {
const Frame frame = {10, 10, true, 0, 1, NULL, 0, -1, 2, 95, 95};
synth_frames_.push_back(frame);
SynthesizeAndRead("single_frame_interlaced_opaque_falling_off_image",
DefineImage());
}
TEST_F(GifAnimationTest,
ReadSingleFrameInterlacedTransparencyFallingOffImage) {
const Frame frame = {10, 10, true, 0, 2, NULL, 0, 4, 2, 95, 95};
synth_frames_.push_back(frame);
SynthesizeAndRead("single_frame_interlaced_transparent_falling_off_image",
DefineImage());
}
// Non-animated, non-interlaced, both global and per-frame colormap,
// varying disposals.
TEST_F(GifAnimationTest, ReadSingleFrameDualColormapsOpaque) {
const Frame frame =
{10, 10, false, 0, 3, kAlternateColorMap, kNumColors, -1, 2, 10, 10};
synth_frames_.push_back(frame);
SynthesizeAndRead("single_frame_colormaps_opaque", DefineImage());
}
TEST_F(GifAnimationTest, ReadSingleFrameDualColormapsTransparency) {
const Frame frame =
{10, 10, false, 0, 4, kAlternateColorMap, kNumColors, 4, 2, 10, 10};
synth_frames_.push_back(frame);
SynthesizeAndRead("single_frame_colormaps_transparency", DefineImage());
}
TEST_F(GifAnimationTest,
ReadSingleFrameDualColormapsOpaqueFallingOffImage) {
const Frame frame =
{10, 10, false, 0, 0, kAlternateColorMap, kNumColors, -1, 2, 95, 95};
synth_frames_.push_back(frame);
SynthesizeAndRead("single_frame_colormaps_opaque_falling_off_image",
DefineImage());
}
TEST_F(GifAnimationTest,
ReadSingleFrameDualColormapsTransparencyFallingOffImage) {
const Frame frame =
{10, 10, false, 0, 1, kAlternateColorMap, kNumColors, 4, 2, 95, 95};
synth_frames_.push_back(frame);
SynthesizeAndRead("single_frame_colormaps_transparent_falling_off_image",
DefineImage());
}
// Non-animated, non-interlaced, only global colormap, varying
// disposals, varying delays.
TEST_F(GifAnimationTest, ReadSingleFrameDelayOpaque) {
const Frame frame = {10, 10, true, 10, 0, NULL, 0, -1, 2, 10, 10};
synth_frames_.push_back(frame);
SynthesizeAndRead("single_frame_opaque", DefineImage());
}
// Animated images.
TEST_F(GifAnimationTest, ReadMultipleFrameOpaque) {
Frame frame1 = {20, 20, false, 100, 0, NULL, 0, -1, 2, 10, 10};
synth_frames_.push_back(frame1);
Frame frame2 = {20, 20, true, 100, 0, NULL, 0, -1, 3, 20, 20};
synth_frames_.push_back(frame2);
SynthesizeAndRead("multiple_frame_opaque", DefineImage());
}
TEST_F(GifAnimationTest, ReadMultipleFrameOpaqueFirstFallingOffImage) {
Frame frame1 = {250, 250, true, 100, 0, NULL, 0, -1, 3, 90, 90};
synth_frames_.push_back(frame1);
Frame frame2 = {20, 20, false, 100, 0, NULL, 0, -1, 2, 79, 79};
synth_frames_.push_back(frame2);
SynthesizeAndRead("multiple_frame_opaque_1st_falling_off_image",
DefineImage());
}
TEST_F(GifAnimationTest, ReadMultipleFrameOpaqueSecondFallingOffImage) {
Frame frame1 = {20, 20, false, 100, 0, NULL, 0, -1, 2, 79, 79};
synth_frames_.push_back(frame1);
Frame frame2 = {250, 250, true, 100, 0, NULL, 0, -1, 3, 90, 90};
synth_frames_.push_back(frame2);
SynthesizeAndRead("multiple_frame_opaque_2nd_falling_off_image",
DefineImage());
}
TEST_F(GifAnimationTest, ReadMultipleFrameOpaqueFirstFallingOffImageAtOrigin) {
Frame frame1 = {250, 250, true, 100, 0, NULL, 0, -1, 3, 0, 0};
synth_frames_.push_back(frame1);
Frame frame2 = {20, 20, false, 100, 0, NULL, 0, -1, 2, 79, 79};
synth_frames_.push_back(frame2);
SynthesizeAndRead("multiple_frame_opaque_1st_falling_off_image_at_origin",
DefineImage());
}
TEST_F(GifAnimationTest, ReadMultipleFrameOpaqueFirstFallingOffXImage) {
Frame frame1 = {250, 20, false, 100, 0, NULL, 0, -1, 3, 10, 10};
synth_frames_.push_back(frame1);
Frame frame2 = {20, 20, false, 100, 0, NULL, 0, -1, 2, 79, 79};
synth_frames_.push_back(frame2);
SynthesizeAndRead("multiple_frame_opaque_1st_falling_off_x_image",
DefineImage());
}
TEST_F(GifAnimationTest, ReadMultipleFrameOpaqueFirstFallingOffYImage) {
Frame frame1 = {20, 250, false, 100, 0, NULL, 0, -1, 3, 10, 10};
synth_frames_.push_back(frame1);
Frame frame2 = {20, 20, false, 100, 0, NULL, 0, -1, 2, 79, 79};
synth_frames_.push_back(frame2);
SynthesizeAndRead("multiple_frame_opaque_1st_falling_off_y_image",
DefineImage());
}
TEST_F(GifAnimationTest, ReadMultipleFrameOpaqueSecondFallingOffImageAtOrigin) {
Frame frame1 = {20, 20, false, 100, 0, NULL, 0, -1, 2, 79, 79};
synth_frames_.push_back(frame1);
Frame frame2 = {250, 250, false, 100, 0, NULL, 0, -1, 3, 0, 0};
synth_frames_.push_back(frame2);
SynthesizeAndRead("multiple_frame_opaque_2nd_falling_off_image_at_origin",
DefineImage());
}
TEST_F(GifAnimationTest, ReadMultipleFrameOpaqueNoDelay) {
Frame frame1 = {20, 20, false, 0, 1, NULL, 0, -1, 2, 10, 10};
synth_frames_.push_back(frame1);
Frame frame2 = {20, 20, false, 0, 2, NULL, 0, -1, 3, 20, 20};
synth_frames_.push_back(frame2);
SynthesizeAndRead("multiple_frame_opaque_nodelay", DefineImage());
}
TEST_F(GifAnimationTest, ReadMultipleFrameTransparency) {
Frame frame1 = {20, 20, false, 100, 0, NULL, 0, -1, 2, 10, 10};
synth_frames_.push_back(frame1);
Frame frame2 = {20, 20, false, 100, 0, NULL, 0, 3, 3, 20, 20};
synth_frames_.push_back(frame2);
Frame frame3 = {20, 20, false, 100, 0, NULL, 0, 2, 3, 25, 25};
synth_frames_.push_back(frame3);
SynthesizeAndRead("multiple_frame_transparency", DefineImage());
}
TEST_F(GifAnimationTest, ReadMultipleFrameNoDelay2FrameOpaque) {
// Tests that the transparency information for one frame is not
// carried to the next. By setting the transparent index, delay, and
// disposal to -1, the synthesized image will not have a Graphic
// Control Extension for the given frame, and we can test that
// transparent index was not carried over.
const int frame1_transparent_idx = 3;
const int frame2_color_idx = frame1_transparent_idx;
Frame frame1 =
{20, 20, false, 0, 0, NULL, 0, frame1_transparent_idx, 2, 10, 10};
synth_frames_.push_back(frame1);
Frame frame2 =
{20, 20, false, -1, -1, NULL, 0, -1, frame2_color_idx, 20, 20};
synth_frames_.push_back(frame2);
SynthesizeAndRead("multiple_frame_transparency_no_delay_2frame_opaque",
DefineImage());
}
TEST_F(GifAnimationTest, ReadMultipleFrameTransparencySkipScanlines) {
read_all_scanlines_ = false;
Frame frame1 = {20, 20, false, 100, 0, NULL, 0, -1, 2, 10, 10};
synth_frames_.push_back(frame1);
Frame frame2 = {20, 20, false, 100, 0, NULL, 0, 3, 3, 20, 20};
synth_frames_.push_back(frame2);
Frame frame3 = {20, 20, false, 100, 0, NULL, 0, 2, 3, 25, 25};
synth_frames_.push_back(frame3);
SynthesizeAndRead("multiple_frame_transparency_skip_scanlines",
DefineImage());
}
TEST_F(GifAnimationTest, ReadMultipleFrameTransparencyMixInterlaced) {
Frame frame1 = {20, 20, false, 100, 0, NULL, 0, -1, 2, 10, 10};
synth_frames_.push_back(frame1);
Frame frame2 = {20, 20, true, 100, 0, NULL, 0, 3, 3, 20, 20};
synth_frames_.push_back(frame2);
Frame frame3 = {20, 20, false, 100, 0, NULL, 0, 2, 3, 25, 25};
synth_frames_.push_back(frame3);
SynthesizeAndRead("multiple_frame_transparency_mix_interlaced",
DefineImage());
}
TEST_F(GifAnimationTest, ReadMultipleFrameTransparencyMixColormaps) {
Frame frame1 = {20, 20, false, 100, 0, NULL, 0, -1, 2, 10, 10};
synth_frames_.push_back(frame1);
Frame frame2 =
{20, 20, false, 100, 0, kAlternateColorMap, kNumColors, 3, 3, 20, 20};
synth_frames_.push_back(frame2);
Frame frame3 = {20, 20, false, 100, 0, NULL, 0, 2, 3, 25, 25};
synth_frames_.push_back(frame3);
SynthesizeAndRead("multiple_frame_transparency_mix_colormaps", DefineImage());
}
TEST_F(GifAnimationTest, ReadMultipleFrameOpaqueFallingOffImage) {
Frame frame1 = {10, 10, false, 0, 2, NULL, 0, -1, 2, 93, 93};
synth_frames_.push_back(frame1);
Frame frame2 = {10, 10, false, 0, 2, NULL, 0, -1, 4, 95, 95};
synth_frames_.push_back(frame2);
SynthesizeAndRead("multiple_frame_opaque_falling_off_image", DefineImage());
}
TEST_F(GifAnimationTest, ReadMultipleFrameTransparencyFallingOffImage) {
Frame frame1 = {10, 10, false, 0, 3, NULL, 0, 4, 2, 93, 93};
synth_frames_.push_back(frame1);
Frame frame2 = {10, 10, false, 0, 3, NULL, 0, 4, 4, 95, 95};
synth_frames_.push_back(frame2);
SynthesizeAndRead("multiple_frame_transparent_falling_off_image",
DefineImage());
}
TEST_F(GifAnimationTest, ReadMultipleFrameOpaqueDisposal) {
Frame frame1 = {20, 20, false, 100, 0, NULL, 0, -1, 2, 10, 10};
synth_frames_.push_back(frame1);
Frame frame2 = {20, 20, false, 100, 0, NULL, 0, -1, 3, 20, 20};
synth_frames_.push_back(frame2);
Frame frame3 = {20, 20, false, 100, 2, NULL, 0, -1, 4, 15, 15};
synth_frames_.push_back(frame3);
Frame frame4 = {20, 20, false, 100, 3, NULL, 0, -1, 1, 0, 0};
synth_frames_.push_back(frame4);
Frame frame5 = {20, 20, false, 100, 1, NULL, 0, -1, 5, 30, 30};
synth_frames_.push_back(frame5);
SynthesizeAndRead("multiple_frame_opaque_disposal", DefineImage());
}
TEST_F(GifAnimationTest, ReadMultipleFrameTransparencyLoopInfinite) {
Image image = DefineImage();
image.loop_count = 0;
Frame frame1 = {20, 20, false, 50, 0, NULL, 0, -1, 2, 10, 10};
synth_frames_.push_back(frame1);
Frame frame2 = {20, 20, false, 50, 0, NULL, 0, 3, 3, 20, 20};
synth_frames_.push_back(frame2);
Frame frame3 = {20, 20, false, 50, 0, NULL, 0, 2, 3, 25, 25};
synth_frames_.push_back(frame3);
SynthesizeAndRead("multiple_frame_transparency_loop_infinite", image);
}
TEST_F(GifAnimationTest, ReadMultipleFrameTransparencyNoDelayLoopInfinite) {
Image image = DefineImage();
image.loop_count = 0;
Frame frame1 = {20, 20, false, 0, 1, NULL, 0, -1, 2, 10, 10};
synth_frames_.push_back(frame1);
Frame frame2 = {20, 20, false, 0, 1, NULL, 0, 3, 3, 20, 20};
synth_frames_.push_back(frame2);
Frame frame3 = {20, 20, false, 0, 1, NULL, 0, 2, 3, 25, 25};
synth_frames_.push_back(frame3);
SynthesizeAndRead("multiple_frame_transparency_nodelay_loop_infinite", image);
}
TEST_F(GifAnimationTest, ReadMultipleFrameTransparencyLoopThrice) {
Image image = DefineImage();
image.loop_count = 3;
Frame frame1 = {20, 20, false, 50, 0, NULL, 0, -1, 2, 10, 10};
synth_frames_.push_back(frame1);
Frame frame2 = {20, 20, false, 50, 0, NULL, 0, 3, 3, 20, 20};
synth_frames_.push_back(frame2);
Frame frame3 = {20, 20, false, 50, 0, NULL, 0, 2, 3, 25, 25};
synth_frames_.push_back(frame3);
SynthesizeAndRead("multiple_frame_transparency_loop_thrice", image);
}
void CheckImageForOutOfBoundsPixel(const char* filename,
int first_invalid_frame) {
const PixelRgbaChannels kTransparentPixel = {0, 0, 0, kAlphaTransparent};
MockMessageHandler message_handler(new NullMutex);
net_instaweb::scoped_ptr<MultipleFrameReader>
reader(new TestGifFrameReader(&message_handler));
GoogleString input_image;
if (!ReadTestFile(kGifTestDir, filename, "gif", &input_image)) {
PS_LOG_FATAL((&message_handler),
"Failed to read file: %s", filename);
return;
}
EXPECT_TRUE(reader->Initialize(input_image.c_str(),
input_image.length()).Success());
ScanlineStatus status;
ImageSpec image_spec;
FrameSpec frame_spec;
EXPECT_TRUE(reader->GetImageSpec(&image_spec, &status));
EXPECT_GT(image_spec.num_frames, first_invalid_frame);
// Frames up to the first invalid frame should have format RGB_888.
for (size_px frame = 0; frame < first_invalid_frame; ++frame) {
EXPECT_TRUE(reader->HasMoreFrames());
EXPECT_TRUE(reader->PrepareNextFrame(&status));
EXPECT_TRUE(reader->GetFrameSpec(&frame_spec, &status));
EXPECT_EQ(RGB_888, frame_spec.pixel_format);
}
// The invalid frame should have format RGBA_8888, and all its
// pixels, which are out of palette bounds, should be reported
// as transparent.
EXPECT_TRUE(reader->HasMoreFrames());
EXPECT_TRUE(reader->PrepareNextFrame(&status));
EXPECT_TRUE(reader->GetFrameSpec(&frame_spec, &status));
EXPECT_EQ(RGBA_8888, frame_spec.pixel_format);
const uint8_t* scanline;
for (size_px row = 0; row < frame_spec.height; ++row) {
EXPECT_TRUE(reader->HasMoreScanlines());
EXPECT_TRUE(
reader->ReadNextScanline(
reinterpret_cast<const void **>(&scanline),
&status));
for (size_px col = 0; col < frame_spec.width; ++col) {
int cmp_result = memcmp(scanline + RGBA_NUM_CHANNELS * col,
&kTransparentPixel,
RGBA_NUM_CHANNELS * sizeof(uint8_t));
EXPECT_EQ(0, cmp_result);
if (cmp_result != 0) {
// Return eagerly to avoid excessive output in case of error.
return;
}
}
}
EXPECT_FALSE(reader->HasMoreScanlines());
// We don't check any additional frames
}
TEST(InvalidPixels, TestOutOfRangePixelValueInLocalPalette) {
// This image has a global palette as well as local palettes for all
// frames. For frame #3, the local palette is only four colors long
// but has pixels referring to color #4, which is out of range.
CheckImageForOutOfBoundsPixel(kInvalidPixelLocalPaletteGif, 3);
}
TEST(InvalidPixels, TestOutOfRangePixelValueInGlobalPalette) {
// This image has a four-color global palette as well as eight-color
// local palettes for frames 0-2. Frame #3 does not have a local
// palette (so it uses the global one) but has pixels referring to
// color #4, which is out of range.
CheckImageForOutOfBoundsPixel(kInvalidPixelGlobalPaletteGif, 3);
}
} // namespace