Int型の変数をUInt8型にクランプして代入する

画像処理をしている場合、途中で Int 型の変数を経由して結果を UInt8 型のデータに戻すというシチュエーションが多くあります。

具体的にはInt型の変数の値が 256 以上になっていたら 255 にして、値が 0 未満(マイナス)になっていたら 0 にする、のようなコードです。

if文をつかってそういったコードをまともに書くのもよいですが、Swift ではコードを簡略化できるアプローチがあるので下記に紹介します。2種類あります。

UInt8 型キャストのときに clamping: を用いる

キャストするときに clampling: をつかえば、範囲外の値をキャストしようとしても、範囲内にクランプしてキャストしてくれます。UInt8 ならば 0 〜 255 にしてからキャストしてくれますので、実行時エラーの発生を抑えることができます。

            // 値を型にあわせてクランプする Debug0.
            Button( action: {

                let NUM_DATA = 4

                var data_a    : [UInt8] = []
                var data_b    : [UInt8] = []
                var data_diff : [UInt8] = []

                data_a    = Array( repeating: 0, count: NUM_DATA )
                data_b    = Array( repeating: 0, count: NUM_DATA )
                data_diff = Array( repeating: 0, count: NUM_DATA )

                data_a[0] = UInt8( 255 )
                data_a[1] = UInt8(   0 )
                data_a[2] = UInt8(   0 )
                data_a[3] = UInt8( 255 )

                data_b[0] = UInt8(   0 )
                data_b[1] = UInt8( 255 )
                data_b[2] = UInt8(   0 )
                data_b[3] = UInt8( 255 )

                let OFFSET: Int = 128
                var diff: Int = 0

                for n in 0 ..< NUM_DATA {

                    // ふたつのデータ系列の差分をとって、それにオフセットを加える.
                    diff = Int( data_a[n] ) - Int( data_b[n] ) + OFFSET

                    // 0 〜 255 の範囲に収めて UInt8 型に代入する.
                    let clamped_value: UInt8 = UInt8(clamping: diff)

                    // UInt8 型のデータを UInt8 型の配列に代入する.
                    data_diff[n] = clamped_value

                    // コンソールに出力する.
                    print( String( format: "%d,%d,%d,%d", data_a[n], data_b[n], diff, data_diff[n] ))

                }

            }){
                Text( "debug (clamping)" )
                    .foregroundColor( Color.white )
            }
            .padding()
            .background( Color.blue )

maxとminを組み合わせて用いる

これは、ああなるほどね、ってなるコロンブスの卵的やり方ですが、可読性がやや低いので初心者の人が混じっているチームで仕事している場合は避けた方がいいかもしれません。

min は二つの引数のうち小さいほうを選ぶメソッドです。max は二つの引数のうち大きいほうを選ぶメソッドです。これの引数を工夫して組み合わせると、得られる値が 0 〜 255 にクランプされます。あとはこれを UInt8 でキャストして代入してやればいいです。

            // 値を型にあわせてクランプする Debug1.
            Button( action: {

                let NUM_DATA = 4

                var data_a    : [UInt8] = []
                var data_b    : [UInt8] = []
                var data_diff : [UInt8] = []

                data_a    = Array( repeating: 0, count: NUM_DATA )
                data_b    = Array( repeating: 0, count: NUM_DATA )
                data_diff = Array( repeating: 0, count: NUM_DATA )

                data_a[0] = UInt8( 255 )
                data_a[1] = UInt8(   0 )
                data_a[2] = UInt8(   0 )
                data_a[3] = UInt8( 255 )

                data_b[0] = UInt8(   0 )
                data_b[1] = UInt8( 255 )
                data_b[2] = UInt8(   0 )
                data_b[3] = UInt8( 255 )

                let OFFSET: Int = 128
                var diff: Int = 0

                for n in 0 ..< NUM_DATA {

                    // ふたつのデータ系列の差分をとって、それにオフセットを加える.
                    diff = Int( data_a[n] ) - Int( data_b[n] ) + OFFSET

                    // 0 〜 255 の範囲に収めて UInt8 型に代入する.
                    let clamped_value: UInt8 = UInt8(max(0, min(255, diff)))

                    // UInt8 型のデータを UInt8 型の配列に代入する.
                    data_diff[n] = clamped_value

                    // コンソールに出力する.
                    print( String( format: "%d,%d,%d,%d", data_a[n], data_b[n], diff, data_diff[n] ))

                }

            }){
                Text( "debug1 (max,min)" )
                    .foregroundColor( Color.white )
            }
            .padding()
            .background( Color.blue )

上記の二つのコードは、下記のようなコンソール出力が得られます。どちらも同じです。

255,0,383,255
0,255,-127,0
0,0,128,128
255,255,128,128