31
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【SwiftUI】BlendMode で View を合成する

Last updated at Posted at 2020-07-12

はじめに

この記事では、Adobe Photoshop などのデザインツールでおなじみのブレンドモードについて扱います。
コードと実行結果は SwiftUI のものですが、ブレンドモードは Core Graphics が提供しているので、SwiftUI を使わない開発でも知識としては使えると思います。

さて、本題です。
SwiftUI で2つのビューをブレンドしてみます。こんな感じのイメージです。
はじめに.jpeg
緑が透過なビューの場合は単純に上に載せるだけなので、非透過な緑色のビューとの合成を考えます。
この場合のサンプルコードはこちら。

struct ContentView: View {
    var body: some View {
        ZStack {
            Image("lenna")
                .resizable()
                .scaledToFit()
            Rectangle()
                .fill(Color.green)
                .blendMode(.multiply) // ← ココ!
        }
        .edgesIgnoringSafeArea(.all)
    }
}

簡単な説明

  • ZStackImage の上に Rectangle を載せる
    • Image を配置
      • アスペクト比を維持してビューにフィット
    • Rectangle を配置
      • green で塗りつぶし
      • 乗算ブレンド
  • セーフエリアを無視して配置

実行結果

はじめに2.jpeg

BlendMode を指定して配置するだけなので、とても簡単ですが、次のことが気になりました。

  • 乗算ブレンド .multiply とはどのような合成方法なのか?
  • 他の BlendMode を指定した場合はどのような結果が得られるのか?

ということで、本記事では各種ブレンドモードについての情報と実際の表示結果を載せました。
表示結果は SwiftUI のプレビュー機能を使用しています。

環境

  • Xcode Version 11.5 (11E608c)

BlendMode

SwiftUI の API リファレンスには概要が記載されていなかったので、Core Graphics の API リファレンスも参照しました。次のように記載されていました。

You can find more information on blend modes, including examples of images produced using them, and many mathematical descriptions of the modes, in PDF Reference, Fourth Edition, Version 1.5, Adobe Systems, Inc. If you are a former QuickDraw developer, it may be helpful for you to think of blend modes as an alternative to transfer modes1

Adobe Systems の PDF Reference, Fourth Edition に正確な情報が記載されているとのことです。
この資料には、全てではないですが、各ブレンドモードの数式も記載がありました。

PDF Reference に記載のあるブレンドモード

BlendMode CGBlendMode 名称
normal kCGBlendModeNormal 通常
multiply kCGBlendModeMultiply 乗算
screen kCGBlendModeScreen スクリーン
overlay kCGBlendModeOverlay オーバーレイ
darken kCGBlendModeDarken 比較(暗)
lighten kCGBlendModeLighten 比較(明)
colorDodge kCGBlendModeColorDodge 覆い焼きカラー
colorBurn kCGBlendModeColorBurn 焼き込みカラー
softLight kCGBlendModeSoftLight ソフトライト
hardLight kCGBlendModeHardLight ハードライト
difference kCGBlendModeDifference 差の絶対値
exclusion kCGBlendModeExclusion 除外
hue kCGBlendModeHue 色相
saturation kCGBlendModeSaturation 彩度
color kCGBlendModeColor カラー
luminosity kCGBlendModeLuminosity 輝度

以下、数学的な説明が必要な場合は、PDF Reference, Fourth Edition で記載されていた次の変数を使います。

変数 意味
$C_b$ 背景の色(基本色)
$C_s$ 前面の色(合成色)
$C(C_b, C_s)$ ブレンド関数

上記変数は各色成分をもつベクトルです。また、今回扱う関数は、各色成分ごとに分離可能な関数です。

normal

通常

各ピクセルを編集またはペイントして結果色を作成します。これは、初期設定のモードです(通常モードは、モノクロ 2 階調画像やインデックスカラー画像で作業するときには、2 階調化と呼ばれます)。2

ブレンド関数は次の通りです。前面をそのまま反映します。

B(c_b,c_s) = c_s
Screen Shot 2020-07-12 at 18.43.59.png Screen Shot 2020-07-12 at 19.47.38.png

multiply

乗算

各チャンネル内のカラー情報に基づき、基本色と合成色を乗算します。結果色は暗いカラーになります。どのカラーも、ブラックで乗算すると結果はブラックになります。どのカラーも、ホワイトで乗算した場合は変更されません。ブラックまたはホワイト以外のカラーでペイントしている場合、ペイントツールで繰り返しストロークを描くとカラーは徐々に暗くなります。この効果は、複数のマーカーペンで描画したような効果が得られます。3

ブレンド関数は次の通りです。背景カラーと前面カラーの RGB の構成要素をそれぞれ乗算します。

B(c_b,c_s) = c_b × c_s

背景ビューと前面ビューを入替えても同じ結果を得ることができます。

色の各成分は、0 以上 1 以下の小数です。特に ブラック(0, 0, 0), ホワイト(1, 1, 1) なので、
ブラックで乗算すると結果はブラック、ホワイトで乗算した場合は変更されないことがわかります。

また、0 以上 1 未満の数は乗算を繰り返すほど、0 に近づくので、
multiply ブレンドモードを繰り返すほど徐々に暗くなります。
Screen Shot 2020-07-12 at 18.44.21.png
Screen Shot 2020-07-12 at 19.47.52.png

screen

スクリーン

各チャンネル内のカラー情報に基づき、合成色と基本色を反転したカラーを乗算します。結果色は明るいカラーになります。ブラックでスクリーニングすると、カラーは変更されません。ホワイトでスクリーニングすると、ホワイトになります。この効果は、複数の写真スライドを重ね合わせて投影したような効果が得られます。4

ブレンド関数は次の通りです。

B(c_b,c_s) = 1 – (1–c_b)×(1–c_s)

背景カラーと前面カラーをそれぞれ反転させたものを乗算して、その結果の値を反転させます。

この式から、multiply と逆の性質を得ることができます。

  • ブラックとスクリーンをすると変更されない
  • ホワイトとスクリーンをすると結果はホワイト
  • 繰り返すほど徐々に明るくなる
Screen Shot 2020-07-12 at 18.44.40.png Screen Shot 2020-07-12 at 19.48.02.png

overlay

オーバーレイ

基本色に応じて、カラーを乗算またはスクリーンします。基本色のハイライトおよびシャドウを保持しながら、パターンまたはカラーを既存のピクセルに重ねます。基本色は、置き換えられませんが、合成色と混合されて基本色の明るさまたは暗さを反映します。5

Screen Shot 2020-07-12 at 18.44.59.png Screen Shot 2020-07-12 at 19.48.20.png

darken

比較(暗)

各チャンネル内のカラー情報に基づき、基本色または合成色のいずれか暗い方を結果色として選択します。合成色よりも明るいピクセルが置き換えられ、合成色よりも暗いピクセルは変更されません。6

ブレンド関数は次の通りです。

B(c_b, c_s) = min(c_b, c_s)
Screen Shot 2020-07-12 at 18.45.19.png Screen Shot 2020-07-12 at 19.48.58.png

lighten

比較(明)

各チャンネル内のカラー情報に基づき、基本色または合成色のいずれか明るい方を結果色として選択します。合成色よりも暗いピクセルが置き換えられ、合成色よりも明るいピクセルは変更されません。7

ブレンド関数は次の通りです。

B(c_b, c_s) = max(c_b, c_s)
Screen Shot 2020-07-12 at 18.45.34.png Screen Shot 2020-07-12 at 19.49.10.png

colorDodge

覆い焼きカラー

各チャンネルのカラー情報に基づき、基本色を明るくして基本色と合成色のコントラストを落とし、合成色を反映します。ブラックと合成しても変化はありません。8

Screen Shot 2020-07-12 at 18.45.48.png

colorBurn

