mirror of https://github.com/oxen-io/session-ios
Add color palette to image editor.
parent
530a07f8ca
commit
de27ed8728
@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "Screen Shot 2019-02-26 at 1.57.23 PM.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
@ -0,0 +1,258 @@
|
||||
//
|
||||
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
public protocol ImageEditorPaletteViewDelegate: class {
|
||||
func selectedColorDidChange()
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
public class ImageEditorPaletteView: UIView {
|
||||
|
||||
public weak var delegate: ImageEditorPaletteViewDelegate?
|
||||
|
||||
public required init() {
|
||||
super.init(frame: .zero)
|
||||
|
||||
createContents()
|
||||
}
|
||||
|
||||
@available(*, unavailable, message: "use other init() instead.")
|
||||
required public init?(coder aDecoder: NSCoder) {
|
||||
notImplemented()
|
||||
}
|
||||
|
||||
// MARK: - Views
|
||||
|
||||
// The actual default is selected later.
|
||||
public var selectedColor = UIColor.white
|
||||
|
||||
private let imageView = UIImageView()
|
||||
private let selectionView = UIView()
|
||||
private let selectionWrapper = OWSLayerView()
|
||||
private var selectionConstraint: NSLayoutConstraint?
|
||||
|
||||
private func createContents() {
|
||||
self.backgroundColor = .clear
|
||||
self.isOpaque = false
|
||||
|
||||
if let image = UIImage(named: "image_editor_palette") {
|
||||
imageView.image = image
|
||||
} else {
|
||||
owsFailDebug("Missing image.")
|
||||
}
|
||||
addSubview(imageView)
|
||||
// We use an invisible margin to expand the hot area of
|
||||
// this control.
|
||||
let margin: CGFloat = 8
|
||||
// TODO: Review sizing when there's an asset.
|
||||
imageView.autoSetDimensions(to: CGSize(width: 8, height: 200))
|
||||
imageView.autoPinEdgesToSuperviewEdges(with: UIEdgeInsets(top: margin, left: margin, bottom: margin, right: margin))
|
||||
|
||||
selectionWrapper.layoutCallback = { [weak self] (view) in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.updateState(fireEvent: false)
|
||||
}
|
||||
imageView.addSubview(selectionWrapper)
|
||||
selectionWrapper.autoPinEdgesToSuperviewEdges()
|
||||
|
||||
selectionView.addBorder(with: .white)
|
||||
selectionView.layer.cornerRadius = selectionSize / 2
|
||||
selectionView.autoSetDimensions(to: CGSize(width: selectionSize, height: selectionSize))
|
||||
selectionWrapper.addSubview(selectionView)
|
||||
selectionView.autoHCenterInSuperview()
|
||||
|
||||
isUserInteractionEnabled = true
|
||||
addGestureRecognizer(PaletteGestureRecognizer(target: self, action: #selector(didTouch)))
|
||||
|
||||
updateState(fireEvent: false)
|
||||
}
|
||||
|
||||
// 0 = the color at the top of the image is selected.
|
||||
// 1 = the color at the bottom of the image is selected.
|
||||
private let selectionSize: CGFloat = 20
|
||||
private var selectionAlpha: CGFloat = 0
|
||||
|
||||
private func selectColor(atLocationY y: CGFloat) {
|
||||
selectionAlpha = y.inverseLerp(0, imageView.height(), shouldClamp: true)
|
||||
|
||||
updateState(fireEvent: true)
|
||||
}
|
||||
|
||||
private func updateState(fireEvent: Bool) {
|
||||
var selectedColor = UIColor.white
|
||||
if let image = imageView.image,
|
||||
let cgImage = image.cgImage {
|
||||
if let imageColor = image.color(atLocation: CGPoint(x: CGFloat(cgImage.width) * 0.5, y: CGFloat(cgImage.height) * selectionAlpha)) {
|
||||
selectedColor = imageColor
|
||||
} else {
|
||||
owsFailDebug("Couldn't determine image color.")
|
||||
}
|
||||
} else {
|
||||
owsFailDebug("Missing image.")
|
||||
}
|
||||
self.selectedColor = selectedColor
|
||||
|
||||
selectionView.backgroundColor = selectedColor
|
||||
|
||||
// There must be a better way to pin the selection view's location,
|
||||
// but I can't find it.
|
||||
self.selectionConstraint?.autoRemove()
|
||||
let selectionY = selectionWrapper.height() * selectionAlpha
|
||||
let selectionConstraint = NSLayoutConstraint(item: selectionView,
|
||||
attribute: .centerY, relatedBy: .equal, toItem: selectionWrapper, attribute: .top, multiplier: 1, constant: selectionY)
|
||||
selectionConstraint.autoInstall()
|
||||
self.selectionConstraint = selectionConstraint
|
||||
|
||||
if fireEvent {
|
||||
self.delegate?.selectedColorDidChange()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Events
|
||||
|
||||
@objc
|
||||
func didTouch(gesture: UIGestureRecognizer) {
|
||||
Logger.verbose("gesture: \(NSStringForUIGestureRecognizerState(gesture.state))")
|
||||
switch gesture.state {
|
||||
case .began, .changed, .ended:
|
||||
break
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
||||
let location = gesture.location(in: imageView)
|
||||
selectColor(atLocationY: location.y)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
extension UIImage {
|
||||
func color(atLocation locationPoints: CGPoint) -> UIColor? {
|
||||
guard let cgImage = cgImage else {
|
||||
owsFailDebug("Missing cgImage.")
|
||||
return nil
|
||||
}
|
||||
guard let dataProvider = cgImage.dataProvider else {
|
||||
owsFailDebug("Could not create dataProvider.")
|
||||
return nil
|
||||
}
|
||||
guard let pixelData = dataProvider.data else {
|
||||
owsFailDebug("dataProvider has no data.")
|
||||
return nil
|
||||
}
|
||||
let bytesPerPixel: Int = cgImage.bitsPerPixel / 8
|
||||
guard bytesPerPixel == 4 else {
|
||||
owsFailDebug("Invalid bytesPerPixel: \(bytesPerPixel).")
|
||||
return nil
|
||||
}
|
||||
let imageWidth: Int = cgImage.width
|
||||
let imageHeight: Int = cgImage.height
|
||||
guard imageWidth > 0,
|
||||
imageHeight > 0 else {
|
||||
owsFailDebug("Invalid image size.")
|
||||
return nil
|
||||
}
|
||||
// Convert the location from points to pixels and clamp to the image bounds.
|
||||
let xPixels: Int = Int(round(locationPoints.x * self.scale)).clamp(0, imageWidth - 1)
|
||||
let yPixels: Int = Int(round(locationPoints.y * self.scale)).clamp(0, imageHeight - 1)
|
||||
let dataLength = (pixelData as Data).count
|
||||
let data: UnsafePointer<UInt8> = CFDataGetBytePtr(pixelData)
|
||||
let index: Int = (imageWidth * yPixels + xPixels) * bytesPerPixel
|
||||
guard index >= 0, index < dataLength else {
|
||||
owsFailDebug("Invalid index.")
|
||||
return nil
|
||||
}
|
||||
|
||||
let red = CGFloat(data[index]) / CGFloat(255.0)
|
||||
let green = CGFloat(data[index+1]) / CGFloat(255.0)
|
||||
let blue = CGFloat(data[index+2]) / CGFloat(255.0)
|
||||
let alpha = CGFloat(data[index+3]) / CGFloat(255.0)
|
||||
|
||||
return UIColor(red: red, green: green, blue: blue, alpha: alpha)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
// The most permissive GR possible. Accepts any number of touches in any locations.
|
||||
private class PaletteGestureRecognizer: UIGestureRecognizer {
|
||||
|
||||
@objc
|
||||
public override func canPrevent(_ preventedGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
@objc
|
||||
public override func canBePrevented(by preventingGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
@objc
|
||||
public override func shouldRequireFailure(of otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
@objc
|
||||
public override func shouldBeRequiredToFail(by otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
@objc
|
||||
public override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||
handle(event: event)
|
||||
}
|
||||
|
||||
@objc
|
||||
public override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||
handle(event: event)
|
||||
}
|
||||
|
||||
@objc
|
||||
public override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||
handle(event: event)
|
||||
}
|
||||
|
||||
@objc
|
||||
public override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||
handle(event: event)
|
||||
}
|
||||
|
||||
private func handle(event: UIEvent) {
|
||||
var hasValidTouch = false
|
||||
if let allTouches = event.allTouches {
|
||||
for touch in allTouches {
|
||||
switch touch.phase {
|
||||
case .began, .moved, .stationary:
|
||||
hasValidTouch = true
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if hasValidTouch {
|
||||
switch self.state {
|
||||
case .possible:
|
||||
self.state = .began
|
||||
case .began, .changed:
|
||||
self.state = .changed
|
||||
default:
|
||||
self.state = .failed
|
||||
}
|
||||
} else {
|
||||
switch self.state {
|
||||
case .began, .changed:
|
||||
self.state = .ended
|
||||
default:
|
||||
self.state = .failed
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue