2015-05-11 40 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

handlePinchhandleRotate父視圖設置將調整,因爲這bottomLayerImage.frame的變換真實存儲在上海華座標,但ScaleAspectFit工作不被施加到所述框架。

這意味着在combineImagesButton計算size的算術是錯誤的,並且當您在該矩形中繪製originalPhoto時,它會變形。要獲得您要查找的效果,您需要計算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) 

你會想要做同樣的事情chosenGhostPhototopLayerImage和結合起來,與數學你已經做了鬼了,應該是與這個例子很簡單。

UPDATE採用WordPress

OK,我終於有機會看看你更新的代碼。有幾件事突出了。

  1. 只是爲了確認:三個手勢識別器都將topLayerImage設置爲recognizer.view正確嗎?所以他們只是在調整鬼魂?
  2. 您註釋掉topLayerImage.contentMode = UIViewContentMode.ScaleAspectFit這意味着如果幽靈的寬高比不同於topLayerImage,則幽靈會拉長。我懷疑你想要那個沒有評論。
  3. 在我的遊樂場originalPhotoFrame & backgroundLayerFrame的設置原因很簡單,因爲我沒有完整的代碼來處理測試/採樣數據。我不確定你爲什麼在做combineImagesButton在做什麼,你在看圖像是否比它高,但我認爲它是錯的。您可以使用originalPhoto.size.[width|height]而不是backgroundLayerFrame,而在計算myScale時可以使用bottomLayerImage.frame
  4. 我在操場scaledSizescaledOrigin的計算是簡單地複製什麼bottomLayerImage是因爲.ScaleAspectFit的做:它一樣,因爲它可以拉伸圖像不失真,然後將其中心在框架縮放後的圖像。因爲識別器會調整topLayerImage.transform,這意味着您不能使用topLayerImage.frame(根據transform屬性的UIView文檔中的警告),所以爲鬼影複製不起作用。我首先想到的是將原始topLayerImage.frame緩存在viewDidLoad中,並且只需使用最終數學中的原始值,但請參閱下面關於旋轉警告的#6。
  5. 如果您將上下文創建爲scaledSize並且像您一樣繪製原點,則不需要scaledOriginscaledOrigin只是計算你沒有使用的scaledPhotoRect(所以也丟掉它)。
  6. 你正在處理設備旋轉嗎?如果用戶從縱向旋轉到橫向,則需要在任何鬼數學中進行調整。如果你沒有進行輪換,我建議在viewDidLoad中緩存topLayerImage.frame,但是請注意,如果你支持橫向方向,你必須更新它。我不確定當您旋轉設備時會發生什麼,並且您的視圖應用了非身份轉換,我認爲對於不同版本的iOS,答案是不同的。 Offhand我認爲你將需要存儲你的轉換,將其設置回身份,然後再次讀出框架,但這會導致視覺流行。也許你可以逃避這樣做,打電話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

@Chris在我編輯的答案中執行代碼可以幫助您解決問題?如果是這樣,你能接受答案,如果沒有,你能告訴我更多關於持續的困難嗎? –