焼き込みカラー

各チャンネルのカラー情報に基づき、基本色を暗くして基本色と合成色のコントラストを強くし、合成色を反映します。ホワイトで合成した場合は、何も変更されません。9

Screen Shot 2020-07-12 at 18.46.01.png

softLight

ソフトライト

合成色に応じて、カラーを暗くまたは明るくします。画像上でスポットライトを照らしたような効果が得られます。合成色(光源)が 50 %グレーよりも明るい場合、画像は覆い焼きされたかのように明るくなります。合成色が 50 %グレーよりも暗い場合、画像は焼き込んだように暗くなります。純粋な黒または白でペイントすると、その部分の明暗ははっきりしますが、純粋な黒または白にはなりません。10

Screen Shot 2020-07-12 at 18.46.14.png

hardLight

ハードライト

合成色に応じて、カラーを乗算またはスクリーンします。画像上で直接スポットライトを照らしたような効果が得られます。合成色(光源)が 50 %グレーよりも明るい場合、画像はスクリーンされたかのように明るくなります。これは、画像にハイライトを追加するときに役立ちます。合成色が 50 %グレーよりも暗い場合、画像は乗算されたかのように暗くなります。これは、画像にシャドウを追加するときに役立ちます。純粋なホワイトまたはブラックでペイントすると、純粋なホワイトまたはブラックになります。11

Screen Shot 2020-07-12 at 18.46.26.png

difference

差の絶対値

各チャンネル内のカラー情報に基づいて、合成色を基本色から取り除くか、基本色を合成色から取り除きます。明るさの値の大きい方のカラーから小さい方のカラーを取り除きます。ホワイトと合成すると基本色の値が反転しますが、ブラックと合成しても変化はありません。12

ブレンド関数は次の通りです。

B(c_b, c_s) = | c_b – c_s |

ホワイトとブレンドすると色反転ができます。

Screen Shot 2020-07-12 at 18.46.43.png Screen Shot 2020-07-12 at 19.49.32.png

exclusion

除外

差の絶対値モードと似ていますが、効果のコントラストはより低くなります。ホワイトと合成すると、基本色の値が反転しますが、ブラックと合成しても変化はありません。13

Screen Shot 2020-07-12 at 18.46.59.png Screen Shot 2020-07-12 at 19.49.47.png

hue

色相

ベースカラーの輝度と彩度、およびブレンドカラーの色相を持つ最終カラーが作成されます。14

saturation

彩度

基本色の輝度と色相および合成色の彩度を使用して、結果色を作成します。このモードで彩度ゼロ(グレー)の領域をペイントした場合は、何も変更されません。15

color

カラー

基本色の輝度と、合成色の色相および彩度を使用して、結果色を作成します。これにより、画像内のグレーレベルが保持され、モノクロ画像のカラー化およびカラー画像の階調化に役立ちます。16

luminosity

輝度

基本色の色相および彩度と、合成色の輝度を使用して、結果色を作成します。このモードでは、カラーモードの反対の効果が作成されます。17

サンプルコード

今回のスクリーンショットで使ったコードはこちら。

表示データ

Item.swift
import SwiftUI

struct Item: Hashable {
    let mode: BlendMode, name: String

    static var items: [Item] = [
        Item(mode: .normal,     name: ".normal"),
        Item(mode: .multiply,   name: ".multiply"),
        Item(mode: .screen,     name: ".screen"),
        Item(mode: .overlay,    name: ".overlay"),
        Item(mode: .darken,     name: ".darken"),
        Item(mode: .lighten,    name: ".lighten"),
        Item(mode: .colorDodge, name: ".colorDodge"),
        Item(mode: .colorBurn,  name: ".colorBurn"),
        Item(mode: .softLight,  name: ".softLight"),
        Item(mode: .hardLight,  name: ".hardLight"),
        Item(mode: .difference, name: ".difference"),
        Item(mode: .exclusion,  name: ".exclusion"),
    ]
}

