質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

89.55%

このプログラムに画像を小さくするプログラムを付け加えたい。

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 158

MashisonJr.

score 7

前提・実現したいこと

カメラを使って画像を撮った時に、その撮った画像をすぐに小さくできるようにしたい。

発生している問題・エラーメッセージ

IphoneXのカメラで撮った画像を使うと、IphoneXのディスプレイにはきれいに画像が表示されるが、表示されている画像とImageViewの大きさがあっていないのか、座標とRGB値がうまく取れない。
そのためカメラで撮った時にすぐに撮った画像のサイズを小さくできるようにしたい。

該当のソースコード

Swift5

import UIKit

class TapViewController: UIViewController {

    @IBOutlet var imageView: UIImageView!
    var image = UIImage()
    //表示されている画像のタップ座標用変数
    var tapPoint = CGPoint(x: 0, y: 0)

    override func viewDidLoad() {
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    //imageviewをタップした時に色を判別
    @IBAction func getImageRGB(_ sender: UITapGestureRecognizer) {

        guard imageView.image != nil else {return}

        //タップした座標の取得
        tapPoint = sender.location(in: imageView)

        let cgImage = imageView.image?.cgImage!
        let pixelData = cgImage?.dataProvider!.data
        let data: UnsafePointer = CFDataGetBytePtr(pixelData)
        //1ピクセルのバイト数
        let bytesPerPixel = (cgImage?.bitsPerPixel)! / 8
        //1ラインのバイト数
        let bytesPerRow = (cgImage?.bytesPerRow)!
        print("bytesPerPixel=\(bytesPerPixel) bytesPerRow=\(bytesPerRow)")
        //タップした位置の座標にあたるアドレスを算出
        let pixelAd: Int = Int(tapPoint.y) * bytesPerRow + Int(tapPoint.x) * bytesPerPixel
        //それぞれRGBAの値をとる
        let r = Int( CGFloat(data[pixelAd])) 
        let g = Int( CGFloat(data[pixelAd+1]))
        let b = Int( CGFloat(data[pixelAd+2]))
        let a = CGFloat(Int( CGFloat(data[pixelAd+3])/CGFloat(255.0)*100)) / 100

        print([r,g,b,a])
        //navigationbarに結果を表示
        let R = "R:" + String(Int(r))
        let G = " G:" + String(Int(g))
        let B = " B:" + String(Int(b))
        let A = " A:" +  String(format: "%.1f", a)
        navigationItem.title = R + G + B + A
    }


}


//画像を選択
extension TapViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate {

    //画像を選んだ時の処理
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {

        let selectedImage = info[.originalImage] as! UIImage
        //サイズを圧縮する
        // let resizedImage = selectedImage.scale(byFactor: 0.4)

        image = selectedImage

        var imageHeight = image.size.height
        var imageWidth = image.size.width

        let navigationBarHeight = navigationController?.navigationBar.frame.height
        let width = self.view.frame.width
        let height = self.view.frame.height
        let centerX = self.view.center.x
        let centerY = self.view.center.y
        let widthRatio = imageWidth
        let heightRatio = imageHeight
        //画像の大きさに応じてiamgeviewのサイズを変える
        if imageHeight > self.view.frame.height || imageWidth > self.view.frame.width {
            imageWidth = width
            imageHeight = width*heightRatio/widthRatio

        } else if imageHeight > self.view.frame.height {
            imageHeight = height
            imageWidth = height*widthRatio/heightRatio

        } else if imageWidth > self.view.frame.width {
            imageWidth = width
            imageHeight = width*heightRatio/widthRatio

        } else {
        }

        imageView.contentMode = UIView.ContentMode.scaleAspectFill
        imageView.frame.size = CGSize(width: imageWidth, height: imageHeight)
        //画像がnavigationbarに被らないようにする
        if imageHeight/2 > (height/2 - navigationBarHeight!) {
            imageView.center = CGPoint(x: centerX, y: centerY + navigationBarHeight!)
        } else {
            imageView.center = CGPoint(x: centerX, y: centerY)
        }

        imageView.image = image

        picker.dismiss(animated: true, completion: nil)
    }



    // 撮影がキャンセルされた時に呼ばれる
    func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
        picker.dismiss(animated: true, completion: nil)
    }

