こんにちは,モノトーンです。
今日は Apple platforms で画像を処理していくお話。
1. Core Graphics extensions
Core Graphics extensions を公開しています。
macOS (AppKit, Cocoa) と tvOS, iPadOS, iOS (UIKit, Cocoa Touch) の両方で扱えるように Core Graphics をベースに設計しています。
2. 利用例
2.1 利用例1: 画像の淵が背景色に近いとき,画像の淵に線を引く
まずは,簡単な流れをご紹介
enum BackgroundType { case light case dark } extension UIImage { func isNearestBackground(_ backgroundType: BackgroundType = .light) -> Bool? { guard let resizedImage = self.cgImage?.sRGB(to: (24, 24)), // sRGBで正規化します。8-bit RGBAになります。options: 引数でそれ以外にも変換できます let pixelProvider = resizedImage.pixelProvider.toHSV else { return nil } let conditionHandler: ([UInt8]) -> Bool if backgroundType == .dark { conditionHandler = { pixels in // 黒背景に近いケースなので, // S (彩度) 16 以下,B (明度) 16 以下, A (透過) 235以上 pixels[1] < 16 && pixels[2] < 16 && pixels[3] > 235 // 透過アイコンの場合,利用者は透過であることを想定しているので,淵を描画させない } } else { conditionHandler = { pixels in // 白背景に近いケースなので, // S (彩度) 16 以下,B (明度) 235 以上, A (透過) 235以上 pixels[1] < 16 && pixels[2] > 235 && pixels[3] > 235 // 透過アイコンの場合,利用者は透過であることを想定しているので,淵を描画させない } } // 淵のピクセルが 2/3 以上が背景に近い場合 return pixelProvider.ratio(onEdge: .unitEdge, conditionHandler: conditionHandler) > 0.666 } }
2.2 利用例2: 画像の全体の中央値が,どちら寄りかで,暗めの画像なのか明るめの画像なのか判定する。
まずは,簡単な流れをご紹介
Median は簡単に処理できるので,以下の関数を用います。この median は厳密 median じゃなくてもいいので,fast 版を call します。
extension UIImage { func isDarkImage() -> Bool? { guard let resizedImage = self.cgImage?.grayscale(to: (24, 24)), // gray(to:) は RGBA 画像を G の単一要素に変換します let pixels = resizedImage.pixelProvider.pixels else { return nil } // grayscale 化した単一要素の明るさが 160 以下の場合,暗い画像とみなします return pixels.map { $0[0] }.medianFast() < 160 } }
まとめ
白い淵が多い画像に枠線をつけたりとか用途は色々!
自分の場合は,画像の更新を「RxSwift.Observable
ちなみに角丸アイコンで線引く処理,高速化したいって人は,MTKit っていうのを公開しているので,それを Swift 側で継承して以下のようにするといいです。テーマ自体のステートは保持していない(そもそも dark か light によって判定式が変わるので,仮に同じ画像を複数箇所で使うなら,viewModel に dark state 持たせて処理させたほうがいいですね)実装です。
import MTKit enum MTBorderedRoundedRectProcessingType { case none case light case dark } final class MTBorderedRoundedRectProcessingImageView: MTRoundedRectProcessingImageView { var mode: MTBorderedRoundedRectProcessingType = .none { didSet { setNeedsProcessingImage() } } override func processImage(after context: CGContext, with rect: CGRect) { guard let path = maskPath else { return } switch mode { case .light: context.setStrokeColor(UIColor(white: 0.0, alpha: 0.3).cgColor) case .dark: context.setStrokeColor(UIColor(white: 1.0, alpha: 0.3).cgColor) default: return } context.setLineWidth(1.0) context.addPath(path) context.drawPath(using: .stroke) } }
色々投げやりですみません。
iOS 13 でさらに触りたいテーマが増えたので,Core Graphics extensions は一旦終わりにして,SwiftUI 触ろうと思います(一週間後に記事上がってくると思います)。
それでは〜