2015-05-11 5 views
0

Работа с приложением, которое берет фоновое изображение из рулона камеры, а затем берет PNG для наложения, манипулирует PNG (ущипнуть, увеличивать, поворачивать и т. Д.), Возвращая композитное изображение на следующий контроллер вида.Получение CGImage для афоризма в Swift

Независимо от того, что я пытаюсь, я ударяю о стену, выясняя, как получить мой код в соответствии с фоновым изображением. Оверлей (topLayerImage.image) в коде ниже по крайней мере сохраняет свой аспект при конверсии. Однако фоновое изображение вынуждено в форму. Например, UIImageView по умолчанию представляет собой вертикальное изображение для портретного вида на телефоне, и результирующее изображение возвращается как эта форма независимо от исходного соотношения сторон исходной фотографии.

Я видел некоторые способы Objective-C, чтобы подойти к этому, хотя я провел весь день без везения. Любой шанс, что кто-то может знать, где я ошибаюсь? Вот мой код:

import UIKit 

class TwoLayerViewController: UIViewController { 

    @IBOutlet weak var ghostHolderView: UIView! 
    @IBOutlet weak var bottomLayerImage: UIImageView! 
    @IBOutlet weak var topLayerImage: UIImageView! 
    @IBOutlet weak var amountSlider: UISlider! 
    @IBOutlet weak var nextPageButton: UIButton! 

    var originalPhoto: UIImage? 
    var chosenGhostPhoto: UIImage? 
    var newImage:UIImage? 
    var newBGImage:UIImage? 



    override func viewDidLoad() { 
     super.viewDidLoad() 

     // Do any additional setup after loading the view. 

     bottomLayerImage.image = originalPhoto 
     bottomLayerImage.contentMode = UIViewContentMode.ScaleAspectFit 

     topLayerImage.image = chosenGhostPhoto 
     topLayerImage.alpha = 1.0 
     topLayerImage.contentMode = UIViewContentMode.ScaleAspectFit 
    } 

    @IBAction func handlePan(recognizer:UIPanGestureRecognizer) { 
     let translation = recognizer.translationInView(self.view) 
     if let view = recognizer.view { 
      view.center = CGPoint(x:view.center.x + translation.x, 
      y:view.center.y + translation.y) 
     } 
     recognizer.setTranslation(CGPointZero, inView: self.view) 
    } 

    @IBAction func handlePinch(recognizer : UIPinchGestureRecognizer) { 
     if let view = recognizer.view { 
      view.transform = CGAffineTransformScale(view.transform, 
       recognizer.scale, recognizer.scale) 
      recognizer.scale = 1 
     } 
    } 

    @IBAction func handleRotate(recognizer : UIRotationGestureRecognizer) { 
     if let view = recognizer.view { 
      view.transform = CGAffineTransformRotate(view.transform, recognizer.rotation) 
      var transform:CGAffineTransform = view.transform 
      var angle:CGFloat = atan2(transform.b, transform.a) 
      println(angle) 
     } 
    } 

    @IBAction func sliderChangeAmount(sender: UISlider) { 

     //  let sliderValue = CGFloat(sender.value) 
     // 
     //  topLayerImage.alpha = sliderValue 

    } 


    @IBAction func combineImagesButton(sender: AnyObject) { 

     let originalWidth = originalPhoto!.size.width 
     let originalHeight = originalPhoto!.size.height 
     let finalWidth = bottomLayerImage.frame.size.width 
     let finalHeight = bottomLayerImage.frame.size.height 
     let finalSize : CGSize = CGSizeMake(finalWidth, finalHeight) 
     UIGraphicsBeginImageContext(finalSize) 

     let size = CGSizeApplyAffineTransform(originalPhoto!.size, CGAffineTransformMakeScale(finalWidth/originalWidth, finalHeight/originalHeight)) 

     originalPhoto!.drawInRect(CGRect(origin: CGPointZero, size: size)) 


     // originalPhoto!.drawInRect(bottomLayerImage.frame) 

     let imgCon = UIGraphicsGetCurrentContext() 
     CGContextTranslateCTM(imgCon, 0.5 * finalSize.width, 0.5 * finalSize.height) ; 
     CGContextRotateCTM(imgCon, atan2(topLayerImage.transform.b, topLayerImage.transform.a)); 

     chosenGhostPhoto!.drawInRect(topLayerImage.frame, blendMode: kCGBlendModeNormal, alpha:0.8) 

     newImage = UIGraphicsGetImageFromCurrentImageContext() 

     UIGraphicsEndImageContext() 
     println(newImage) 
     println(finalSize) 
     println(originalPhoto!.size.width, originalPhoto!.size.height) 
    } 