    func tappedlibrary() {
        let sourceType:UIImagePickerController.SourceType =
            UIImagePickerController.SourceType.photoLibrary

        if UIImagePickerController.isSourceTypeAvailable(
            UIImagePickerController.SourceType.photoLibrary){
            // インスタンスの作成
            let cameraPicker = UIImagePickerController()
            cameraPicker.sourceType = sourceType
            cameraPicker.delegate = self
            self.present(cameraPicker, animated: true, completion: nil)
        }
        else{
            print("error")

        }
    }

    func tappedcamera() {
        let sourceType:UIImagePickerController.SourceType =
            UIImagePickerController.SourceType.camera
        // カメラが利用可能かチェック
        if UIImagePickerController.isSourceTypeAvailable(
            UIImagePickerController.SourceType.camera){
            // インスタンスの作成
            let cameraPicker = UIImagePickerController()
            cameraPicker.sourceType = sourceType
            cameraPicker.delegate = self
            self.present(cameraPicker, animated: true, completion: nil)

        }
        else{
            print("error")
        }
    }

    @IBAction func selecteImageButton(_ sender: UITapGestureRecognizer) {
        //アラート表示のために
        let actionSheet = UIAlertController(title: "", message: "写真の選択", preferredStyle: UIAlertController.Style.actionSheet)

        let tappedcamera = UIAlertAction(title: "カメラで撮影する", style: UIAlertAction.Style.default, handler: {
            (action: UIAlertAction!) in
            self.tappedcamera()
        })

        let tappedlibrary = UIAlertAction(title: "ライブラリから選択する", style: UIAlertAction.Style.default, handler: {
            (action: UIAlertAction!) in
            self.tappedlibrary()
        })

        let cancel = UIAlertAction(title: "キャンセル", style: UIAlertAction.Style.cancel, handler: {
            (action: UIAlertAction!) in
            print("キャンセル")
        })

        actionSheet.addAction(tappedcamera)
        actionSheet.addAction(tappedlibrary)
        actionSheet.addAction(cancel)

        present(actionSheet, animated: true, completion: nil)
    }

}

試したこと

画像のリサイズなどを自分で加えたりしたがうまくいかなかった。

補足情報(FW/ツールのバージョンなど)

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • takabosoft

    2019/11/19 16:56

    }``` ←中括弧の後ろに改行入れてください。

    あと、現状で何が問題になっているのかが質問文から読み取れません。
    質問欄は編集できます。

    キャンセル

  • eytyet

    2019/11/22 13:13

    ソースコード全体の前と後ろに、```だけの行を追加してください。そうすると、ソースコードのインデントやキーワードの色付けなどがされて読みやすくなります。今の状態ですと読むのが難しいです。

    ```swift
    ソースコード
    ```

    ↑こんな感じで、最初の方にswiftとつけるともっと良いです。

    キャンセル

回答 1

checkベストアンサー

0

本当にやりたい事は、画像上の点の色を調べたい、という事ですね。

(1) 座標変換すればリサイズしなくても可能です。
tapPointには「表示されているimageViewの画面上のサイズの点」の座標が入るので、それを元に元画像上の座標を割り出します。

  • imageViewcontentModescaleToFillにします。

  • xとyそれぞれ"比"を使って変換します。let x = tapPoint.x / imageView.frame.width * cgImage.widthという感じで。

imageViewの表示がおかしくなると思うので、imageViewの縦横比が元画像と同じになるように、Auto Layoutを設定してください。

(2) RGBAの並び
RGBAの並びは固定ではありません。試した限りでは、assetに入れたJPEG画像のままではRGBAでしたが、後述のリサイズを通した画像はBGRAでした。これは、CGImage.bitmapInfoの情報から判別可能です。
また、1ピクセルが8ビットとも限りません。CGImage.bitPerComponentで分かります。(殆どのケースは8ビット固定でも大丈夫だと思います)

座標変換を反映したonImageTapはこちらです。置き換えて下さい。

@IBAction func onImageTap(_ sender: UITapGestureRecognizer) {
    guard let image = imageView.image else { return }
    let pointOnImageView = sender.location(in: imageView)
    let x = pointOnImageView.x / imageView.frame.width * image.size.width
    let y = pointOnImageView.y / imageView.frame.height * image.size.height

    if let color = image.color(of: CGPoint(x: x, y: y)) {
        label.text = String(format: "%4dx%4d - (r:%3d, g:%3d, b:%3d, a:", Int(x), Int(y), color.red, color.green, color.blue) + String(describing: color.alpha) + ")"
    }
}

