diff --git a/Signal/src/ViewControllers/CropScaleImageViewController.swift b/Signal/src/ViewControllers/CropScaleImageViewController.swift index ad5bbca1f..09c0d0166 100644 --- a/Signal/src/ViewControllers/CropScaleImageViewController.swift +++ b/Signal/src/ViewControllers/CropScaleImageViewController.swift @@ -204,13 +204,16 @@ class CropScaleImageViewController: OWSViewController { let maskingView = OWSBezierPathView() contentView.addSubview(maskingView) - maskingView.configureShapeLayerBlock = { layer, bounds in + maskingView.configureShapeLayerBlock = { [weak self] layer, bounds in + guard let strongSelf = self else { + return + } let path = UIBezierPath(rect: bounds) - let radius = min(bounds.size.width, bounds.size.height) * 0.5 - self.maskMargin - // Center the circle's bounding rectangle - let circleRect = CGRect(x: bounds.size.width * 0.5 - radius, y: bounds.size.height * 0.5 - radius, width: radius * 2, height: radius * 2) + let circleRect = strongSelf.cropFrame(forBounds:bounds) + let radius = circleRect.size.width * 0.5 let circlePath = UIBezierPath(roundedRect: circleRect, cornerRadius: radius) + path.append(circlePath) path.usesEvenOddFillRule = true @@ -239,6 +242,15 @@ class CropScaleImageViewController: OWSViewController { contentView.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(handlePan(sender:)))) } + // Given the current bounds for the image view, return the frame of the + // crop region within that view. + private func cropFrame(forBounds bounds: CGRect) -> CGRect { + let radius = min(bounds.size.width, bounds.size.height) * 0.5 - self.maskMargin + // Center the circle's bounding rectangle + let circleRect = CGRect(x: bounds.size.width * 0.5 - radius, y: bounds.size.height * 0.5 - radius, width: radius * 2, height: radius * 2) + return circleRect + } + override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() @@ -294,11 +306,14 @@ class CropScaleImageViewController: OWSViewController { return } - let viewSizePoints = imageView.frame.size + // The size of the image view (should be full screen). + let imageViewSizePoints = imageView.frame.size guard - (viewSizePoints.width > 0 && viewSizePoints.height > 0) else { + (imageViewSizePoints.width > 0 && imageViewSizePoints.height > 0) else { return } + // The frame of the crop circle within the image view. + let cropFrame = self.cropFrame(forBounds:CGRect(origin:CGPoint.zero, size: imageViewSizePoints)) // Normalize the scaling property. imageScale = max(kMinImageScale, min(kMaxImageScale, imageScale)) @@ -317,29 +332,29 @@ class CropScaleImageViewController: OWSViewController { srcTranslation = CGPoint(x: max(minSrcTranslationPoints.x, min(maxSrcTranslationPoints.x, srcTranslation.x)), y: max(minSrcTranslationPoints.y, min(maxSrcTranslationPoints.y, srcTranslation.y))) - var imageViewFrame = imageRenderRect(forDstSize: viewSizePoints) - - // offset to vertically center image in view (aspect ratio?) - let srcToViewRatio = viewSizePoints.width / srcCropSizePoints.width / imageScale - let heightOfImageLayer = srcDefaultCropSizePoints.height * srcToViewRatio - let yOffset = imageView.frame.height * 0.5 - heightOfImageLayer * 0.5 - imageViewFrame = imageViewFrame.offsetBy(dx: 0, dy: yOffset) - - // inset frame by the with of the masking margin so the user can pan to the very edge of the image - imageViewFrame = CGRect(x: imageViewFrame.origin.x + self.maskMargin, - y: imageViewFrame.origin.y + self.maskMargin, - width: imageViewFrame.width - maskMargin * 2, - height: imageViewFrame.height - maskMargin * 2) + // The frame of the image layer in crop frame coordinates. + let rawImageLayerFrame = imageRenderRect(forDstSize: cropFrame.size) + // The frame of the image layer in image view coordinates. + let imageLayerFrame = CGRect(x: rawImageLayerFrame.origin.x + cropFrame.origin.x, + y: rawImageLayerFrame.origin.y + cropFrame.origin.y, + width: rawImageLayerFrame.size.width, + height: rawImageLayerFrame.size.height) // Disable implicit animations for snappier panning/zooming. CATransaction.begin() CATransaction.setDisableActions(true) - imageLayer.frame = imageViewFrame + imageLayer.frame = imageLayerFrame CATransaction.commit() } + // Give the size of a given view or image context into which we + // will render the source image, return the frame (in that + // view/context's coordinate system) to render the source image. + // + // Gathering this logic in a single function ensures that the + // output will be WYSIWYG with the view state. private func imageRenderRect(forDstSize dstSize: CGSize) -> CGRect { let srcCropSizePoints = CGSize(width:srcDefaultCropSizePoints.width / imageScale, @@ -347,10 +362,10 @@ class CropScaleImageViewController: OWSViewController { let srcToViewRatio = dstSize.width / srcCropSizePoints.width - return CGRect(origin: CGPoint(x: srcTranslation.x * -srcToViewRatio, - y: srcTranslation.y * -srcToViewRatio), - size: CGSize(width:srcImageSizePoints.width * +srcToViewRatio, - height:srcImageSizePoints.height * +srcToViewRatio + return CGRect(origin: CGPoint(x:srcTranslation.x * -srcToViewRatio, + y:srcTranslation.y * -srcToViewRatio), + size: CGSize(width:srcImageSizePoints.width * +srcToViewRatio, + height:srcImageSizePoints.height * +srcToViewRatio )) }