    func getBoundingRectAfterRotation(rect: CGRect, angle: Float) -> CGRect { 
     let newWidth : Float = Float(rect.size.width) * abs(cosf(angle)) + Float(rect.size.height) * fabs(sinf(angle)) 

     let newHeight : Float = Float(rect.size.height) * fabs(cosf(angle)) + Float(rect.size.width) * fabs(sinf(angle)) 

     let newX : Float = Float(rect.origin.x) + ((Float(rect.size.width) - newWidth)/2); 
     let newY : Float = Float(rect.origin.y) + ((Float(rect.size.height) - newHeight)/2); 
     let rotatedRect : CGRect = CGRectMake(CGFloat(newX), CGFloat(newY), CGFloat(newWidth), CGFloat(newHeight)) 
     return rotatedRect 
    } 


    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { 
     if let vc = segue.destinationViewController as? CombinedLayerViewController { 

      vc.fullImage = self.newImage 
     } 
    } 

} 

UPDATE: предложение Тимофея, с некоторыми незначительными твиками, работало на фоне вопрос, но у меня возникают проблемы с наложением, по-видимому, в масштабе и, я думаю, происхождение точки. Вот мой неаккуратный код:

import UIKit 

class TwoLayerViewController: UIViewController { 

@IBOutlet weak var ghostHolderView: UIView! 
@IBOutlet weak var bottomLayerImage: UIImageView! 
@IBOutlet weak var topLayerImage: UIImageView! 
@IBOutlet weak var amountSlider: UISlider! 
@IBOutlet weak var nextPageButton: UIButton! 

var originalPhoto: UIImage? 
var chosenGhostPhoto: UIImage? 
var newImage:UIImage? 
var newBGImage:UIImage? 



override func viewDidLoad() { 
    super.viewDidLoad() 

    // Do any additional setup after loading the view. 

    bottomLayerImage.image = originalPhoto 
    bottomLayerImage.contentMode = UIViewContentMode.ScaleAspectFit 

    topLayerImage.image = chosenGhostPhoto 
//  topLayerImage.alpha = 1.0 
//  topLayerImage.contentMode = UIViewContentMode.ScaleAspectFit 

} 

@IBAction func handlePan(recognizer:UIPanGestureRecognizer) { 
    let translation = recognizer.translationInView(self.view) 
    if let view = recognizer.view { 
     view.center = CGPoint(x:view.center.x + translation.x, 
      y:view.center.y + translation.y) 
    } 
    recognizer.setTranslation(CGPointZero, inView: self.view) 

} 

@IBAction func handlePinch(recognizer : UIPinchGestureRecognizer) { 
    if let view = recognizer.view { 
     view.transform = CGAffineTransformScale(view.transform, 
      recognizer.scale, recognizer.scale) 
     recognizer.scale = 1 

    } 
} 

@IBAction func handleRotate(recognizer : UIRotationGestureRecognizer) { 
    if let view = recognizer.view { 
     view.transform = CGAffineTransformRotate(view.transform, recognizer.rotation) 
     var transform:CGAffineTransform = view.transform 
     var angle:CGFloat = atan2(transform.b, transform.a) 
     println(angle) 

    } 
} 

@IBAction func sliderChangeAmount(sender: UISlider) { 

    //  let sliderValue = CGFloat(sender.value) 
    // 
    //  topLayerImage.alpha = sliderValue 

} 


@IBAction func combineImagesButton(sender: AnyObject) { 

    var originalPhotoFrame: CGRect? 
    var backgroundLayerFrame: CGRect? 
    var ghostOriginalPhotoFrame: CGRect? 
    var ghostBackgroundLayerFrame: CGRect? 

    if bottomLayerImage.image!.size.width > bottomLayerImage.image!.size.height { 

     originalPhotoFrame = CGRect(x: 0,y: 0, width: bottomLayerImage.image!.size.width, height: bottomLayerImage.image!.size.height) 
     backgroundLayerFrame = CGRect(x: 0, y: 0, width: bottomLayerImage.frame.size.width, height: bottomLayerImage.frame.size.height) 

    } else { 

     originalPhotoFrame = CGRect(x: 0,y: 0, width: bottomLayerImage.frame.size.width, height: bottomLayerImage.frame.height) 
     backgroundLayerFrame = CGRect(x: 0, y: 0, width: bottomLayerImage.frame.size.width, height: bottomLayerImage.frame.size.height) 
    } 

    // Now figure out whether the ScaleAspectFit was horizontally or vertically bound. 
    let horizScale = backgroundLayerFrame!.width/originalPhotoFrame!.width 
    let vertScale = backgroundLayerFrame!.height/originalPhotoFrame!.height 
    let myScale = min(horizScale, vertScale) 

    // So we don't need to do each of these calculations on a separate line, but for ease of explanation… 
    // Now we can calculate the size to scale originalPhoto 
    let scaledSize = CGSize(width: originalPhotoFrame!.size.width * myScale, 
     height: originalPhotoFrame!.size.height * myScale) 
    // And now we need to center originalPhoto inside backgroundLayerFrame 
    let scaledOrigin = CGPoint(x: (backgroundLayerFrame!.width - scaledSize.width)/2, 
     y: (backgroundLayerFrame!.height - scaledSize.height)/2) 

    // Put it all together 
    let scaledPhotoRect = CGRect(origin: scaledOrigin, size: scaledSize) 


    ////// 

    if topLayerImage.image!.size.width > topLayerImage.image!.size.height { 

     ghostOriginalPhotoFrame = CGRect(x: 0,y: 0, width: topLayerImage.image!.size.width, height: topLayerImage.image!.size.height) 
     ghostBackgroundLayerFrame = CGRect(x: topLayerImage.frame.origin.x, y: topLayerImage.frame.origin.y, width: topLayerImage.frame.size.width, height: topLayerImage.frame.size.height) 

    } else { 

     ghostOriginalPhotoFrame = CGRect(x: topLayerImage.frame.origin.x,y: topLayerImage.frame.origin.y, width: topLayerImage.frame.size.width, height: topLayerImage.frame.height) 
     ghostBackgroundLayerFrame = CGRect(x: topLayerImage.frame.origin.x, y: topLayerImage.frame.origin.y, width: topLayerImage.frame.size.width, height: topLayerImage.frame.size.height) 
    } 

    // Now figure out whether the ScaleAspectFit was horizontally or vertically bound. 
    let ghostHorizScale = ghostBackgroundLayerFrame!.width/ghostOriginalPhotoFrame!.width 
    let ghostVertScale = ghostBackgroundLayerFrame!.height/ghostOriginalPhotoFrame!.height 
    let ghostMyScale = min(ghostHorizScale, ghostVertScale) 

    // So we don't need to do each of these calculations on a separate line, but for ease of explanation… 
    // Now we can calculate the size to scale originalPhoto 
    let ghostScaledSize = CGSize(width: ghostOriginalPhotoFrame!.size.width * ghostMyScale, 
     height: ghostOriginalPhotoFrame!.size.height * ghostMyScale) 
    // And now we need to center originalPhoto inside backgroundLayerFrame 
    let ghostScaledOrigin = CGPoint(x: (ghostBackgroundLayerFrame!.width - ghostScaledSize.width)/2, 
     y: (ghostBackgroundLayerFrame!.height - ghostScaledSize.height)/2) 

    // Put it all together 
    let ghostScaledPhotoRect = CGRect(origin: ghostScaledOrigin, size: ghostScaledSize) 



    ////// 






//  UIGraphicsBeginImageContext(scaledSize) 
    UIGraphicsBeginImageContextWithOptions(scaledSize, false, 0) 

    originalPhoto!.drawInRect(CGRect(origin: CGPointZero, size: scaledSize)) 


    let imgCon = UIGraphicsGetCurrentContext() 
//  CGContextTranslateCTM(imgCon, 0.5 * finalSize.width, 0.5 * finalSize.height) ; 
    CGContextRotateCTM(imgCon, atan2(topLayerImage.transform.b, topLayerImage.transform.a)); 

//  chosenGhostPhoto!.drawInRect(CGRect(origin: topLayerImage.frame.origin, size: ghostScaledSize)) 

    chosenGhostPhoto!.drawInRect(ghostScaledPhotoRect, blendMode: kCGBlendModeNormal, alpha: 0.8) 

//  chosenGhostPhoto!.drawInRect(CGRect(origin: chosenGhostPhoto, size: ghostScaledSize), blendMode: kCGBlendModeNormal, alpha:0.8) 



    newImage = UIGraphicsGetImageFromCurrentImageContext() 

    UIGraphicsEndImageContext() 
    println(newImage) 
//  println(finalSize) 
    println(originalPhoto!.size.width, originalPhoto!.size.height) 

} 

// func getBoundingRectAfterRotation(rect: CGRect, angle: Float) -> CGRect { 
//  let newWidth : Float = Float(rect.size.width) * abs(cosf(angle)) + Float(rect.size.height) * fabs(sinf(angle)) 
//   
//  let newHeight : Float = Float(rect.size.height) * fabs(cosf(angle)) + Float(rect.size.width) * fabs(sinf(angle)) 
//   
//  let newX : Float = Float(rect.origin.x) + ((Float(rect.size.width) - newWidth)/2); 
//  let newY : Float = Float(rect.origin.y) + ((Float(rect.size.height) - newHeight)/2); 
//  let rotatedRect : CGRect = CGRectMake(CGFloat(newX), CGFloat(newY), CGFloat(newWidth), CGFloat(newHeight)) 
//  return rotatedRect 
// } 


override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { 
    if let vc = segue.destinationViewController as? CombinedLayerViewController { 

     vc.fullImage = self.newImage 
    } 
} 

ответ

0

Скриншоты делают его намного яснее. Этот первый снимок экрана показывает то, что делает ScaleAspectFit: изображение пейзажа масштабируется без искажений, чтобы заполнить всю более узкую ширину bottomLayerImage и по центру по вертикали на большей высоте. Однако: ничего не меняется bottomLayerImage.frame в этом коде. Единственное, что облегчит визуализацию, - установить bottomLayerImage.backgroundColor = .greenColor(), чтобы вы могли видеть, что bottomLayerImage занимает такое же пространство экрана и просто центрирует originalPhoto по вертикали.

Преобразования, установленное на родительском представлении в handlePinch и handleRotate отрегулирует bottomLayerImage.frame с момента, который хранится в координатах SuperView, но работа ScaleAspectFit не применяются к раме.

Это означает, что в combineImagesButton математика, которая вычисляет size, является неправильной, и когда вы рисуете originalPhoto в rect, она искажается. Чтобы получить эффект, который вы ищете, вам нужно рассчитать масштаб, который был применен ScaleAspectFit. Я взбила детскую площадку, чтобы показать математику. Может быть оптимизирован и еще много чего, но должен передать суть того, что вам нужно.

//: Playground - noun: a place where people can play 

import UIKit 

// Just some test values. Adjust them to see different results 
let originalPhotoFrame = CGRect(x: 0,y: 0, width: 3000, height: 2000) 
let backgroundLayerFrame = CGRect(x: 0, y: 0, width: 640, height: 960) 

// Now figure out whether the ScaleAspectFit was horizontally or vertically bound. 
let horizScale = backgroundLayerFrame.width/originalPhotoFrame.width 
let vertScale = backgroundLayerFrame.height/originalPhotoFrame.height 
let myScale = min(horizScale, vertScale) 

// So we don't need to do each of these calculations on a separate line, but for ease of explanation… 
// Now we can calculate the size to scale originalPhoto 
let scaledSize = CGSize(width: originalPhotoFrame.size.width * myScale, 
height: originalPhotoFrame.size.height * myScale) 
// And now we need to center originalPhoto inside backgroundLayerFrame 
let scaledOrigin = CGPoint(x: (backgroundLayerFrame.width - scaledSize.width)/2, 
y: (backgroundLayerFrame.height - scaledSize.height)/2) 

// Put it all together 
let scaledPhotoRect = CGRect(origin: scaledOrigin, size: scaledSize) 

Вы будете хотеть, чтобы сделать то же самое с chosenGhostPhoto и topLayerImage и объединить, что с математикой вы уже делаете для призрака, но это должно быть довольно просто с этим примером.

UPDATE 2015-06-03

ОК, я, наконец, имел возможность посмотреть на обновленный код. Несколько вещей торчат.

