34524


<http://swift-salaryman.com/pixcelcolor.php>  
のUIImageの色情報を抜き出すをを参考にSwift3として動作するようにしてみたいと思い

[code]

    let url = URL(string:"http://swift-salaryman.com/files/image/page_top.gif")!
    let imageData = try? Data(contentsOf: url)
    let image = UIImage(data:imageData!)
    
    
    func getPixelColorFromUIImage(myUIImage:UIImage, pos:CGPoint) -> UIColor {
    
    let pixelData = myUIImage.cgImage!.dataProvider!.data
    let data: UnsafePointer = CFDataGetBytePtr(pixelData)
    
    let pixelInfo: Int = ((Int(myUIImage.size.width) * Int(pos.y)) + Int(pos.x)) * 4
    let r = CGFloat(data[pixelInfo]) / CGFloat(255.0)
    let g = CGFloat(data[pixelInfo+1]) / CGFloat(255.0)
    let b = CGFloat(data[pixelInfo+2]) / CGFloat(255.0)
    let a = CGFloat(data[pixelInfo+3]) / CGFloat(255.0)
    
    return UIColor(red: r, green: g, blue: b, alpha: a)
    
    }
    
[/code]

としました。  
リンク先の記事には画像中から座標をランダムに30個ピックアップして、その座標の色情報を‌​出力するコードが含まれています。しかし、そのままでは動作せずまたランダムに30個ではなく、「画像全部」を出力するようにするにはどのようにすればよろしいでしょうか。

またこのような方法以外により良い方法などはございますでしょうか。  
最終的な出力の結果はこののちに続くコードに使用したいと考えています。  
できましたら画像の左上部や右上部方向に向かって、というように方向性も指定できればと思います。  
ご教授いただけますと幸いです。


CC BY-SA 3.0
2017-05-07T13:19:20.200
34519
2017-05-07T17:17:44.260
2017-05-07T15:09:04.907
14780
14780
question
swift3
Swift3でUIImageの色情報を抜き出す


「そのままでは動作せず」の部分がよくわからない(元記事のコードは実際に使うには問題満載ですが、正しくSwift3に置き換えれば動作自体はするはずです)のですが、そのまま使えるようなコードではないので、詳細については置いておきましょう。

  * ピクセル値を求めるごとに`dataProvider`から`CFData`を作成している  
(100万画素分のデータを取得すると、100万回`CFData`が作成される。)

  * `CGImage`の`width`, `height`は元の`UIImage`の`size.width`, `size.height`と異なることがあるのにそのことを考慮していない  
(`UIImage`は`CGImage`を回転させたりスケーリングしたりできる。)

  * `CGImage`の特定のビットマップ表現(RGBA各8ビットでピクセルあたり32ビット)に依存している  
(`CGImage`自体は様々な他のビットマップ表現のデータを保持できる。)

このうち2点目と3点目については、元画像がこの条件を満たすように選んでやれば良いだけなので、修正が簡単な2点目には対応するとして、3点目の制約条件はそのままとさせてもらいました。

* * *

と言うわけで、取得したビットマップデータにアクセスしやすくするために、`ViewController`とは別に次のようなクラスを用意しておきます。

[code]

    class PixelBuffer {
        private var pixelData: Data
        var width: Int
        var height: Int
        private var bytesPerRow: Int
        private let bytesPerPixel = 4 //1ピクセルが4バイトのデータしか扱わない
    
        init?(uiImage: UIImage) {
            guard
                //CGImageが取得できること
                let cgImage = uiImage.cgImage,
                //R,G,B,A各8Bit
                cgImage.bitsPerComponent == 8,
                //1 pixelが32bit
                cgImage.bitsPerPixel == bytesPerPixel * 8
                //本当はRGBAになっているかもチェックしたいが確実な方法が見当たらないため省略
                else {return nil}
            pixelData = cgImage.dataProvider!.data! as Data
            width = cgImage.width
            height = cgImage.height
            bytesPerRow = cgImage.bytesPerRow
        }
    
        func getColor(x: Int, y: Int) -> UIColor {
            let pixelInfo = bytesPerRow * y + x * bytesPerPixel
            let r = CGFloat(pixelData[pixelInfo]) / CGFloat(255.0)
            let g = CGFloat(pixelData[pixelInfo+1]) / CGFloat(255.0)
            let b = CGFloat(pixelData[pixelInfo+2]) / CGFloat(255.0)
            let a = CGFloat(pixelData[pixelInfo+3]) / CGFloat(255.0)
    
            return UIColor(red: r, green: g, blue: b, alpha: a)
        }
    
        subscript (x: Int, y: Int) -> UIColor {
            return getColor(x: x, y: y)
        }
    }
    
[/code]

やっていることは、あなたの探してきた`getPixelColorFromUIImage(myUIImage:pos:)`ほとんど同じなのですが、

  * `dataPrivider`に`CFData`を作成させるのはコンストラクタの中で1回きり
  * `UIImage`の`size.width`ではなく、`CGImage`の`width`を使用している

と言うのが主な修正点です。

以上のクラスを使って全画素を`UIColor`としてコンソールに出力するコードはこんな感じになります。

[code]

        //リソースから画像読み込みUIImage作成
        let url = Bundle.main.url(forResource: "sample", withExtension: "png")
        do {
            let imageData = try Data(contentsOf: url!,options: NSData.ReadingOptions.mappedIfSafe)
            let img = UIImage(data: imageData)!
    
            if let pixelBuffer = PixelBuffer(uiImage: img) {
                for x in 0..<pixelBuffer.width {
                    for y in 0..<pixelBuffer.height {
                        print(pixelBuffer[x, y].debugDescription)
                    }
                }
            } else {
                print("Bad image format")
            }
        } catch {
            print(error)
        }
    
[/code]

元記事と同じく`viewDidLoad()`の中に置いて動作確認してもらうことを想定していますので、メインスレッドの中で外部リソースに対して`Data(contentsOf:)`してはいけませんから、画像はアプリのバンドルに入れてしまうと言う想定です。

ちなみに上記の例では`for
y`を内側のループに指定していますので、画素を縦方向に順に1列ずつ出力することになりますが、横方向に順に1行ずつ出力してやりたければ、

[code]

                for y in 0..<pixelBuffer.height {
                    for x in 0..<pixelBuffer.width {
                        print(pixelBuffer[x, y].debugDescription)
                    }
                }
    
[/code]

といった感じで、`for y`の行と`for x`の行を入れ替えてやるだけです。

(ちなみに`pixelBuffer[x,
y]`という記述を可能にしているのは`PixelBuffer`クラスの`subscript`の定義の部分です。`pixelBuffer.getColor(x:
x, y: y)`と書いても同じになります。)

* * *

最初に書いたような制約のいくつかは`dataProvider`を用いた場合に特有のものであるせいか、本家stackoverflowではあまり推奨されていませんでした。

[How to get pixel data from a UIImage (Cocoa Touch) or CGImage (Core
Graphics)?](https://stackoverflow.com/a/1262893/6541007)

ざっくり言って、自分が望む形式で画素データを取り出したければ、その形式のビットマップを作って、そのビットマップに画像を描画しなさいよ、と言うことです。

同じやり方の日本語Swift版の記事もすぐに見つかるかと思ったのですが、簡単に調べても検索の上位には出てきませんでした。そのまま続けると長くなりすぎますので、こちらのやり方に興味がおありでしたら別途ご質問のスレを立ててください。


CC BY-SA 3.0
2017-05-07T17:17:44.260
34524
2017-05-07T17:17:44.260
2017-05-23T12:38:56.083
-1
13972
34519
answer