// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/354829279): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif
#include <Cocoa/Cocoa.h>
#include <stddef.h>
#include <vector>
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/resource/resource_scale_factor.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/image/image_png_rep.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/gfx/image/image_skia_rep.h"
#include "ui/gfx/image/image_skia_util_mac.h"
#include "ui/gfx/image/image_unittest_util.h"
namespace {
// Returns true if the structure of |ns_image| matches the structure
// described by |width|, |height|, and |scales|.
// The structure matches if:
// - |ns_image| is not nil.
// - |ns_image| has NSImageReps of |scales|.
// - Each of the NSImageReps has a pixel size of [|ns_image| size] *
// scale.
bool NSImageStructureMatches(
NSImage* ns_image,
int width,
int height,
const std::vector<float>& scales) {
if (!ns_image || ns_image.size.width != width ||
ns_image.size.height != height ||
ns_image.representations.count != scales.size()) {
return false;
}
for (float scale : scales) {
bool found_match = false;
for (size_t j = 0; j < ns_image.representations.count; ++j) {
NSImageRep* ns_image_rep = [ns_image representations][j];
if (ns_image_rep && ns_image_rep.pixelsWide == width * scale &&
ns_image_rep.pixelsHigh == height * scale) {
found_match = true;
break;
}
}
if (!found_match)
return false;
}
return true;
}
NSBitmapImageRep* BitmapImageRep(int width, int height) {
NSBitmapImageRep* image_rep =
[[NSBitmapImageRep alloc] initWithBitmapDataPlanes:nullptr
pixelsWide:width
pixelsHigh:height
bitsPerSample:8
samplesPerPixel:3
hasAlpha:NO
isPlanar:NO
colorSpaceName:NSDeviceRGBColorSpace
bitmapFormat:0
bytesPerRow:0
bitsPerPixel:0];
unsigned char* image_rep_data = image_rep.bitmapData;
for (int i = 0; i < width * height * 3; ++i) {
image_rep_data[i] = 255;
}
return image_rep;
}
class ImageMacTest : public testing::Test {
public:
ImageMacTest() = default;
ImageMacTest(const ImageMacTest&) = delete;
ImageMacTest& operator=(const ImageMacTest&) = delete;
~ImageMacTest() override = default;
private:
ui::test::ScopedSetSupportedResourceScaleFactors supported_scale_factors_{
{ui::k100Percent, ui::k200Percent}};
};
namespace gt = gfx::test;
TEST_F(ImageMacTest, MultiResolutionNSImageToImageSkia) {
const int kWidth1x = 10;
const int kHeight1x = 12;
const int kWidth2x = 20;
const int kHeight2x = 24;
NSBitmapImageRep* ns_image_rep1 = BitmapImageRep(kWidth1x, kHeight1x);
NSBitmapImageRep* ns_image_rep2 = BitmapImageRep(kWidth2x, kHeight2x);
NSImage* ns_image =
[[NSImage alloc] initWithSize:NSMakeSize(kWidth1x, kHeight1x)];
[ns_image addRepresentation:ns_image_rep1];
[ns_image addRepresentation:ns_image_rep2];
gfx::Image image(ns_image);
EXPECT_EQ(1u, image.RepresentationCount());
const gfx::ImageSkia* image_skia = image.ToImageSkia();
std::vector<float> scales;
scales.push_back(1.0f);
scales.push_back(2.0f);
EXPECT_TRUE(gt::ImageSkiaStructureMatches(*image_skia, kWidth1x, kHeight1x,
scales));
// ToImageSkia should create a second representation.
EXPECT_EQ(2u, image.RepresentationCount());
}
// Test that converting to an ImageSkia from an NSImage with scale factors other
// than 1x and 2x results in an ImageSkia with scale factors 1x and 2x;
TEST_F(ImageMacTest, UnalignedMultiResolutionNSImageToImageSkia) {
const int kWidth1x = 10;
const int kHeight1x= 12;
const int kWidth4x = 40;
const int kHeight4x = 48;
NSBitmapImageRep* ns_image_rep4 = BitmapImageRep(kWidth4x, kHeight4x);
NSImage* ns_image =
[[NSImage alloc] initWithSize:NSMakeSize(kWidth1x, kHeight1x)];
[ns_image addRepresentation:ns_image_rep4];
gfx::Image image(ns_image);
EXPECT_EQ(1u, image.RepresentationCount());
const gfx::ImageSkia* image_skia = image.ToImageSkia();
std::vector<float> scales;
scales.push_back(1.0f);
scales.push_back(2.0f);
EXPECT_TRUE(gt::ImageSkiaStructureMatches(*image_skia, kWidth1x, kHeight1x,
scales));
// ToImageSkia should create a second representation.
EXPECT_EQ(2u, image.RepresentationCount());
}
TEST_F(ImageMacTest, MultiResolutionImageSkiaToNSImage) {
const int kWidth1x = 10;
const int kHeight1x= 12;
const int kWidth2x = 20;
const int kHeight2x = 24;
gfx::ImageSkia image_skia;
image_skia.AddRepresentation(gfx::ImageSkiaRep(
gt::CreateBitmap(kWidth1x, kHeight1x), 1.0f));
image_skia.AddRepresentation(gfx::ImageSkiaRep(
gt::CreateBitmap(kWidth2x, kHeight2x), 2.0f));
gfx::Image image(image_skia);
EXPECT_EQ(1u, image.RepresentationCount());
EXPECT_EQ(2u, image.ToImageSkia()->image_reps().size());
NSImage* ns_image = image.ToNSImage();
std::vector<float> scales;
scales.push_back(1.0f);
scales.push_back(2.0f);
EXPECT_TRUE(NSImageStructureMatches(ns_image, kWidth1x, kHeight1x, scales));
// Request for NSImage* should create a second representation.
EXPECT_EQ(2u, image.RepresentationCount());
}
TEST_F(ImageMacTest, MultiResolutionPNGToNSImage) {
const int kSize1x = 25;
const int kSize2x = 50;
scoped_refptr<base::RefCountedMemory> bytes1x = gt::CreatePNGBytes(kSize1x);
scoped_refptr<base::RefCountedMemory> bytes2x = gt::CreatePNGBytes(kSize2x);
std::vector<gfx::ImagePNGRep> image_png_reps;
image_png_reps.emplace_back(bytes1x, 1.0f);
image_png_reps.emplace_back(bytes2x, 2.0f);
gfx::Image image(image_png_reps);
NSImage* ns_image = image.ToNSImage();
std::vector<float> scales;
scales.push_back(1.0f);
scales.push_back(2.0f);
EXPECT_TRUE(NSImageStructureMatches(ns_image, kSize1x, kSize1x, scales));
// Converting from PNG to NSImage should not go through ImageSkia.
EXPECT_FALSE(image.HasRepresentation(gfx::Image::kImageRepSkia));
// Convert to ImageSkia to check pixel contents of NSImageReps.
gfx::ImageSkia image_skia = gfx::ImageSkiaFromNSImage(ns_image);
EXPECT_TRUE(gt::ArePNGBytesCloseToBitmap(
*bytes1x, image_skia.GetRepresentation(1.0f).GetBitmap(),
gt::MaxColorSpaceConversionColorShift()));
EXPECT_TRUE(gt::ArePNGBytesCloseToBitmap(
*bytes2x, image_skia.GetRepresentation(2.0f).GetBitmap(),
gt::MaxColorSpaceConversionColorShift()));
}
} // namespace