1// Copyright 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "skia/ext/skia_utils_ios.h"
6
7#import <ImageIO/ImageIO.h>
8#include <stddef.h>
9#include <stdint.h>
10#import <UIKit/UIKit.h>
11
12#include "base/ios/ios_util.h"
13#include "base/logging.h"
14#include "base/mac/scoped_cftyperef.h"
15#include "base/stl_util.h"
16#include "third_party/skia/include/utils/mac/SkCGUtils.h"
17
18namespace {
19
20const uint8_t kICOHeaderMagic[4] = {0x00, 0x00, 0x01, 0x00};
21
22// Returns whether the data encodes an ico image.
23bool EncodesIcoImage(NSData* image_data) {
24  if (image_data.length < base::size(kICOHeaderMagic))
25    return false;
26  return memcmp(kICOHeaderMagic, image_data.bytes,
27                base::size(kICOHeaderMagic)) == 0;
28}
29
30}  // namespace
31
32namespace skia {
33
34SkBitmap CGImageToSkBitmap(CGImageRef image, CGSize size, bool is_opaque) {
35  SkBitmap bitmap;
36  if (!image)
37    return bitmap;
38
39  if (!bitmap.tryAllocN32Pixels(size.width, size.height, is_opaque))
40    return bitmap;
41
42  void* data = bitmap.getPixels();
43
44  // Allocate a bitmap context with 4 components per pixel (BGRA). Apple
45  // recommends these flags for improved CG performance.
46#define HAS_ARGB_SHIFTS(a, r, g, b) \
47            (SK_A32_SHIFT == (a) && SK_R32_SHIFT == (r) \
48             && SK_G32_SHIFT == (g) && SK_B32_SHIFT == (b))
49#if defined(SK_CPU_LENDIAN) && HAS_ARGB_SHIFTS(24, 16, 8, 0)
50  base::ScopedCFTypeRef<CGColorSpaceRef> color_space(
51      CGColorSpaceCreateDeviceRGB());
52  base::ScopedCFTypeRef<CGContextRef> context(CGBitmapContextCreate(
53      data,
54      size.width,
55      size.height,
56      8,
57      size.width * 4,
58      color_space,
59      kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host));
60#else
61#error We require that Skia's and CoreGraphics's recommended \
62       image memory layout match.
63#endif
64#undef HAS_ARGB_SHIFTS
65
66  DCHECK(context);
67  if (!context)
68    return bitmap;
69
70  CGRect imageRect = CGRectMake(0.0, 0.0, size.width, size.height);
71  CGContextSetBlendMode(context, kCGBlendModeCopy);
72  CGContextDrawImage(context, imageRect, image);
73
74  return bitmap;
75}
76
77UIImage* SkBitmapToUIImageWithColorSpace(const SkBitmap& skia_bitmap,
78                                         CGFloat scale,
79                                         CGColorSpaceRef color_space) {
80  if (skia_bitmap.isNull())
81    return nil;
82
83  // First convert SkBitmap to CGImageRef.
84  base::ScopedCFTypeRef<CGImageRef> cg_image(
85      SkCreateCGImageRefWithColorspace(skia_bitmap, color_space));
86
87  // Now convert to UIImage.
88  return [UIImage imageWithCGImage:cg_image.get()
89                             scale:scale
90                       orientation:UIImageOrientationUp];
91}
92
93std::vector<SkBitmap> ImageDataToSkBitmaps(NSData* image_data) {
94  DCHECK(image_data);
95
96  // On iOS 8.1.1 |CGContextDrawImage| crashes when processing images included
97  // in .ico files that are 88x88 pixels or larger (http://crbug.com/435068).
98  bool skip_images_88x88_or_larger =
99      base::ios::IsRunningOnOrLater(8, 1, 1) && EncodesIcoImage(image_data);
100
101  base::ScopedCFTypeRef<CFDictionaryRef> empty_dictionary(
102      CFDictionaryCreate(NULL, NULL, NULL, 0, NULL, NULL));
103  std::vector<SkBitmap> frames;
104
105  base::ScopedCFTypeRef<CGImageSourceRef> source(
106      CGImageSourceCreateWithData((CFDataRef)image_data, empty_dictionary));
107
108  size_t count = CGImageSourceGetCount(source);
109  for (size_t index = 0; index < count; ++index) {
110    base::ScopedCFTypeRef<CGImageRef> cg_image(
111        CGImageSourceCreateImageAtIndex(source, index, empty_dictionary));
112
113    CGSize size = CGSizeMake(CGImageGetWidth(cg_image),
114                             CGImageGetHeight(cg_image));
115    if (size.width >= 88 && size.height >= 88 && skip_images_88x88_or_larger)
116      continue;
117
118    const SkBitmap bitmap = CGImageToSkBitmap(cg_image, size, false);
119    if (!bitmap.empty())
120      frames.push_back(bitmap);
121  }
122
123  DLOG_IF(WARNING, frames.size() != count) << "Only decoded " << frames.size()
124      << " frames for " << count << " expected.";
125  return frames;
126}
127
128UIColor* UIColorFromSkColor(SkColor color) {
129  return [UIColor colorWithRed:SkColorGetR(color) / 255.0f
130                         green:SkColorGetG(color) / 255.0f
131                          blue:SkColorGetB(color) / 255.0f
132                         alpha:SkColorGetA(color) / 255.0f];
133}
134
135}  // namespace skia
136