  1. Просто для подтверждения: три распознавателя жестов имеют верхний слой, установленный как recognizer.view правильно? Значит, они только приспосабливают призрак?
  2. Вы закомментировали topLayerImage.contentMode = UIViewContentMode.ScaleAspectFit, что означает, что призрак будет растянут, если он имеет разный формат, чем topLayerImage. Я подозреваю, что вы хотите, чтобы он был раскопан.
  3. В моей игровой площадке originalPhotoFrame & backgroundLayerFrame были созданы просто потому, что у меня не было всего вашего кода для работы и были предназначены для тестирования/данных образца. Я не уверен, почему вы делаете то, что делаете в combineImagesButton, где вы смотрите, является ли изображение шире, чем оно высокое, но я думаю, что это неправильно. Вместо originalPhotoFrame.[width|height] вы можете использовать originalPhoto.size.[width|height] и вместо backgroundLayerFrame вы можете использовать bottomLayerImage.frame при расчетах myScale.
  4. Расчетов в моей игровой площадке для scaledSize и scaledOrigin были просто повторить то, что bottomLayerImage делает из-за .ScaleAspectFit: он растягивает изображение настолько, насколько это возможно без искажений, а затем он сосредотачивает масштабируемое изображение в кадре. Репликация, что для призрака не работает, потому что распознаватели регулируют topLayerImage.transform, что означает, что вы не можете использовать topLayerImage.frame (согласно предупреждению в документации UIView для свойства transform.) Моя первая мысль здесь заключается в том, что кеширование оригинала topLayerImage.frame в viewDidLoad и просто используйте исходное значение в итоговой математике, но см. № 6 ниже о предостережении о повороте.
  5. Вам не нужно scaledOrigin, если вы создаете контекст, который должен быть scaledSize и рисунком в источнике, как и вы. scaledOrigin должен был только рассчитать scaledPhotoRect, который вы не используете (так что потеряйте его также).
  6. Вы управляете поворотом устройства? Если пользователь поворачивается от портрета к ландшафту, вам нужно приспособиться к нему в любой призрачной математике. Если вы не выполняете ротацию, я предлагаю кэшировать topLayerImage.frame в viewDidLoad, но имейте в виду, что вам придется обновить это, если вы поддерживаете ландшафтную ориентацию. Я не уверен, что происходит, когда вы поворачиваете устройство, и ваше представление использует не-идентичное преобразование, и я думаю, что ответ отличается для разных версий iOS. Оффлайн Я думаю, вам нужно будет спрятать свое преобразование, вернуть его в личность, а затем снова зачитать рамку, но это вызовет визуальный поп. Может быть, вы могли бы уйти от этого, вызвав layoutSubviews, получив ваше значение, вернув преобразование и снова позвонив layoutSubviews, чтобы все это происходило без промежуточного рендера, но я не уверен. В любом случае, просто знайте, что вам нужно проверить, что происходит во время вращения.

Что делать с призраком

