Respond to CR.

// FREEBIE
This commit is contained in:
Matthew Chen 2017-08-25 16:50:14 -04:00
parent 2aaa9155de
commit c90ca331e3

View file

@ -6,7 +6,7 @@ import Foundation
import MediaPlayer
class OWSLayerView: UIView {
var layoutCallback : (() -> Void)?
let layoutCallback : (() -> Void)
required init(frame: CGRect, layoutCallback : @escaping () -> Void) {
self.layoutCallback = layoutCallback
@ -21,23 +21,36 @@ class OWSLayerView: UIView {
override var bounds: CGRect {
didSet {
guard let layoutCallback = self.layoutCallback else {
return
}
layoutCallback()
}
}
override var frame: CGRect {
didSet {
guard let layoutCallback = self.layoutCallback else {
return
}
layoutCallback()
}
}
}
// This kind of view is tricky. I've tried to organize things in the
// simplest possible way.
//
// I've tried to avoid the following sources of confusion:
//
// * Points vs. pixels. All variables should have names that
// reflect the units. Pretty much everything is done in points
// except rendering of the output image which is done in pixels.
// * Coordinate systems. You have a) the src image coordinates
// b) the image view coordinates c) the output image coordinates.
// Wherever possible, I've tried to use src image coordinates.
// * Translation & scaling vs. crop region. The crop region is
// implicit. We represent the crop state using the translation
// and scaling of the "default" crop region (the largest possible
// crop region, at the origin (upper left) of the source image.
// Given the translation & scaling, we can determine a) the crop
// region b) the rectangle at which the src image should be rendered
// given a dst view or output context that will yield the
// appropriate cropping.
class CropScaleImageViewController: OWSViewController {
let TAG = "[CropScaleImageViewController]"
@ -46,14 +59,14 @@ class CropScaleImageViewController: OWSViewController {
let srcImage: UIImage
var successCompletion: ((UIImage) -> Void)?
let successCompletion: ((UIImage) -> Void)
var imageView: UIView?
var imageView: UIView!
// We use a CALayer to render the image for performance reasons.
var imageLayer: CALayer?
var imageLayer: CALayer!
var dashedBorderLayer: CAShapeLayer?
var dashedBorderLayer: CAShapeLayer!
// In width/height.
//
@ -87,9 +100,11 @@ class CropScaleImageViewController: OWSViewController {
// MARK: Initializers
@available(*, unavailable, message:"use attachment: constructor instead.")
@available(*, unavailable, message:"use srcImage:successCompletion: constructor instead.")
required init?(coder aDecoder: NSCoder) {
self.srcImage = UIImage(named:"fail")!
self.successCompletion = { _ in
}
super.init(coder: aDecoder)
owsFail("\(self.TAG) invalid constructor")
@ -108,7 +123,9 @@ class CropScaleImageViewController: OWSViewController {
// MARK: Cropping and Scaling
private func configureCropAndScale() {
// Size of bounding box that reflects the target aspect ratio, whose longer side = 1.
// We use a "unit" view size (long dimension of length 1, short dimension reflects
// the dst aspect ratio) since we want to be able to perform this logic before we
// know the actual size of the cropped image view.
let unitSquareHeight: CGFloat = (dstAspectRatio >= 1.0 ? 1.0 : 1.0 / dstAspectRatio)
let unitSquareWidth: CGFloat = (dstAspectRatio >= 1.0 ? dstAspectRatio * unitSquareHeight : 1.0)
let unitSquareSize = CGSize(width: unitSquareWidth, height: unitSquareHeight)
@ -207,10 +224,6 @@ class CropScaleImageViewController: OWSViewController {
contentView.isUserInteractionEnabled = true
contentView.addGestureRecognizer(UIPinchGestureRecognizer(target: self, action: #selector(handlePinch(sender:))))
contentView.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(handlePan(sender:))))
let doubleTap = UITapGestureRecognizer(target: self, action: #selector(handleDoubleTap(sender:)))
doubleTap.numberOfTapsRequired = 2
doubleTap.numberOfTouchesRequired = 1
contentView.addGestureRecognizer(doubleTap)
}
override func viewDidLayoutSubviews() {
@ -232,6 +245,10 @@ class CropScaleImageViewController: OWSViewController {
updateImageLayout()
}
// Given a src image size and a dst view size, this finds the bounds
// of the largest rectangular crop region with the correct dst aspect
// ratio that fits in the src image's aspect ratio, in src image point
// coordinates.
private func defaultCropFramePoints(imageSizePoints: CGSize, viewSizePoints: CGSize) -> (CGRect) {
let imageAspectRatio = imageSizePoints.width / imageSizePoints.height
let viewAspectRatio = viewSizePoints.width / viewSizePoints.height
@ -257,12 +274,6 @@ class CropScaleImageViewController: OWSViewController {
guard let imageView = self.imageView else {
return
}
guard let imageLayer = self.imageLayer else {
return
}
guard let dashedBorderLayer = self.dashedBorderLayer else {
return
}
guard srcImageSizePoints.width > 0 && srcImageSizePoints.height > 0 else {
return
}
@ -297,10 +308,22 @@ class CropScaleImageViewController: OWSViewController {
CATransaction.begin()
CATransaction.setDisableActions(true)
imageLayer.frame = imageViewFrame
CATransaction.commit()
// Mask to circle.
let maskLayer = CAShapeLayer()
maskLayer.frame = imageViewFrame
maskLayer.fillRule = kCAFillRuleEvenOdd
let maskFrame = CGRect(origin:CGPoint(x:-imageViewFrame.origin.x * 2,
y: -imageViewFrame.origin.y * 2),
size:imageView.bounds.size)
maskLayer.path =
CGPath(ellipseIn: maskFrame, transform: nil)
imageLayer.mask = maskLayer
dashedBorderLayer.frame = imageView.bounds
dashedBorderLayer.path = UIBezierPath(rect: imageView.bounds).cgPath
CATransaction.commit()
}
private func imageRenderRect(forDstSize dstSize: CGSize) -> CGRect {
@ -335,10 +358,6 @@ class CropScaleImageViewController: OWSViewController {
lastPinchScale = sender.scale
break
case .changed, .ended:
guard let imageView = self.imageView else {
return
}
if sender.numberOfTouches > 1 {
let location =
sender.location(in: sender.view)
@ -391,9 +410,6 @@ class CropScaleImageViewController: OWSViewController {
srcTranslationAtPanStart = srcTranslation
break
case .changed, .ended:
guard let imageView = self.imageView else {
return
}
let viewSizePoints = imageView.frame.size
let srcCropSizePoints = CGSize(width:srcDefaultCropSizePoints.width / imageScale,
height:srcDefaultCropSizePoints.height / imageScale)
@ -416,16 +432,6 @@ class CropScaleImageViewController: OWSViewController {
updateImageLayout()
}
func handleDoubleTap(sender: UIPanGestureRecognizer) {
if (sender.state == .recognized) {
if imageScale > 1.5 {
imageScale = kMinImageScale
}
}
updateImageLayout()
}
private func createButtonRow(contentView: UIView) {
let buttonTopMargin = ScaleFromIPhone5To7Plus(30, 40)
let buttonBottomMargin = ScaleFromIPhone5To7Plus(25, 40)
@ -477,7 +483,7 @@ class CropScaleImageViewController: OWSViewController {
guard let dstImage = self.generateDstImage() else {
return
}
successCompletion?(dstImage)
successCompletion(dstImage)
})
}
@ -488,12 +494,15 @@ class CropScaleImageViewController: OWSViewController {
let dstScale: CGFloat = 1.0 // The size is specified in pixels, not in points.
UIGraphicsBeginImageContextWithOptions(dstSizePixels, !hasAlpha, dstScale)
let context = UIGraphicsGetCurrentContext()
context!.interpolationQuality = .high
let imageViewFrame = imageRenderRect(forDstSize:dstSizePixels)
srcImage.draw(in:imageViewFrame)
let scaledImage = UIGraphicsGetImageFromCurrentImageContext()
if scaledImage == nil {
Logger.error("\(TAG) could not generate dst image.")
owsFail("\(TAG) could not generate dst image.")
}
UIGraphicsEndImageContext()
return scaledImage