PQImageCropVC.swift 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. //
  2. // PQImageCropVC.swift
  3. // PQSpeed
  4. //
  5. // Created by ak on 2020/8/3.
  6. // Copyright © 2020 BytesFlow. All rights reserved.
  7. //
  8. // add by ak 主要功能 图片裁剪界面
  9. // e.g.
  10. // let image = UIImage(named:"img.jpg")!
  11. // let vc = PQImageCropVC(frame: (self.navigationController?.view.frame)!, image: image, aspectWidth:9, aspectHeight: 16)
  12. import BFUIKit
  13. import UIKit
  14. import BFCommonKit
  15. class PQImageCropVC: BFBaseViewController, UIScrollViewDelegate {
  16. var uploadData: PQUploadModel?
  17. var updataVideoData: PQVideoListModel? // 如果updataVideoData不为空则为修改视频
  18. var aspectW: CGFloat!
  19. var aspectH: CGFloat!
  20. var img: UIImage!
  21. var imageView: UIImageView!
  22. var scrollView: UIScrollView!
  23. var holeRect: CGRect!
  24. var gap: CGFloat = 100 // 距屏幕边缘距离
  25. required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") }
  26. init(image: UIImage, aspectWidth: CGFloat, aspectHeight: CGFloat) {
  27. super.init(nibName: nil, bundle: nil)
  28. aspectW = aspectWidth
  29. aspectH = aspectHeight
  30. img = image
  31. BFLog(message: "原图大小 \(img.size) aspectW:\(aspectW!) aspectH:\(aspectH!)")
  32. }
  33. override func viewDidLoad() {
  34. super.viewDidLoad()
  35. setupView()
  36. }
  37. func setupView() {
  38. let holeWidth = view.frame.width - gap
  39. BFLog(message: "aspectH :\(String(describing: aspectH))")
  40. var holeHeight = holeWidth * aspectH / aspectW
  41. if holeHeight.isNaN {
  42. holeHeight = 0
  43. }
  44. if img.imageOrientation != .up {
  45. UIGraphicsBeginImageContextWithOptions(img.size, false, img.scale)
  46. var rect = CGRect.zero
  47. rect.size = img.size
  48. img.draw(in: rect)
  49. img = UIGraphicsGetImageFromCurrentImageContext()
  50. UIGraphicsEndImageContext()
  51. }
  52. holeRect = CGRect(x: 0, y: view.frame.height / 2 - holeHeight / 2, width: holeWidth, height: holeHeight)
  53. imageView = UIImageView(image: img)
  54. scrollView = UIScrollView(frame: CGRect(x: gap / 2.0, y: 0, width: holeWidth, height: holeHeight))
  55. scrollView.center = view.center
  56. scrollView.addSubview(imageView)
  57. scrollView.showsVerticalScrollIndicator = false
  58. scrollView.showsHorizontalScrollIndicator = false
  59. scrollView.alwaysBounceHorizontal = true
  60. scrollView.alwaysBounceVertical = true
  61. scrollView.delegate = self
  62. view.addSubview(scrollView)
  63. // 设置最小 zoom 是防止在裁剪区出现黑边导致裁剪后的图不对
  64. let minZoom = max(holeWidth / img.size.width, holeHeight / img.size.height)
  65. BFLog(message: "minZoom is: \(minZoom)")
  66. scrollView.minimumZoomScale = minZoom
  67. scrollView.maximumZoomScale = minZoom * 4
  68. // 后设置自动缩放
  69. scrollView.setZoomScale(minZoom, animated: false)
  70. scrollView.clipsToBounds = false
  71. scrollView.contentOffset = CGPoint(x: (img.size.width * minZoom - holeWidth) / 2, y: (img.size.height * minZoom - holeHeight) / 2)
  72. // 添加 mark layer
  73. let cropView = UIView(frame: view.frame)
  74. cropView.isUserInteractionEnabled = false
  75. view.addSubview(cropView)
  76. let layer = CAShapeLayer()
  77. let clipWidth = holeWidth
  78. let clipHeight = holeHeight
  79. let cropAreaX = (cScreenWidth - clipWidth) / 2
  80. let cropAreaY = (cScreenHeigth - clipHeight) / 2
  81. let cropAreaWidth = clipWidth
  82. let cropAreaHeight = clipHeight
  83. let cropframe = CGRect(x: cropAreaX, y: cropAreaY, width: cropAreaWidth, height: cropAreaHeight)
  84. let path = UIBezierPath(roundedRect: cropView.frame, cornerRadius: 0)
  85. let cropPath = UIBezierPath(rect: cropframe)
  86. path.append(cropPath)
  87. layer.path = path.cgPath
  88. layer.fillRule = CAShapeLayerFillRule.evenOdd
  89. layer.fillColor = UIColor.black.cgColor
  90. layer.opacity = 0.5
  91. layer.frame = cropView.bounds
  92. cropView.layer.addSublayer(layer)
  93. view.bringSubviewToFront(cropView)
  94. // 单独画线
  95. let drawLine = DrawLine()
  96. drawLine.setCropAreaLeft(cropAreaLeft: cropAreaX, cropAreaTop: cropAreaY, cropAreaRight: cropAreaX + cropAreaWidth, cropAreaBottom: cropAreaY + cropAreaHeight)
  97. drawLine.frame = cropView.bounds
  98. cropView.layer.addSublayer(drawLine)
  99. view.bringSubviewToFront(cropView)
  100. // 添加下边工具条
  101. view.addSubview(bottomView)
  102. // 显示引导
  103. let haveShowGuid = getUserDefaults(key: cIsShowImageCropGuid)
  104. if haveShowGuid == nil {
  105. // let guid = PQGuidClipView(frame: view.frame)
  106. // view.addSubview(guid)
  107. // saveUserDefaults(key: cIsShowImageCropGuid, value: cIsShowImageCropGuid)
  108. }
  109. }
  110. lazy var bottomView: UIView = {
  111. let bottomView = UIView(frame: CGRect(x: 0, y: cScreenHeigth - cDevice_iPhoneTabBarHei, width: cScreenWidth, height: cDevice_iPhoneTabBarHei))
  112. let deleteBtn = UIButton(frame: CGRect(x: cDefaultMargin, y: 0, width: cDevice_iPhoneTabBarHei, height: cDevice_iPhoneTabBarHei))
  113. bottomView.backgroundColor = BFConfig.shared.styleBackGroundColor
  114. deleteBtn.setImage(imageInUIKit(by: "icon_detail_back"), for: .normal)
  115. deleteBtn.addTarget(self, action: #selector(tappedClose), for: .touchUpInside)
  116. bottomView.addSubview(deleteBtn)
  117. let selecteBtn = UIButton(frame: CGRect(x: 0, y: 0, width: 100, height: cDevice_iPhoneTabBarHei))
  118. selecteBtn.setTitle("裁剪封面", for: .normal)
  119. selecteBtn.setTitleColor(.black, for: .normal)
  120. selecteBtn.titleLabel?.font = UIFont.systemFont(ofSize: 16, weight: .medium)
  121. bottomView.addSubview(selecteBtn)
  122. selecteBtn.center.x = bottomView.center.x
  123. let nextBtn = UIButton(frame: CGRect(x: bottomView.frame.width - 100, y: (cDevice_iPhoneTabBarHei - 40) / 2, width: 80, height: 40))
  124. nextBtn.addCorner(corner: 20)
  125. nextBtn.setTitle("确定", for: .normal)
  126. nextBtn.addTarget(self, action: #selector(tappedCrop), for: .touchUpInside)
  127. nextBtn.setTitleColor(UIColor.white, for: .normal)
  128. nextBtn.titleLabel?.font = UIFont.systemFont(ofSize: 16, weight: .medium)
  129. nextBtn.backgroundColor = UIColor.hexColor(hexadecimal: BFConfig.shared.styleColor.rawValue)
  130. bottomView.addSubview(nextBtn)
  131. return bottomView
  132. }()
  133. // MARK: scrollView delegate
  134. func viewForZooming(in _: UIScrollView) -> UIView? {
  135. BFLog(message: "viewForZooming")
  136. // 是imageView的容器,实现这个代理保证图片的放大拖动交互
  137. return imageView
  138. }
  139. func scrollViewDidZoom(_ scrollView: UIScrollView) {
  140. BFLog(message: "scrollViewDidZoom")
  141. let gapToTheHole = scrollView.frame.height / 2 - holeRect.height / 2
  142. scrollView.contentInset = UIEdgeInsets(top: gapToTheHole, left: 0, bottom: gapToTheHole, right: 0)
  143. }
  144. // 返回
  145. @objc func tappedClose() {
  146. BFLog(message: "返回")
  147. // add by ak bugfix 图片超出 返回异常
  148. navigationController?.popViewController(animated: true)
  149. }
  150. override func viewWillAppear(_ animated: Bool) {
  151. super.viewWillAppear(animated)
  152. scrollView.isHidden = false
  153. }
  154. override func viewWillDisappear(_ animated: Bool) {
  155. super.viewWillDisappear(animated)
  156. scrollView.isHidden = true
  157. }
  158. // 裁剪
  159. @objc func tappedCrop() {
  160. BFLog(message: "开始裁剪图片")
  161. // 根据scrollView缩放系数和偏移量 对原图进行裁剪
  162. var imgX: CGFloat = 0
  163. if scrollView.contentOffset.x > 0 {
  164. imgX = scrollView.contentOffset.x / scrollView.zoomScale
  165. }
  166. let gapToTheHole: CGFloat = 0
  167. var imgY: CGFloat = 0
  168. if scrollView.contentOffset.y + 0 > 0 {
  169. imgY = (scrollView.contentOffset.y + gapToTheHole) / scrollView.zoomScale
  170. }
  171. let imgW = holeRect.width / scrollView.zoomScale
  172. let imgH = holeRect.height / scrollView.zoomScale
  173. print("IMG x: \(imgX) y: \(imgY) w: \(imgW) h: \(imgH)")
  174. let cropRect = CGRect(x: imgX, y: imgY, width: imgW, height: imgH)
  175. let imageRef = img.cgImage!.cropping(to: cropRect)
  176. let croppedImage = UIImage(cgImage: imageRef!)
  177. // 这个方法是使用view 截屏
  178. // let croppedImage : UIImage = scrollView.graphicsGetImage() ?? img
  179. BFLog(message: "croppedImage\(croppedImage.size)")
  180. postNotification(name: cSelectedImageSuccessKey, userInfo: ["image": croppedImage])
  181. let publicVideoVC = navigationController?.viewControllers.first(where: { (vc) -> Bool in
  182. vc is PQStuckPointPublicController
  183. })
  184. if publicVideoVC != nil {
  185. navigationController?.popToViewController(publicVideoVC!, animated: true)
  186. } else {
  187. navigationController?.popViewController(animated: true)
  188. }
  189. }
  190. }
  191. class DrawLine: CAShapeLayer {
  192. var mCropAreaLeft: CGFloat = 0
  193. var mCropAreaTop: CGFloat = 0
  194. var mCropAreaRight: CGFloat = cScreenWidth
  195. var mCropAreaBottom: CGFloat = 0
  196. let mLineWidth: CGFloat = 7
  197. func setCropAreaLeft(cropAreaLeft: CGFloat, cropAreaTop: CGFloat, cropAreaRight: CGFloat, cropAreaBottom: CGFloat) {
  198. mCropAreaLeft = cropAreaLeft
  199. mCropAreaTop = cropAreaTop
  200. mCropAreaRight = cropAreaRight
  201. mCropAreaBottom = cropAreaBottom
  202. setNeedsDisplay()
  203. }
  204. override func draw(in ctx: CGContext) {
  205. UIGraphicsPushContext(ctx)
  206. ctx.setStrokeColor(UIColor.white.cgColor)
  207. ctx.setLineWidth(mLineWidth)
  208. ctx.addRect(CGRect(x: mCropAreaLeft, y: mCropAreaTop, width: mCropAreaRight - mCropAreaLeft, height: mCropAreaBottom - mCropAreaTop))
  209. // 必和路径
  210. ctx.strokePath()
  211. UIGraphicsPopContext()
  212. }
  213. }