  • У вас есть две разные трансформирует вам нужно применить. Первая - это репликация того, что .ScaleAspectFit сделало для вас в первую очередь, а второе - это комбинированные изменения, сделанные пользователем с помощью распознавателей жестов.
  • Если изменить код ghostScaledSize таким же образом я перечисляю выше для scaledSize, но использовать кэшированные topLayerImage.frame значение, а затем просто позвоните chosenGhostPhoto!.drawInRect(CGRect(origin: CGPointZero, size: scaledSize)) вы должны получить привидение, в оригинальном размере изображения, в верхнем левом углу финала образ. Я бы сделал это и подтвердил, что это работает.
  • Как только вы достигли этого, все дело в очень хорошей форме. Теперь вы можете просто взять bottomLayerImage.transform и применить его к контексту, используя CGContextConcatCTM, прежде чем рисовать призрачное изображение. (Или, возможно, вам нужно принять обратный преобразования? Я не уверен, и это должно быть очевидно при тестировании.) Важно сделать это как одну операцию, потому что если вы отдельно применяете поворот &, «Попробуйте сделать это, вы должны сделать их в том же порядке, что и iOS. Кроме того, на данный момент у вас уже есть преобразование, поэтому нет причин его разлагать, а затем применять каждую операцию отдельно. Вы все равно хотите применить обе части.
+0

Несомненно, ниже приведены некоторые скриншоты. Во-первых, вы можете видеть, как кто-то может настроить topLayerImage (призрак), а bottomLayerImage - только один, выбранный из рулона камеры. Второе изображение - результат. Третий - это перемещенная фотография призрака, чтобы вы могли видеть ее лучше. Оказывается, пропорции этого изображения остаются, что нельзя сказать о фоновом изображении. http://imgur.com/7ymXJG7,6OJdwVW,jD9byza http://imgur.com/7ymXJG7,6OJdwVW,jD9byza#1 http://imgur.com/7ymXJG7,6OJdwVW,jD9byza#2 – Chris

+0

Посмотрев более внимательно, я стою исправлено: изображение призрака, кажется, искажает некоторые. – Chris

+0

@ Крис делает код в моем отредактированном ответе, который помогает вам решить проблему? Если да, можете ли вы принять ответ, а если нет, можете ли вы рассказать мне больше о продолжающейся трудности? –