[go: nahoru, domu]

blob: b50ebb2925d6a1c377174d7919cd9c36e47bee3a [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/web_applications/os_integration/icns_encoder.h"
#include <ImageIO/ImageIO.h>
#include <numeric>
#include "base/apple/foundation_util.h"
#include "base/apple/scoped_cftyperef.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/logging.h"
#include "base/mac/mac_util.h"
#include "base/path_service.h"
#include "skia/ext/skia_utils_mac.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/image/image.h"
namespace web_app {
TEST(IcnsEncoderTest, AppendRLEImageData) {
struct TestCase {
std::vector<unsigned char> input;
std::vector<unsigned char> expected;
const char* description;
} cases[] = {
{{}, {}, "Empty input"},
{{1, 2, 3, 4, 5}, {4, 1, 2, 3, 4, 5}, "Non compressible data"},
{{1, 2, 3, 3, 3, 4, 5},
{1, 1, 2, 0x80, 3, 1, 4, 5},
"Compressible run in the middle"},
{{5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6},
{0x82, 5, 0x83, 6},
"Multiple compressible runs"},
{std::vector<unsigned char>(300, 5),
{0xff, 5, 0xff, 5, 0xa5, 5},
"Long compressible data"},
{{}, {}, "Long uncompressible data"},
};
// Fill the input and expected output for the long uncompressible data case.
auto& long_case = cases[5];
long_case.input.resize(200);
std::iota(long_case.input.begin(), long_case.input.end(), 0);
long_case.expected = long_case.input;
long_case.expected.insert(long_case.expected.begin(), 0x7F);
long_case.expected.insert(long_case.expected.begin() + 0x7F + 2, 0x47);
for (const auto& c : cases) {
SCOPED_TRACE(c.description);
std::vector<unsigned char> result;
IcnsEncoder::AppendRLEImageData(c.input, &result);
EXPECT_EQ(c.expected, result);
}
}
namespace {
gfx::Image LoadTestPNG(const base::FilePath::CharType* path) {
base::FilePath data_root;
base::PathService::Get(base::DIR_SRC_TEST_DATA_ROOT, &data_root);
base::FilePath image_path = data_root.Append(path);
std::string png_data;
ReadFileToString(image_path, &png_data);
return gfx::Image::CreateFrom1xPNGBytes(
reinterpret_cast<const unsigned char*>(png_data.data()), png_data.size());
}
// Matcher used to verify that pixel values are near their expected value on
// older versions of Mac OS where non-opaque pixels don't always load with their
// correct pixel values.
testing::Matcher<uint8_t> ValueIsNear(uint8_t target) {
return testing::AllOf(testing::Le(target),
testing::Ge(target < 20 ? 0 : target - 20));
}
} // namespace
TEST(IcnsEncoderTest, RoundTrip) {
// Load a couple of test images.
gfx::Image basic_192 =
LoadTestPNG(FILE_PATH_LITERAL("chrome/test/data/web_apps/basic-192.png"));
ASSERT_TRUE(!basic_192.IsEmpty());
gfx::Image green_128 = LoadTestPNG(FILE_PATH_LITERAL(
"chrome/test/data/web_apps/standalone/128x128-green.png"));
ASSERT_TRUE(!green_128.IsEmpty());
gfx::Image basic_48 =
LoadTestPNG(FILE_PATH_LITERAL("chrome/test/data/web_apps/basic-48.png"));
ASSERT_TRUE(!basic_48.IsEmpty());
gfx::Image chromium_32 = LoadTestPNG(
FILE_PATH_LITERAL("chrome/test/data/web_apps/chromium-32.png"));
ASSERT_TRUE(!chromium_32.IsEmpty());
// Add the images to a IcnsEncoder.
IcnsEncoder encoder;
// 192x192 is not a supported image size
EXPECT_FALSE(encoder.AddImage(basic_192));
// 128, 48 and 32 are valid sizes, and should cover both encoding paths.
EXPECT_TRUE(encoder.AddImage(green_128));
EXPECT_TRUE(encoder.AddImage(basic_48));
EXPECT_TRUE(encoder.AddImage(chromium_32));
// Save the .icns file to disk.
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
base::FilePath icon_path = temp_dir.GetPath().AppendASCII("app.icns");
EXPECT_TRUE(encoder.WriteToFile(icon_path));
// Now use Image I/O methods to load the .icns file back in.
base::ScopedCFTypeRef<CFDictionaryRef> empty_dict(
CFDictionaryCreate(nullptr, nullptr, nullptr, 0, nullptr, nullptr));
base::ScopedCFTypeRef<CFURLRef> url = base::apple::FilePathToCFURL(icon_path);
base::ScopedCFTypeRef<CGImageSourceRef> source(
CGImageSourceCreateWithURL(url, nullptr));
// And make sure we got back the same images that were written to the file.
EXPECT_EQ(3u, CGImageSourceGetCount(source));
for (size_t i = 0; i < CGImageSourceGetCount(source); ++i) {
base::ScopedCFTypeRef<CGImageRef> cg_image(
CGImageSourceCreateImageAtIndex(source, i, empty_dict));
SkBitmap bitmap = skia::CGImageToSkBitmap(cg_image);
EXPECT_EQ(bitmap.width(), bitmap.height());
EXPECT_TRUE(bitmap.width() == 32 || bitmap.width() == 48 ||
bitmap.width() == 128)
<< "Loaded width was " << bitmap.width();
SCOPED_TRACE(bitmap.width());
SkBitmap reference = bitmap.width() == 32 ? chromium_32.AsBitmap()
: bitmap.width() == 48 ? basic_48.AsBitmap()
: green_128.AsBitmap();
for (int y = 0; y < bitmap.height(); ++y) {
std::vector<testing::Matcher<uint8_t>> expected_r, expected_g, expected_b,
expected_a;
std::vector<uint8_t> bitmap_r, bitmap_g, bitmap_b, bitmap_a;
for (int x = 0; x < bitmap.width(); ++x) {
SkColor expected_c = reference.getColor(x, y);
uint8_t alpha = SkColorGetA(expected_c);
expected_a.push_back(testing::Eq(alpha));
expected_r.push_back(testing::Eq(SkColorGetR(expected_c)));
expected_g.push_back(testing::Eq(SkColorGetG(expected_c)));
expected_b.push_back(testing::Eq(SkColorGetB(expected_c)));
SkColor bitmap_c = bitmap.getColor(x, y);
bitmap_a.push_back(SkColorGetA(bitmap_c));
bitmap_r.push_back(SkColorGetR(bitmap_c));
bitmap_g.push_back(SkColorGetG(bitmap_c));
bitmap_b.push_back(SkColorGetB(bitmap_c));
}
EXPECT_THAT(bitmap_r, testing::ElementsAreArray(expected_r))
<< "Row " << y;
EXPECT_THAT(bitmap_g, testing::ElementsAreArray(expected_g))
<< "Row " << y;
EXPECT_THAT(bitmap_b, testing::ElementsAreArray(expected_b))
<< "Row " << y;
EXPECT_THAT(bitmap_a, testing::ElementsAreArray(expected_a))
<< "Row " << y;
}
}
}
} // namespace web_app