Add crop gesture.

pull/1/head
Matthew Chen 6 years ago
parent 03cbeb5fee
commit 57f888a447

@ -201,15 +201,6 @@ public class ImageEditorView: UIView, ImageEditorModelDelegate {
self.currentStrokeSamples.removeAll() self.currentStrokeSamples.removeAll()
} }
let referenceView = layersView
let unitSampleForGestureLocation = { () -> CGPoint in
// TODO: Smooth touch samples before converting into stroke samples.
let location = gestureRecognizer.location(in: referenceView)
let x = CGFloatClamp01(CGFloatInverseLerp(location.x, 0, referenceView.bounds.width))
let y = CGFloatClamp01(CGFloatInverseLerp(location.y, 0, referenceView.bounds.height))
return CGPoint(x: x, y: y)
}
// TODO: Color picker. // TODO: Color picker.
let strokeColor = UIColor.blue let strokeColor = UIColor.blue
// TODO: Tune stroke width. // TODO: Tune stroke width.
@ -219,14 +210,14 @@ public class ImageEditorView: UIView, ImageEditorModelDelegate {
case .began: case .began:
removeCurrentStroke() removeCurrentStroke()
currentStrokeSamples.append(unitSampleForGestureLocation()) currentStrokeSamples.append(unitSampleForGestureLocation(gestureRecognizer))
let stroke = ImageEditorStrokeItem(color: strokeColor, unitSamples: currentStrokeSamples, unitStrokeWidth: unitStrokeWidth) let stroke = ImageEditorStrokeItem(color: strokeColor, unitSamples: currentStrokeSamples, unitStrokeWidth: unitStrokeWidth)
model.append(item: stroke) model.append(item: stroke)
currentStroke = stroke currentStroke = stroke
case .changed, .ended: case .changed, .ended:
currentStrokeSamples.append(unitSampleForGestureLocation()) currentStrokeSamples.append(unitSampleForGestureLocation(gestureRecognizer))
guard let lastStroke = self.currentStroke else { guard let lastStroke = self.currentStroke else {
owsFailDebug("Missing last stroke.") owsFailDebug("Missing last stroke.")
@ -250,67 +241,104 @@ public class ImageEditorView: UIView, ImageEditorModelDelegate {
} }
} }
private func unitSampleForGestureLocation(_ gestureRecognizer: UIGestureRecognizer) -> CGPoint {
let referenceView = layersView
// TODO: Smooth touch samples before converting into stroke samples.
let location = gestureRecognizer.location(in: referenceView)
let x = CGFloatClamp01(CGFloatInverseLerp(location.x, 0, referenceView.bounds.width))
let y = CGFloatClamp01(CGFloatInverseLerp(location.y, 0, referenceView.bounds.height))
return CGPoint(x: x, y: y)
}
// MARK: - Crop // MARK: - Crop
private var cropStartUnit = CGPoint.zero
private var cropEndUnit = CGPoint.zero
private var cropLayer1 = CAShapeLayer()
private var cropLayer2 = CAShapeLayer()
private var cropLayers: [CAShapeLayer] {
return [cropLayer1, cropLayer2]
}
@objc @objc
public func handleCropGesture(_ gestureRecognizer: UIGestureRecognizer) { public func handleCropGesture(_ gestureRecognizer: UIGestureRecognizer) {
AssertIsOnMainThread() AssertIsOnMainThread()
// let removeCurrentStroke = { let kCropDashLength: CGFloat = 3
// if let stroke = self.currentStroke { let cancelCrop = {
// self.model.remove(item: stroke) for cropLayer in self.cropLayers {
// } cropLayer.removeFromSuperlayer()
// self.currentStroke = nil cropLayer.removeAllAnimations()
// self.currentStrokeSamples.removeAll() }
// } }
// let updateCropLayer = { (cropLayer: CAShapeLayer) in
// let referenceView = self cropLayer.fillColor = nil
// let unitSampleForGestureLocation = { () -> CGPoint in cropLayer.lineWidth = 1.0
// // TODO: Smooth touch samples before converting into stroke samples. cropLayer.lineDashPattern = [NSNumber(value: Double(kCropDashLength)), NSNumber(value: Double(kCropDashLength))]
// let location = gestureRecognizer.location(in: referenceView)
// let x = CGFloatClamp01(CGFloatInverseLerp(location.x, 0, referenceView.bounds.width)) let viewSize = self.layersView.bounds.size
// let y = CGFloatClamp01(CGFloatInverseLerp(location.y, 0, referenceView.bounds.height)) cropLayer.frame = CGRect(origin: .zero, size: viewSize)
// return CGPoint(x: x, y: y)
// } // Find the upper-left and bottom-right corners of the
// // crop rectangle, in unit coordinates.
// // TODO: Color picker. let unitMin = CGPointMin(self.cropStartUnit, self.cropEndUnit)
// let strokeColor = UIColor.blue let unitMax = CGPointMax(self.cropStartUnit, self.cropEndUnit)
// // TODO: Tune stroke width.
// let unitStrokeWidth = ImageEditorStrokeItem.defaultUnitStrokeWidth() let transformSampleToPoint = { (unitSample: CGPoint) -> CGPoint in
// return CGPoint(x: viewSize.width * unitSample.x,
// switch gestureRecognizer.state { y: viewSize.height * unitSample.y)
// case .began: }
// removeCurrentStroke()
// // Convert from unit coordinates to view coordinates.
// currentStrokeSamples.append(unitSampleForGestureLocation()) let pointMin = transformSampleToPoint(unitMin)
// let pointMax = transformSampleToPoint(unitMax)
// let stroke = ImageEditorStrokeItem(color: strokeColor, unitSamples: currentStrokeSamples, unitStrokeWidth: unitStrokeWidth) let cropRect = CGRect(x: pointMin.x,
// model.append(item: stroke) y: pointMin.y,
// currentStroke = stroke width: pointMax.x - pointMin.x,
// height: pointMax.y - pointMin.y)
// case .changed, .ended: let bezierPath = UIBezierPath(rect: cropRect)
// currentStrokeSamples.append(unitSampleForGestureLocation()) cropLayer.path = bezierPath.cgPath
// }
// guard let lastStroke = self.currentStroke else { let updateCrop = {
// owsFailDebug("Missing last stroke.") updateCropLayer(self.cropLayer1)
// removeCurrentStroke() updateCropLayer(self.cropLayer2)
// return self.cropLayer1.strokeColor = UIColor.white.cgColor
// } self.cropLayer2.strokeColor = UIColor.black.cgColor
// self.cropLayer1.lineDashPhase = 0
// // Model items are immutable; we _replace_ the self.cropLayer2.lineDashPhase = self.cropLayer1.lineDashPhase + kCropDashLength
// // stroke item rather than modify it. }
// let stroke = ImageEditorStrokeItem(itemId: lastStroke.itemId, color: strokeColor, unitSamples: currentStrokeSamples, unitStrokeWidth: unitStrokeWidth) let startCrop = {
// model.replace(item: stroke, suppressUndo: true) for cropLayer in self.cropLayers {
// self.layersView.layer.addSublayer(cropLayer)
// if gestureRecognizer.state == .ended { }
// currentStroke = nil
// currentStrokeSamples.removeAll() updateCrop()
// } else { }
// currentStroke = stroke let endCrop = {
// } updateCrop()
// default:
// removeCurrentStroke() for cropLayer in self.cropLayers {
// } cropLayer.removeFromSuperlayer()
cropLayer.removeAllAnimations()
}
}
switch gestureRecognizer.state {
case .began:
let unitSample = unitSampleForGestureLocation(gestureRecognizer)
cropStartUnit = unitSample
cropEndUnit = unitSample
startCrop()
case .changed:
cropEndUnit = unitSampleForGestureLocation(gestureRecognizer)
updateCrop()
case .ended:
cropEndUnit = unitSampleForGestureLocation(gestureRecognizer)
endCrop()
default:
cancelCrop()
}
} }
// MARK: - ImageEditorModelDelegate // MARK: - ImageEditorModelDelegate
@ -515,6 +543,7 @@ public class ImageEditorView: UIView, ImageEditorModelDelegate {
shapeLayer.path = bezierPath.cgPath shapeLayer.path = bezierPath.cgPath
shapeLayer.fillColor = nil shapeLayer.fillColor = nil
shapeLayer.lineCap = kCALineCapRound shapeLayer.lineCap = kCALineCapRound
shapeLayer.lineJoin = kCALineJoinRound
return shapeLayer return shapeLayer
} }

