画像データ(符号なし32bitsカラー)を画面に表示する

この記事は下記の記事の発展です。よってコード差分の説明にとどめますので、先にこちらを読んでいただいたほうがいいと思います。

画像データ(符号なし8bits)を画面に表示する

符号なし8bitsデータを画面に表示する方法を紹介します。

カラーの画像データは、赤成分が符号なし8bits、緑成分が符号なし8bits、青成分が符号なし8bits、透明度成分が符号なし8bits で管理されます。昔から画像処理業界では「RGBA32bits」データとか「R8G8B8A8」データと略称されます。

下記のコードの解説をします。

59行目、カラーデータを格納するための画像メモリ領域を確保します。RGBAそれぞれ8bitsですから4倍のメモリ量が必要です。
62行目、任意のデータを画像メモリ領域にしこみます。
67行目、CGContext を作成します。bytesPerRow: space: bitmapInfo: の引数がグレー画像のときと違うことに注目してください。
105〜134行目、連続した R8G8B8A8 の UInt8 として画像データをしこんでいます。

import SwiftUI

struct ContentView: View {

    // 画像データ.
    let DATA_W = 256
    let DATA_H = 256
    @State var Data000: UnsafeMutablePointer<UInt8>?

    // 画像データを画面に表示するためのグッズ.
    @State var TheCGContext: CGContext? = nil
    @State var TheUIImage: UIImage? = nil

    var body: some View {

        ZStack{

            // 最背面は背景を塗りつぶす.
            Rectangle()
                .foregroundColor( .gray )

            // 中層面で画像を表示する.
            if let TheUIImageUnwrapped = TheUIImage {

                let ui_img_w = CGFloat( DATA_W )
                let ui_img_h = CGFloat( DATA_H )

                Image( uiImage: TheUIImageUnwrapped )
                    .frame( width: ui_img_w, height: ui_img_h )

            }
            else
            {
                // TheUIImage が適切に生成されていなければ何も表示しない.
                Spacer()
            }

            // 最前面の画面上部にデバッグ情報を表示する.
            VStack{

                let scr_w = UIScreen.main.bounds.size.width
                let scr_h = UIScreen.main.bounds.size.height

                Text( String( format: "ScreenWH is %.1f * %.1f", scr_w, scr_h ))
                    .foregroundColor( .white )
                Text( String( format: "DataWH is %d * %d", DATA_W, DATA_H ))
                    .foregroundColor( .white )

            }
            .frame( maxWidth: .infinity, maxHeight: .infinity, alignment: .top )
            .padding( .top )

        } // ZStack end.
        .onAppear{

            // メモリを確保する.
            let numpix = DATA_W * DATA_H
            let alloc_bytes = numpix * 4 // R8G8B8A8.
            Data000 = UnsafeMutablePointer<UInt8>.allocate(capacity: alloc_bytes)

            // 確保したメモリに、ななめグラデーションデータを仕込む.
            SetGradDataBits32( Data000!, DATA_W, DATA_H )

            let one_scan_bytes = DATA_W * 4

            // ビットマップコンテキストを作成する.
            TheCGContext = CGContext(
                data: Data000,
                width: DATA_W,
                height: DATA_H,
                bitsPerComponent: 8,
                bytesPerRow: one_scan_bytes, // 8bits のときとここが違う.
                space: CGColorSpaceCreateDeviceRGB(), // 8bits のときとここが違う.
                bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue  // 8bits のときとここが違う.
            )

            if TheCGContext != nil
            {
                let the_cgimage = TheCGContext!.makeImage()
                if let the_cgimage = the_cgimage
                {
                    TheUIImage = UIImage( cgImage: the_cgimage )
                }
                else
                {
                    // デバッグコンソールに出力する.
                    print( "the_cgimage is nil.")
                }
            }
            else
            {
                // デバッグコンソールに出力する.
                print( "the_cgcontext is nil." )
            }

        }
        .onDisappear {
            // メモリを破棄する.
            Data000?.deallocate()
        }

    } // some View end.

    // 画像にデータを仕込むメソッド.
    func SetGradDataBits32(
            _ data: UnsafeMutablePointer<UInt8>,
            _ width: Int,
            _ height: Int
            )->Void {

        let w = width
        let h = height

        var value: UInt8 = 0

        let one_scan_bytes = w * 4

        for j in 0 ..< h{

            var adrs = ( one_scan_bytes * j )

            for i in 0 ..< w{
                let tmp = i + j
                value = UInt8( tmp % 256 )
                data[ adrs ] = 0x00;  adrs += 1; // R
                data[ adrs ] = 0x00;  adrs += 1; // G
                data[ adrs ] = value; adrs += 1; // B
                data[ adrs ] = 0xff;  adrs += 1; // A
            }
        }

        return

    }

}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

コードとしては、これで動くのですがデータを処理するメソッドの SetGradDataBits32() の中が、連続した RGBA としてのデータ解釈なのでなんとも煩雑ですね。

たとえば画像データを RGBA をひとまとめとした画素の構造体配列として扱えば、画像処理は下記のコードのように簡単にできるはずです。なんとかならないものでしょうか?

struct STRUCT_RGBA {
    var R: UInt8
    var G: UInt8
    var B: UInt8
    var A: UInt8
}

// 画像にデータを仕込むメソッド.
func SetGradDataRGBA(
        _ data: UnsafeMutablePointer<STRUCT_RGBA>,
        _ width: Int,
        _ height: Int
        )->Void {

    let w = width
    let h = height

    var value: UInt8 = 0

    for j in 0 ..< h{
        var adrs = w * j
        for i in 0 ..< w{
            let tmp = i + j
            value = UInt8( tmp % 256 )
            data[ adrs ].R = 0x00;  // R
            data[ adrs ].G = 0x00;  // G
            data[ adrs ].B = value; // B
            data[ adrs ].A = 0xff;  // A
            adrs += 1
        }
    }

    return

}

なんとかなります!構造体配列として扱い、わかりやすいコードを使うことが可能です。下記の記事でその方法をご紹介します。

画像データ(R8G8B8A8構造体カラー)を画面に表示する

構造体として解釈された符号なしR8G8B8A8カラー画像データを画面に表示する方法を紹介します。