|
|
|
@ -15,7 +15,20 @@ public class ImageEditorView: UIView, ImageEditorModelDelegate {
|
|
|
|
|
case crop
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private var editorMode = EditorMode.brush
|
|
|
|
|
private var editorMode = EditorMode.brush {
|
|
|
|
|
didSet {
|
|
|
|
|
AssertIsOnMainThread()
|
|
|
|
|
|
|
|
|
|
switch editorMode {
|
|
|
|
|
case .brush:
|
|
|
|
|
// Brush strokes can start and end (and return from) outside the view.
|
|
|
|
|
editorGestureRecognizer?.shouldAllowOutsideView = true
|
|
|
|
|
case .crop:
|
|
|
|
|
// Crop gestures can start and end (and return from) outside the view.
|
|
|
|
|
editorGestureRecognizer?.shouldAllowOutsideView = true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@objc
|
|
|
|
|
public required init(model: ImageEditorModel) {
|
|
|
|
@ -36,9 +49,10 @@ public class ImageEditorView: UIView, ImageEditorModelDelegate {
|
|
|
|
|
private let imageView = UIImageView()
|
|
|
|
|
private var imageViewConstraints = [NSLayoutConstraint]()
|
|
|
|
|
private let layersView = OWSLayerView()
|
|
|
|
|
private var editorGestureRecognizer: ImageEditorGestureRecognizer?
|
|
|
|
|
|
|
|
|
|
@objc
|
|
|
|
|
public func createImageView() -> Bool {
|
|
|
|
|
public func configureSubviews() -> Bool {
|
|
|
|
|
self.addSubview(imageView)
|
|
|
|
|
|
|
|
|
|
guard updateImageView() else {
|
|
|
|
@ -54,8 +68,10 @@ public class ImageEditorView: UIView, ImageEditorModelDelegate {
|
|
|
|
|
|
|
|
|
|
self.isUserInteractionEnabled = true
|
|
|
|
|
layersView.isUserInteractionEnabled = true
|
|
|
|
|
let anyTouchGesture = ImageEditorGestureRecognizer(target: self, action: #selector(handleTouchGesture(_:)))
|
|
|
|
|
layersView.addGestureRecognizer(anyTouchGesture)
|
|
|
|
|
let editorGestureRecognizer = ImageEditorGestureRecognizer(target: self, action: #selector(handleTouchGesture(_:)))
|
|
|
|
|
editorGestureRecognizer.referenceView = layersView
|
|
|
|
|
self.addGestureRecognizer(editorGestureRecognizer)
|
|
|
|
|
self.editorGestureRecognizer = editorGestureRecognizer
|
|
|
|
|
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
@ -217,6 +233,15 @@ public class ImageEditorView: UIView, ImageEditorModelDelegate {
|
|
|
|
|
self.currentStroke = nil
|
|
|
|
|
self.currentStrokeSamples.removeAll()
|
|
|
|
|
}
|
|
|
|
|
let tryToAppendStrokeSample = {
|
|
|
|
|
let newSample = self.unitSampleForGestureLocation(gestureRecognizer, shouldClamp: false)
|
|
|
|
|
if let prevSample = self.currentStrokeSamples.last,
|
|
|
|
|
prevSample == newSample {
|
|
|
|
|
// Ignore duplicate samples.
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
self.currentStrokeSamples.append(newSample)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: Color picker.
|
|
|
|
|
let strokeColor = UIColor.blue
|
|
|
|
@ -227,14 +252,14 @@ public class ImageEditorView: UIView, ImageEditorModelDelegate {
|
|
|
|
|
case .began:
|
|
|
|
|
removeCurrentStroke()
|
|
|
|
|
|
|
|
|
|
currentStrokeSamples.append(unitSampleForGestureLocation(gestureRecognizer))
|
|
|
|
|
tryToAppendStrokeSample()
|
|
|
|
|
|
|
|
|
|
let stroke = ImageEditorStrokeItem(color: strokeColor, unitSamples: currentStrokeSamples, unitStrokeWidth: unitStrokeWidth)
|
|
|
|
|
model.append(item: stroke)
|
|
|
|
|
currentStroke = stroke
|
|
|
|
|
|
|
|
|
|
case .changed, .ended:
|
|
|
|
|
currentStrokeSamples.append(unitSampleForGestureLocation(gestureRecognizer))
|
|
|
|
|
tryToAppendStrokeSample()
|
|
|
|
|
|
|
|
|
|
guard let lastStroke = self.currentStroke else {
|
|
|
|
|
owsFailDebug("Missing last stroke.")
|
|
|
|
@ -258,12 +283,17 @@ public class ImageEditorView: UIView, ImageEditorModelDelegate {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private func unitSampleForGestureLocation(_ gestureRecognizer: UIGestureRecognizer) -> CGPoint {
|
|
|
|
|
private func unitSampleForGestureLocation(_ gestureRecognizer: UIGestureRecognizer,
|
|
|
|
|
shouldClamp: Bool) -> 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))
|
|
|
|
|
var x = CGFloatInverseLerp(location.x, 0, referenceView.bounds.width)
|
|
|
|
|
var y = CGFloatInverseLerp(location.y, 0, referenceView.bounds.height)
|
|
|
|
|
if shouldClamp {
|
|
|
|
|
x = CGFloatClamp01(x)
|
|
|
|
|
y = CGFloatClamp01(y)
|
|
|
|
|
}
|
|
|
|
|
return CGPoint(x: x, y: y)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -350,18 +380,22 @@ public class ImageEditorView: UIView, ImageEditorModelDelegate {
|
|
|
|
|
self.model.crop(unitCropRect: unitCropRect)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let currentUnitSample = {
|
|
|
|
|
self.unitSampleForGestureLocation(gestureRecognizer, shouldClamp: true)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch gestureRecognizer.state {
|
|
|
|
|
case .began:
|
|
|
|
|
let unitSample = unitSampleForGestureLocation(gestureRecognizer)
|
|
|
|
|
let unitSample = currentUnitSample()
|
|
|
|
|
cropStartUnit = unitSample
|
|
|
|
|
cropEndUnit = unitSample
|
|
|
|
|
startCrop()
|
|
|
|
|
|
|
|
|
|
case .changed:
|
|
|
|
|
cropEndUnit = unitSampleForGestureLocation(gestureRecognizer)
|
|
|
|
|
cropEndUnit = currentUnitSample()
|
|
|
|
|
updateCrop()
|
|
|
|
|
case .ended:
|
|
|
|
|
cropEndUnit = unitSampleForGestureLocation(gestureRecognizer)
|
|
|
|
|
cropEndUnit = currentUnitSample()
|
|
|
|
|
endCrop()
|
|
|
|
|
default:
|
|
|
|
|
cancelCrop()
|
|
|
|
@ -501,12 +535,10 @@ public class ImageEditorView: UIView, ImageEditorModelDelegate {
|
|
|
|
|
viewSize: CGSize) -> CALayer? {
|
|
|
|
|
AssertIsOnMainThread()
|
|
|
|
|
|
|
|
|
|
Logger.verbose("\(item.itemId), viewSize: \(viewSize)")
|
|
|
|
|
|
|
|
|
|
let strokeWidth = ImageEditorStrokeItem.strokeWidth(forUnitStrokeWidth: item.unitStrokeWidth,
|
|
|
|
|
dstSize: viewSize)
|
|
|
|
|
let unitSamples = item.unitSamples
|
|
|
|
|
guard unitSamples.count > 1 else {
|
|
|
|
|
guard unitSamples.count > 0 else {
|
|
|
|
|
// Not an error; the stroke doesn't have enough samples to render yet.
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
@ -532,7 +564,10 @@ public class ImageEditorView: UIView, ImageEditorModelDelegate {
|
|
|
|
|
let point = points[index]
|
|
|
|
|
|
|
|
|
|
let forwardVector: CGPoint
|
|
|
|
|
if index == 0 {
|
|
|
|
|
if points.count <= 1 {
|
|
|
|
|
// Skip forward vectors.
|
|
|
|
|
forwardVector = .zero
|
|
|
|
|
} else if index == 0 {
|
|
|
|
|
// First sample.
|
|
|
|
|
let nextPoint = points[index + 1]
|
|
|
|
|
forwardVector = CGPointSubtract(nextPoint, point)
|
|
|
|
@ -552,6 +587,10 @@ public class ImageEditorView: UIView, ImageEditorModelDelegate {
|
|
|
|
|
if index == 0 {
|
|
|
|
|
// First sample.
|
|
|
|
|
bezierPath.move(to: point)
|
|
|
|
|
|
|
|
|
|
if points.count == 1 {
|
|
|
|
|
bezierPath.addLine(to: point)
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
let previousPoint = points[index - 1]
|
|
|
|
|
// We apply more than one kind of smoothing.
|
|
|
|
|