点の取得を関数にしました。どこかのファイルに、以下も追加してください。

extension UIImage {
    /// Image上の点の色を返す。cgImageをもち、8bit/色だけ有効。alphaのみは非対応でnilを返す。
    /// - Parameter point: Image上の色を調べたい点の座標。
    /// - Returns: 不明のときはnilが返る。各色の値は0-255。alphaがない場合nilを返す。
    func color(of point: CGPoint) -> (red:Int, green:Int, blue:Int, alpha:Int?)? {
        guard let cgImage = self.cgImage else { return nil }
        guard let pixelData = cgImage.dataProvider?.data else { return nil }
        guard let data = CFDataGetBytePtr(pixelData) else { return nil }
        guard 0 <= point.x && Int(point.x) < cgImage.width &&
              0 <= point.y && Int(point.y) < cgImage.height else { return nil }
        guard cgImage.bitsPerComponent == 8 else { return nil }
        print(CGImageAlphaInfo.premultipliedLast.rawValue, CGImageAlphaInfo.premultipliedFirst.rawValue)
        let byteOrder = cgImage.bitmapInfo.intersection(.byteOrderMask)
        guard byteOrder == [] || byteOrder == .byteOrder32Big || byteOrder == .byteOrder32Little else { return nil }

        //タップした位置の座標にあたるアドレスを算出
        let address = Int(point.y) * cgImage.bytesPerRow + Int(point.x) * cgImage.bitsPerPixel / 8

        //それぞれRGBAの値をとる
        let alphaInfo = CGImageAlphaInfo(rawValue: cgImage.bitmapInfo.intersection(.alphaInfoMask).rawValue)
        var offset: (red:Int, green:Int, blue:Int, alpha:Int?)
        if byteOrder == .byteOrder32Little {
            if [.first, .noneSkipFirst, .premultipliedFirst].contains(alphaInfo) {
                offset = (2, 1, 0, 3)
            } else {
                offset = (3, 2, 1, 0)
            }
        } else {    // [] || .byteOrder32Big
            if [.first, .noneSkipFirst, .premultipliedFirst].contains(alphaInfo) {
                offset = (1, 2, 3, 0)
            } else {
                offset = (0, 1, 2, 3)
            }
        }
        // alphaがない場合
        if [CGImageAlphaInfo.none, .alphaOnly, .noneSkipFirst, .noneSkipLast].contains(alphaInfo) {
            offset.alpha = nil
        }

        let r = Int(data[address + offset.red])
        let g = Int(data[address + offset.green])
        let b = Int(data[address + offset.blue])
        let a = offset.alpha != nil ? Int(data[address + offset.alpha!]) : nil
        return (r, g, b, a)
    }
}

なお、正しく点を調べられるようになっても、一点だけだと見た目とはだいぶ違う色が取れたりします。今回のように大きい画像を縮小して表示していると、全然違う色を読んでくる場合も多いでしょう。
その場合は縮小は一つの解決策になるかと思います。

リサイズする関数の例はこちらです。

    func resize(image: UIImage, ratio: CGFloat) -> UIImage? {
        let newSize = CGSize(width: image.size.width * ratio, height: image.size.height * ratio)
        UIGraphicsBeginImageContext(newSize)
        image.draw(in: CGRect(origin: CGPoint.zero, size: newSize))
        let resizedImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return resizedImage
    }

ライブラリを使うのも近道かなと思います。
私は使った事がないので恐縮ですが、この辺りを参考になさってはどうでしょうか。
https://qiita.com/koher/items/7dc1aa10755b79102539

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/11/22 16:22

    このプログラムにそれらを追加しようとしたら、どこにどのように追加したらいいんでしょうか?
    よければ教えていただきたいです。

    キャンセル

  • 2019/11/22 16:33

    ここまでコードが書けていたら難しいことではないと思うのですが、どのあたりが分かりにくいでしょうか。

    キャンセル

  • 2019/11/22 16:46

    すいません。
    見様見真似でしてみたものの、IphoneXの画像だとうまく座標が合わなくて困ってます。
    色々調べて自分で改良したのですが、うまくいかないです。

    座標の部分とRGBAの部分を詳しく知りたいです。よろしくお願いいたします。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 89.55%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる