iPhone のフォトアルバムから写真を選択して、任意の箇所を切り取って、これを別のビューコントローラに渡すというコードを作りました。
フォトアルバムから写真を選択するときには UIImagePickerController を使いますが、UIImagePickerController には任意の箇所を切り取る編集機能がビルトインされています。しかし、この編集機能では、写真を拡大しないで切り取るとオリジナルのアスペクト比のイメージになったりします。
そこで今回は、オリジナルの写真を持ってきて、この写真の中で正方形のフレームを表示させ、このフレームのサイズをスライダで変え、位置をタップあるいはドラッグで動かして写真の範囲の中で任意のサイズの正方形で切り取るようなルーチンにしました。
以下の例は、この機能のデバッグのために作ったアプリです。
この写真の取り出しと、編集、そして別のビューコントローラに編修済みのイメージを渡すという動作の検証が目的です。
最初のビューコントローラから Move ボタンでイメージピッカーのビューコントローラに遷移し、ここで Pick ボタンで ImagePicker が呼ばれて、選択された写真が画面に表示されて中心部分で写真が収まる最大サイズで黄色いフレームが表示されます。この枠のサイズはスライダーで変化し、位置は画面のタップあるいはドラッグで動くようにしmした。切取部分が決まったら Done ボタンでそのときの枠の部分のイメージを切り出し、 Return ボタンでこのイメージを最初のビューコントローラに渡して戻るというものです。
これはシミュレータおよび iPhone 6 では全く問題なく動作しますが、iPhone4S の実機を接続すると、時にメモリワーニングが出てクラッシュします。
どうやらイメージを操作しているとメモリが足りなくなってしまうようです。
このプログラムは、UIImagePickerController の allowEditing プロパティに false を設定して、ビルトインの編集機能を使わないようにしています。
そこで、試しにこのビルトインの編集機能を生かして、つまり、
picker.allowEditing = true
として、写真を受け取る didFinishPickingMediaWithInfo で、
UIImagePickerControllerOriginalImage
ではなく
UIImagePickerControllerEditedImage
を使うようにします。
そうするとiPhone4S でも問題なく動作するようになりました。
そこで、いろいろな状態で受け取ったイメージのサイズを調べて見ました。
1. ビルトイン編集を使わないオリジナルイメージ
このように同じoriginalImage を受け取っても allowEditing の状態で受け取るイメージのサイズが変わります。
なお、ここで1,でカメラロールのサイズが 3264 x 2448 になっているのはこの写真が iPhone6 で撮影されたものだからです。
iPhone4S で 8MPixel のオリジナルイメージを使うのは少し厳しいようです。
ということで今回は折角作った編集ルーチンは使わないことにしましたが、受け取ってすぐにサイズを縮小して使うなどの工夫をしてみましょう。
図:ストーリーボード
コード: ChildViewController
//
// AWAChildViewController.swift
// AWATransferData
//
// Created by Minori Awamura on 2015/06/07.
// Copyright (c) 2015年 Minori Awamura. All rights reserved.
//
import UIKit
class AWAChildViewController: UIViewController,
UINavigationControllerDelegate, UIImagePickerControllerDelegate {
@IBOutlet weak var _childText: UITextField!
@IBOutlet weak var _returnButton: UIButton!
@IBOutlet weak var viewForPhoto: UIView!
@IBOutlet weak var photoFrame: UIImageView!
@IBOutlet weak var cropSize: UISlider!
@IBOutlet weak var doneButton: UIToolbar!
@IBOutlet weak var pickButton: UIBarButtonItem!
@IBOutlet weak var cancelButton: UIBarButtonItem!
var trans = "test"
var gameImage = UIImage()
var photoImage: UIImage?
var cropFrame: CGContextRef?
var maxSizeOfFrame: CGFloat?
var minSizeOfFrame: CGFloat?
var heightOfImageInFrame: CGFloat?
var widthOfImageInFrame: CGFloat?
var scaleOfImage: CGFloat?
var cropFrameView = UIImageView()
var cropFrameSize: CGFloat = 0.0
var originOfPhotoImage = CGPointZero
var oppositeOfPhotoImage = CGPointZero
override func viewDidLoad() {
super.viewDidLoad()
_childText.text = trans
if let photo = photoImage {
photoFrame.image = photoImage
} else {
photoImage = UIImage(named: "IMG_0281.jpg")
photoFrame.image = photoImage
}
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(true)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// 汎用メソッド
func moveCursorFrame(size: CGFloat, center: CGPoint) {
if cropFrameView.isDescendantOfView(viewForPhoto) {
cropFrameView.removeFromSuperview()
}
UIGraphicsBeginImageContext(CGSizeMake(size, size))
cropFrame = UIGraphicsGetCurrentContext()
CGContextSetLineCap(cropFrame, kCGLineCapSquare)
CGContextSetLineWidth(cropFrame, 1.5)
CGContextSetStrokeColorWithColor(cropFrame, UIColor.yellowColor().CGColor)
CGContextSetFillColorWithColor(cropFrame, UIColor.clearColor().CGColor)
CGContextMoveToPoint(cropFrame, 0, 0)
CGContextAddLineToPoint(cropFrame, cropFrameSize, 0)
CGContextAddLineToPoint(cropFrame, cropFrameSize, cropFrameSize)
CGContextAddLineToPoint(cropFrame, 0, cropFrameSize)
CGContextAddLineToPoint(cropFrame, 0, 0)
CGContextStrokePath(cropFrame)
cropFrameView = UIImageView(image: UIImage(CGImage: CGBitmapContextCreateImage(cropFrame)))
cropFrameView.center = center
viewForPhoto.addSubview(cropFrameView)
}
@IBAction func doneAction(sender: UIBarButtonItem) {
var cropFrameOfImage = CGRectZero
cropFrameOfImage.origin.x = (cropFrameView.frame.origin.x - originOfPhotoImage.x) * scaleOfImage!
cropFrameOfImage.origin.y = (cropFrameView.frame.origin.y - originOfPhotoImage.y) * scaleOfImage!
cropFrameOfImage.size.width = cropFrameSize * scaleOfImage!
cropFrameOfImage.size.height = cropFrameSize * scaleOfImage!
UIGraphicsBeginImageContextWithOptions(cropFrameOfImage.size, false, 0)
photoImage!.drawInRect(CGRectMake(-1 * cropFrameOfImage.origin.x, -1 * cropFrameOfImage.origin.y,
photoImage!.size.width, photoImage!.size.height))
gameImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
let cropedImagesize = gameImage.size
photoFrame.image = gameImage
cropFrameView.removeFromSuperview()
}
@IBAction func pickAction(sender: UIBarButtonItem) {
// イメージピッカーの生成
let picker = UIImagePickerController()
picker.sourceType = UIImagePickerControllerSourceType.PhotoLibrary
picker.delegate = self
picker.allowsEditing = false
// ビューコントローラのビューを開く
presentViewController(picker, animated: true, completion: nil)
}
@IBAction func cancelAction(sender: UIBarButtonItem) {
}
@IBAction func sizeChanged(sender: UISlider) {
let scale = CGFloat(sender.value)
cropFrameSize = maxSizeOfFrame! * CGFloat(cropSize.value)
var centerOfFrame = cropFrameView.center
var originOfFrame = CGPointMake(centerOfFrame.x - cropFrameSize / 2, centerOfFrame.y - cropFrameSize / 2)
var oppositeOfFrame = CGPointMake(originOfFrame.x + cropFrameSize, originOfFrame.y + cropFrameSize)
if originOfFrame.x < originOfPhotoImage.x {
originOfFrame.x = originOfPhotoImage.x
} else if oppositeOfFrame.x > oppositeOfPhotoImage.x {
originOfFrame.x = oppositeOfPhotoImage.x - cropFrameSize
}
if originOfFrame.y < originOfPhotoImage.y {
originOfFrame.y = originOfPhotoImage.y
} else if oppositeOfFrame.y > oppositeOfPhotoImage.y {
originOfFrame.y = oppositeOfPhotoImage.y - cropFrameSize
}
centerOfFrame.x = originOfFrame.x + cropFrameSize / 2
centerOfFrame.y = originOfFrame.y + cropFrameSize / 2
moveCursorFrame(cropFrameSize, center: centerOfFrame)
// 端にいるときに画像からはみ出さないよう中心位置を変える
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
super.touchesBegan(touches, withEvent: event)
let touch = touches.first as? UITouch
let pos: CGPoint = touch!.locationInView(photoFrame)
let originOfPhotoFrameX = photoFrame.frame.origin.x // イメージビュー
let originOfPhotoFrameY = photoFrame.frame.origin.y
let imageOriginX = photoFrame.center.x - originOfPhotoFrameX - widthOfImageInFrame! / 2
let imageOriginY = photoFrame.center.y - originOfPhotoFrameY - heightOfImageInFrame! / 2
let margin = cropFrameSize / 2.0
let minCenterX = margin + imageOriginX
let maxCenterX = imageOriginX + widthOfImageInFrame! - margin
let minCenterY = imageOriginY + margin
let maxCenterY = imageOriginY + heightOfImageInFrame! - margin
if pos.x >= imageOriginX && pos.x <= (imageOriginX + widthOfImageInFrame!)
&& pos.y >= imageOriginY && pos.y <= (imageOriginY + heightOfImageInFrame!) {
var centerX, centerY: CGFloat
if pos.x < minCenterX {
centerX = minCenterX
} else if pos.x > maxCenterX {
centerX = maxCenterX
} else {
centerX = pos.x
}
if pos.y < minCenterY {
centerY = minCenterY
} else if pos.y > maxCenterY {
centerY = maxCenterY
} else {
centerY = pos.y
}
cropFrameView.center = CGPointMake(centerX + originOfPhotoFrameX, centerY + originOfPhotoFrameY)
}
}
override func touchesMoved(touches: Set<NSObject>, withEvent event: UIEvent) {
super.touchesMoved(touches, withEvent: event)
let touch = touches.first as? UITouch
let pos: CGPoint = touch!.locationInView(photoFrame)
let originOfPhotoFrameX = photoFrame.frame.origin.x
let originOfPhotoFrameY = photoFrame.frame.origin.y
let imageOriginX = photoFrame.center.x - originOfPhotoFrameX - widthOfImageInFrame! / 2
let imageOriginY = photoFrame.center.y - originOfPhotoFrameY - heightOfImageInFrame! / 2
let margin = cropFrameSize / 2.0
let minCenterX = margin + imageOriginX
let maxCenterX = imageOriginX + widthOfImageInFrame! - margin
let minCenterY = imageOriginY + margin
let maxCenterY = imageOriginY + heightOfImageInFrame! - margin
if pos.x >= imageOriginX && pos.x <= (imageOriginX + widthOfImageInFrame!)
&& pos.y >= imageOriginY && pos.y <= (imageOriginY + heightOfImageInFrame!) {
var centerX, centerY: CGFloat
if pos.x < minCenterX {
centerX = minCenterX
} else if pos.x > maxCenterX {
centerX = maxCenterX
} else {
centerX = pos.x
}
if pos.y < minCenterY {
centerY = minCenterY
} else if pos.y > maxCenterY {
centerY = maxCenterY
} else {
centerY = pos.y
}
cropFrameView.center = CGPointMake(centerX + originOfPhotoFrameX, centerY + originOfPhotoFrameY)
}
}
override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {
super.touchesEnded(touches, withEvent: event)
let touch = touches.first as? UITouch
let pos: CGPoint = touch!.locationInView(photoFrame)
let originOfPhotoFrameX = photoFrame.frame.origin.x
let originOfPhotoFrameY = photoFrame.frame.origin.y
let imageOriginX = photoFrame.center.x - originOfPhotoFrameX - widthOfImageInFrame! / 2
let imageOriginY = photoFrame.center.y - originOfPhotoFrameY - heightOfImageInFrame! / 2
let margin = cropFrameSize / 2.0
let minCenterX = margin + imageOriginX
let maxCenterX = imageOriginX + widthOfImageInFrame! - margin
let minCenterY = imageOriginY + margin
let maxCenterY = imageOriginY + heightOfImageInFrame! - margin
if pos.x >= imageOriginX && pos.x <= (imageOriginX + widthOfImageInFrame!)
&& pos.y >= imageOriginY && pos.y <= (imageOriginY + heightOfImageInFrame!) {
var centerX, centerY: CGFloat
if pos.x < minCenterX {
centerX = minCenterX
} else if pos.x > maxCenterX {
centerX = maxCenterX
} else {
centerX = pos.x
}
if pos.y < minCenterY {
centerY = minCenterY
} else if pos.y > maxCenterY {
centerY = maxCenterY
} else {
centerY = pos.y
}
cropFrameView.center = CGPointMake(centerX + originOfPhotoFrameX, centerY + originOfPhotoFrameY)
}
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let vcP = segue.destinationViewController as! ViewController
vcP.returnValue = _childText.text
vcP._photoView1.image = gameImage
}
// ビューの戻り
@IBAction func firstViewReturnActionForSegue(segue: UIStoryboardSegue) {
}
// UIImagePickerControllerDelegate
func imagePickerController(picker: UIImagePickerController,
didFinishPickingMediaWithInfo info: [NSObject : AnyObject]) {
// イメージの指定
photoImage = (info[UIImagePickerControllerOriginalImage]! as! UIImage)
photoFrame.image = photoImage
// photoImage = (info[UIImagePickerControllerEditedImage]! as! UIImage)
// photoFrame.image = photoImage
// ビューコントローラのビューを閉じる
picker.presentingViewController?
.dismissViewControllerAnimated(true , completion: nil)
let photoSize = photoImage!.size
println(photoSize)
// 矩形フレーム表示
let widthOfImage = photoImage?.size.width
let heightOfImage = photoImage?.size.height
let viewWidth = photoFrame.bounds.width
let viewHeight = photoFrame.bounds.height
let aspectOfImage = heightOfImage! / widthOfImage!
let aspectOfView = viewHeight / viewWidth
if aspectOfView > aspectOfImage {
maxSizeOfFrame = viewWidth * aspectOfImage
heightOfImageInFrame = maxSizeOfFrame
widthOfImageInFrame = viewWidth
scaleOfImage = widthOfImage! / widthOfImageInFrame!
} else {
maxSizeOfFrame = viewHeight / aspectOfImage
heightOfImageInFrame = viewHeight
widthOfImageInFrame = maxSizeOfFrame
scaleOfImage = heightOfImage! / heightOfImageInFrame!
}
minSizeOfFrame = maxSizeOfFrame! * CGFloat(cropSize.minimumValue)
cropSize.value = 1.0
cropFrameSize = maxSizeOfFrame! * CGFloat(cropSize.value)
moveCursorFrame(cropFrameSize, center: photoFrame.center)
originOfPhotoImage.x = photoFrame!.center.x - widthOfImageInFrame! / 2
originOfPhotoImage.y = photoFrame!.center.y - heightOfImageInFrame! / 2
oppositeOfPhotoImage.x = originOfPhotoImage.x + widthOfImageInFrame!
oppositeOfPhotoImage.y = originOfPhotoImage.y + heightOfImageInFrame!
}
// イメージピッカーのキャンセル時に呼ばれる
func imagePickerControllerDidCancel(picker: UIImagePickerController) {
// ビューコントローラのビューを閉じる
picker.presentingViewController?
.dismissViewControllerAnimated(true , completion: nil)
}
func saveDefault() {
let myDefault = NSUserDefaults.standardUserDefaults()
let imageData = UIImagePNGRepresentation(gameImage)
myDefault.setObject(imageData, forKey: "gamePicture")
}
}