画像と色のブレンド

ImageBlendView.swift
import SwiftUI

struct ImageBlendView: View {
    var color: Color
    var mode: BlendMode

    var body: some View {
        ZStack {
            Image("lenna").resizable().scaledToFit()
            Rectangle().fill(color).blendMode(mode)
        }
    }
}

struct ImageBlendViewSamples: View {
    var mode: BlendMode
    var colors: [Color] = [.clear, .red, .green, .blue, .black, .white]
    var body: some View {
        HStack(spacing: 0) { [colors, mode] in
            ForEach(0..<colors.count) { i in
                ImageBlendView(color: colors[i], mode: mode)
            }
        }
    }
}

// MARK: - PreviewProvider
struct ImageBlendViewSamples_Previews: PreviewProvider {
    static var previews: some View {
        Group { [items = Item.items] in
            ForEach(0..<items.count) { i in
                ImageBlendViewSamples(mode: items[i].mode)
                    .previewDisplayName(items[i].name)
            }
        }
        .previewLayout(.fixed(width: 100 * 6, height: 100))
    }
}

円のブレンド

import SwiftUI

struct ColorBlendView: View {
    var mode: BlendMode
    var background: Color
    var colors: [Color]
    var body: some View {
        GeometryReader<AnyView> { [mode, colors] geometry in
            let edge = min(geometry.size.width, geometry.size.height)/2
            let offset = edge/3
            let arg: (Int) -> CGFloat = { 2*(.pi)*CGFloat($0)/CGFloat(colors.count) - (.pi)/2 }
            return AnyView(
                ZStack {
                    ForEach(0..<colors.count) { index in
                        Circle()
                            .fill(colors[index])
                            .frame(width: edge)
                            .offset(x: offset*cos(arg(index)), y: offset*sin(arg(index)))
                            .blendMode(mode)
                    }
                }
                .frame(width: geometry.size.width, height: geometry.size.height)
            )
        }
        .background(background)
        .edgesIgnoringSafeArea(.all)
    }
}

struct ColorBlendViewSamples: View {
    var mode: BlendMode
    var foregrounds: [[Color]] = [
        [.red, .green, .blue],
        .init(repeating: .green, count: 7)
    ]
    var backgrounds: [Color] = [
        Color(red: 0, green: 0, blue: 0),
        Color(red: 1, green: 1, blue: 1),
    ]
    var body: some View {
        HStack(spacing: .zero) { [mode, foregrounds, backgrounds] in
            ForEach(0..<backgrounds.count) { i in
                ForEach(0..<foregrounds.count) { j in
                    ColorBlendView(mode: mode, background: backgrounds[i], colors: foregrounds[j])
                }
            }
        }
    }
}

// MARK: - PreviewProvider
struct ColorBlendViewSamples_Previews: PreviewProvider {
    static var previews: some View {
        Group { [items = Item.items] in
            ForEach(0..<items.count) { i in
                ColorBlendViewSamples(mode: items[i].mode)
                    .previewDisplayName(items[i].name)
            }
        }
        .previewLayout(.fixed(width: 120 * 4, height: 120))
    }
}

参考

  1. CGBlendMode | Apple Developer Documentation

  2. Adobe Photoshop での描画モード

  3. Adobe Photoshop での描画モード

  4. Adobe Photoshop での描画モード

  5. Adobe Photoshop での描画モード

  6. Adobe Photoshop での描画モード

  7. Adobe Photoshop での描画モード

  8. Adobe Photoshop での描画モード

  9. Adobe Photoshop での描画モード

  10. Adobe Photoshop での描画モード

  11. Adobe Photoshop での描画モード

  12. Adobe Photoshop での描画モード

  13. Adobe Photoshop での描画モード

  14. Adobe Photoshop での描画モード

  15. Adobe Photoshop での描画モード

  16. Adobe Photoshop での描画モード

  17. Adobe Photoshop での描画モード

31
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
31
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?