@ -194,6 +194,16 @@ CG_INLINE CGFloat CGPointDistance(CGPoint left, CGPoint right)
return sqrt(delta.x * delta.x + delta.y * delta.y); return sqrt(delta.x * delta.x + delta.y * delta.y);
} }
CG_INLINE CGPoint CGPointMin(CGPoint left, CGPoint right)
{
return CGPointMake(MIN(left.x, right.x), MIN(left.y, right.y));
}
CG_INLINE CGPoint CGPointMax(CGPoint left, CGPoint right)
{
return CGPointMake(MAX(left.x, right.x), MAX(left.y, right.y));
}
CG_INLINE CGSize CGSizeScale(CGSize size, CGFloat factor) CG_INLINE CGSize CGSizeScale(CGSize size, CGFloat factor)
{ {
return CGSizeMake(size.width * factor, size.height * factor); return CGSizeMake(size.width * factor, size.height * factor);
@ -204,6 +214,14 @@ CG_INLINE CGSize CGSizeAdd(CGSize left, CGSize right)
return CGSizeMake(left.width + right.width, left.height + right.height); return CGSizeMake(left.width + right.width, left.height + right.height);
} }
CG_INLINE CGRect CGRectScale(CGRect rect, CGFloat factor)
{
CGRect result;
result.origin = CGPointScale(rect.origin, factor);
result.size = CGSizeScale(rect.size, factor);
return result;
}
CGFloat CGHairlineWidth(void); CGFloat CGHairlineWidth(void);
NS_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END

Loading…
Cancel
Save