chromium/ui/gfx/image/image_mac_unittest.mm

// 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