mirror of
https://github.com/joncardasis/ChromaColorPicker.git
synced 2024-11-25 11:42:12 +03:00
Pulled out ColorWheelView into its own view class. Added color value to cgpoint calculation relative to color wheel. Removed unused code.
This commit is contained in:
parent
5a8f85fbcd
commit
01f0cc0e57
@ -18,6 +18,7 @@
|
||||
FC1BD8C02207D7B700817AF3 /* ChromaColorPickerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC1BD8BF2207D7B700817AF3 /* ChromaColorPickerTests.swift */; };
|
||||
FC1BD8C22207D7B700817AF3 /* ChromaColorPicker.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3503B82F1F2689BC00750356 /* ChromaColorPicker.framework */; };
|
||||
FCCA42A5226022A800BE2FF9 /* ColorWheelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCCA42A4226022A800BE2FF9 /* ColorWheelView.swift */; };
|
||||
FCCA42A7226023F000BE2FF9 /* ChromaColorHandle.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCCA42A6226023F000BE2FF9 /* ChromaColorHandle.swift */; };
|
||||
FCEA4E272235AAA200C0A1B6 /* ChromaColorPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCEA4E262235AAA200C0A1B6 /* ChromaColorPicker.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
@ -73,6 +74,7 @@
|
||||
FC1BD8CC2207FCE100817AF3 /* ChromaAddButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChromaAddButton.swift; sourceTree = "<group>"; };
|
||||
FC1BD8CD2207FCE100817AF3 /* ColorModeToggleButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ColorModeToggleButton.swift; sourceTree = "<group>"; };
|
||||
FCCA42A4226022A800BE2FF9 /* ColorWheelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorWheelView.swift; sourceTree = "<group>"; };
|
||||
FCCA42A6226023F000BE2FF9 /* ChromaColorHandle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChromaColorHandle.swift; sourceTree = "<group>"; };
|
||||
FCEA4E262235AAA200C0A1B6 /* ChromaColorPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChromaColorPicker.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
@ -110,6 +112,7 @@
|
||||
3503B8311F2689BC00750356 /* ChromaColorPicker.h */,
|
||||
3503B8321F2689BC00750356 /* Info.plist */,
|
||||
FCEA4E262235AAA200C0A1B6 /* ChromaColorPicker.swift */,
|
||||
FCCA42A6226023F000BE2FF9 /* ChromaColorHandle.swift */,
|
||||
FCCA42A4226022A800BE2FF9 /* ColorWheelView.swift */,
|
||||
);
|
||||
path = Source;
|
||||
@ -319,6 +322,7 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
FCEA4E272235AAA200C0A1B6 /* ChromaColorPicker.swift in Sources */,
|
||||
FCCA42A7226023F000BE2FF9 /* ChromaColorHandle.swift in Sources */,
|
||||
FCCA42A5226022A800BE2FF9 /* ColorWheelView.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
@ -12,7 +12,7 @@ import ChromaColorPicker
|
||||
class ViewController: UIViewController {
|
||||
@IBOutlet weak var colorDisplayView: UIView!
|
||||
|
||||
let colorPicker = ChromaColorPicker2()
|
||||
let colorPicker = ChromaColorPicker()
|
||||
|
||||
|
||||
override func viewDidLoad() {
|
||||
|
24
Source/ChromaColorHandle.swift
Normal file
24
Source/ChromaColorHandle.swift
Normal file
@ -0,0 +1,24 @@
|
||||
//
|
||||
// ChromaColorHandle.swift
|
||||
// ChromaColorPicker
|
||||
//
|
||||
// Created by Jon Cardasis on 4/11/19.
|
||||
// Copyright © 2019 Jonathan Cardasis. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
public class ChromaColorHandle {
|
||||
/// Current selected color of the handle.
|
||||
fileprivate(set) var color: UIColor
|
||||
|
||||
/// An image to display above the handle.
|
||||
var popoverImage: UIImage?
|
||||
|
||||
/// A view to display above the handle. Overrides any provided `popoverImage`.
|
||||
var popoverView: UIView?
|
||||
|
||||
init(color: UIColor) {
|
||||
self.color = color
|
||||
}
|
||||
}
|
@ -14,33 +14,11 @@ public protocol ChromaColorPickerDelegate {
|
||||
func colorPickerDidChooseColor(_ colorPicker: ChromaColorPicker, color: UIColor)
|
||||
}
|
||||
|
||||
func timeElapsedInSecondsWhenRunningCode(operation: ()->()) -> Double { //TEMP - DEBUG
|
||||
let startTime = CFAbsoluteTimeGetCurrent()
|
||||
operation()
|
||||
let timeElapsed = CFAbsoluteTimeGetCurrent() - startTime
|
||||
return Double(timeElapsed)
|
||||
}
|
||||
|
||||
|
||||
public class ChromaColorHandle {
|
||||
/// Current selected color of the handle.
|
||||
fileprivate(set) var color: UIColor
|
||||
|
||||
/// An image to display above the handle.
|
||||
var popoverImage: UIImage?
|
||||
|
||||
/// A view to display above the handle. Overrides any provided `popoverImage`.
|
||||
var popoverView: UIView?
|
||||
|
||||
init(color: UIColor) {
|
||||
self.color = color
|
||||
}
|
||||
}
|
||||
|
||||
@IBDesignable
|
||||
public class ChromaColorPicker: UIControl {
|
||||
|
||||
|
||||
//MARK: - Initialization
|
||||
override public init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
@ -60,10 +38,6 @@ public class ChromaColorPicker: UIControl {
|
||||
|
||||
public override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
let minDimensionSize = min(bounds.width, bounds.height)
|
||||
let colorWheelImage = makeColorWheel(radius: minDimensionSize * 3.0) // TEMP?
|
||||
colorWheelImageView.image = colorWheelImage
|
||||
}
|
||||
|
||||
public func addHandle(at color: UIColor? = nil) -> ChromaColorHandle {
|
||||
@ -71,50 +45,24 @@ public class ChromaColorPicker: UIControl {
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
internal var colorWheelImageView: UIImageView!
|
||||
internal let colorWheelView = ColorWheelView()
|
||||
|
||||
internal func commonInit() {
|
||||
self.backgroundColor = UIColor.clear
|
||||
self.layer.masksToBounds = false
|
||||
setupColorWheel()
|
||||
setupColorWheelView()
|
||||
applySmoothingMaskToColorWheel()
|
||||
setupGestures()
|
||||
|
||||
|
||||
|
||||
// DEBUG - BENCHMARK
|
||||
func timeElapsedInSecondsWhenRunningCode(operation: ()->()) -> Double {
|
||||
let startTime = CFAbsoluteTimeGetCurrent()
|
||||
operation()
|
||||
let timeElapsed = CFAbsoluteTimeGetCurrent() - startTime
|
||||
return Double(timeElapsed)
|
||||
}
|
||||
|
||||
var times = [Double]()
|
||||
for _ in 0..<10 {
|
||||
let time = timeElapsedInSecondsWhenRunningCode {
|
||||
_ = makeColorWheel(radius: 1400)
|
||||
}
|
||||
times.append(time)
|
||||
print(time)
|
||||
}
|
||||
|
||||
let avgTime = times.reduce(0, +) / Double(times.count)
|
||||
print("\n\nAvgTime: \(avgTime)")
|
||||
// END DEBUG
|
||||
}
|
||||
|
||||
internal func setupColorWheel() {
|
||||
colorWheelImageView = UIImageView(image: nil)
|
||||
colorWheelImageView.contentMode = .scaleAspectFit
|
||||
|
||||
colorWheelImageView.translatesAutoresizingMaskIntoConstraints = false
|
||||
addSubview(colorWheelImageView)
|
||||
internal func setupColorWheelView() {
|
||||
colorWheelView.translatesAutoresizingMaskIntoConstraints = false
|
||||
addSubview(colorWheelView)
|
||||
NSLayoutConstraint.activate([
|
||||
colorWheelImageView.centerXAnchor.constraint(equalTo: self.centerXAnchor),
|
||||
colorWheelImageView.centerYAnchor.constraint(equalTo: self.centerYAnchor),
|
||||
colorWheelImageView.widthAnchor.constraint(equalTo: self.widthAnchor),
|
||||
colorWheelImageView.heightAnchor.constraint(equalTo: colorWheelImageView.widthAnchor),
|
||||
colorWheelView.centerXAnchor.constraint(equalTo: self.centerXAnchor),
|
||||
colorWheelView.centerYAnchor.constraint(equalTo: self.centerYAnchor),
|
||||
colorWheelView.widthAnchor.constraint(equalTo: self.widthAnchor),
|
||||
colorWheelView.heightAnchor.constraint(equalTo: colorWheelView.widthAnchor),
|
||||
])
|
||||
}
|
||||
|
||||
@ -125,177 +73,16 @@ public class ChromaColorPicker: UIControl {
|
||||
|
||||
internal func setupGestures() {
|
||||
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(colorWheelTapped(_:)))
|
||||
colorWheelImageView.isUserInteractionEnabled = true
|
||||
colorWheelImageView.addGestureRecognizer(tapGesture)
|
||||
colorWheelView.isUserInteractionEnabled = true
|
||||
colorWheelView.addGestureRecognizer(tapGesture)
|
||||
}
|
||||
|
||||
@objc
|
||||
internal func colorWheelTapped(_ gesture: UITapGestureRecognizer) {
|
||||
let location = gesture.location(in: colorWheelImageView)
|
||||
let pixelColor = colorWheelImageView.getPixelColor(at: location)
|
||||
print()
|
||||
}
|
||||
|
||||
/**
|
||||
Generates a color wheel image from a given radius.
|
||||
- Parameters:
|
||||
- radius: The radius of the wheel in points. A radius of 100 would generate an
|
||||
image of 200x200 (400x400 pixels on a device with 2x scaling.)
|
||||
*/
|
||||
internal func makeColorWheel(radius: CGFloat) -> UIImage {
|
||||
let filter = CIFilter(name: "CIHueSaturationValueGradient", parameters: [
|
||||
"inputColorSpace": CGColorSpaceCreateDeviceRGB(),
|
||||
"inputDither": 0,
|
||||
"inputRadius": radius,
|
||||
"inputSoftness": 0,
|
||||
"inputValue": 1
|
||||
])!
|
||||
return UIImage(ciImage: filter.outputImage!)
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
internal lazy var context = makeMetalContext()
|
||||
|
||||
internal func makeMetalContext() -> CIContext {
|
||||
let mtlDevice = MTLCreateSystemDefaultDevice()
|
||||
if let device = mtlDevice, device.supportsFeatureSet(.iOS_GPUFamily1_v1) {
|
||||
return CIContext(mtlDevice: device, options: [CIContextOption.useSoftwareRenderer: false])
|
||||
} else if let eaglContext = EAGLContext(api: .openGLES2) {
|
||||
return CIContext(eaglContext: eaglContext)
|
||||
} else {
|
||||
return CIContext()
|
||||
}
|
||||
}
|
||||
|
||||
internal lazy var lookupImage: CGImage = {
|
||||
let colorWheelCIImage = makeColorWheel(radius: 50).ciImage!
|
||||
return context.createCGImage(colorWheelCIImage, from: colorWheelCIImage.extent)!
|
||||
}()
|
||||
|
||||
internal func location(of color: UIColor) -> CGPoint? {
|
||||
// guard let image = colorWheelImageView.image else { return nil }
|
||||
//
|
||||
// let ci = image.ciImage!
|
||||
// let cgImage = context.createCGImage(ci, from: ci.extent)!
|
||||
let cgImage = self.lookupImage
|
||||
|
||||
var red : CGFloat = 0
|
||||
var green : CGFloat = 0
|
||||
var blue : CGFloat = 0
|
||||
color.getRed(&red, green: &green, blue: &blue, alpha: nil)
|
||||
|
||||
let r = UInt8(red * 255.0)
|
||||
let g = UInt8(green * 255.0)
|
||||
let b = UInt8(blue * 255.0)
|
||||
|
||||
let width = 100 //Int(image.size.width)
|
||||
let height = 100 //Int(image.size.height)
|
||||
if let cfData = cgImage.dataProvider?.data, let pointer = CFDataGetBytePtr(cfData) {
|
||||
for x in 0..<width {
|
||||
for y in 0..<height {
|
||||
// TODO: do a DISTANCE calc and find closest distance
|
||||
let pixelAddress = x * 4 + y * width * 4
|
||||
if pointer.advanced(by: pixelAddress).pointee == r && //Red
|
||||
pointer.advanced(by: pixelAddress + 1).pointee == g && //Green
|
||||
pointer.advanced(by: pixelAddress + 2).pointee == b { //Blue
|
||||
print(CGPoint(x: x, y: y)) //temp
|
||||
return CGPoint(x: x, y: y)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
||||
|
||||
private let defaultHandleColorPosition: UIColor = .black
|
||||
|
||||
|
||||
internal extension UIImageView {
|
||||
internal func getPixelColor(at point: CGPoint) -> UIColor {
|
||||
let pixel = UnsafeMutablePointer<CUnsignedChar>.allocate(capacity: 4)
|
||||
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue)
|
||||
guard let context = CGContext(
|
||||
data: pixel,
|
||||
width: 1,
|
||||
height: 1,
|
||||
bitsPerComponent: 8,
|
||||
bytesPerRow: 4,
|
||||
space: colorSpace,
|
||||
bitmapInfo: bitmapInfo.rawValue
|
||||
) else {
|
||||
return UIColor.white
|
||||
}
|
||||
|
||||
context.translateBy(x: -point.x, y: -point.y)
|
||||
layer.render(in: context)
|
||||
let color = UIColor(
|
||||
red: CGFloat(pixel[0]) / 255.0,
|
||||
green: CGFloat(pixel[1]) / 255.0,
|
||||
blue: CGFloat(pixel[2]) / 255.0,
|
||||
alpha: CGFloat(pixel[3]) / 255.0
|
||||
)
|
||||
|
||||
pixel.deallocate()
|
||||
return color
|
||||
let location = gesture.location(in: colorWheelView)
|
||||
let pixelColor = colorWheelView.pixelColor(at: location)
|
||||
print(pixelColor)
|
||||
}
|
||||
}
|
||||
|
||||
extension UIImage {
|
||||
|
||||
func resized(newWidth: CGFloat, context: CIContext) -> UIImage? {
|
||||
let scale = newWidth / self.size.width
|
||||
let newHeight = self.size.height * scale
|
||||
UIGraphicsBeginImageContext(CGSize(width: newWidth, height: newHeight))
|
||||
self.draw(in: CGRect(x: 0, y: 0, width: newWidth, height: newHeight))
|
||||
let newImage = UIGraphicsGetImageFromCurrentImageContext()
|
||||
UIGraphicsEndImageContext()
|
||||
|
||||
return newImage
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension CGImage {
|
||||
func resizeImageUsingVImage(size:CGSize) -> CGImage? {
|
||||
let cgImage = self
|
||||
var format = vImage_CGImageFormat(bitsPerComponent: 8, bitsPerPixel: 32, colorSpace: nil, bitmapInfo: CGBitmapInfo(rawValue: CGImageAlphaInfo.first.rawValue), version: 0, decode: nil, renderingIntent: CGColorRenderingIntent.defaultIntent)
|
||||
var sourceBuffer = vImage_Buffer()
|
||||
defer {
|
||||
free(sourceBuffer.data)
|
||||
}
|
||||
var error = vImageBuffer_InitWithCGImage(&sourceBuffer, &format, nil, cgImage, numericCast(kvImageNoFlags))
|
||||
guard error == kvImageNoError else { return nil }
|
||||
// create a destination buffer
|
||||
//let scale = self.scale
|
||||
let destWidth = Int(size.width)
|
||||
let destHeight = Int(size.height)
|
||||
let bytesPerPixel = self.bitsPerPixel/8
|
||||
let destBytesPerRow = destWidth * bytesPerPixel
|
||||
let destData = UnsafeMutablePointer<UInt8>.allocate(capacity: destHeight * destBytesPerRow)
|
||||
defer {
|
||||
destData.deallocate(capacity: destHeight * destBytesPerRow)
|
||||
}
|
||||
var destBuffer = vImage_Buffer(data: destData, height: vImagePixelCount(destHeight), width: vImagePixelCount(destWidth), rowBytes: destBytesPerRow)
|
||||
// scale the image
|
||||
error = vImageScale_ARGB8888(&sourceBuffer, &destBuffer, nil, numericCast(kvImageHighQualityResampling))
|
||||
guard error == kvImageNoError else { return nil }
|
||||
// create a CGImage from vImage_Buffer
|
||||
var destCGImage = vImageCreateCGImageFromBuffer(&destBuffer, &format, nil, nil, numericCast(kvImageNoFlags), &error)?.takeRetainedValue()
|
||||
guard error == kvImageNoError else { return nil }
|
||||
// create a UIImage
|
||||
// defer {
|
||||
// destCGImage = nil
|
||||
// }
|
||||
return destCGImage
|
||||
|
||||
// let resizedImage = destCGImage.flatMap { UIImage(cgImage: $0, scale: 0.0, orientation: self.imageOrientation) }
|
||||
// destCGImage = nil
|
||||
// return resizedImage
|
||||
}
|
||||
}
|
||||
internal let defaultHandleColorPosition: UIColor = .black
|
||||
|
@ -10,4 +10,119 @@ import UIKit
|
||||
|
||||
public class ColorWheelView: UIView {
|
||||
|
||||
public override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
commonInit()
|
||||
}
|
||||
|
||||
public required init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
commonInit()
|
||||
}
|
||||
|
||||
public override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
let minDimensionSize = min(bounds.width, bounds.height)
|
||||
if let colorWheelImage = makeColorWheelImage(radius: minDimensionSize) {
|
||||
imageView.image = UIImage(ciImage: colorWheelImage)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the (x,y) location of the color provided within the ColorWheelView.
|
||||
*/
|
||||
public func location(of color: UIColor) -> CGPoint {
|
||||
var hue: CGFloat = 0
|
||||
var saturation: CGFloat = 0
|
||||
color.getHue(&hue, saturation: &saturation, brightness: nil, alpha: nil)
|
||||
|
||||
let radianAngle = hue * (2 * .pi)
|
||||
let distance = saturation * radius
|
||||
let colorTranslation = CGPoint(x: distance * cos(radianAngle), y: -distance * sin(radianAngle))
|
||||
let colorPoint = CGPoint(x: center.x + colorTranslation.x, y: center.y + colorTranslation.y)
|
||||
|
||||
return colorPoint
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the color on the wheel on a given point relative to the view. nil is returned if
|
||||
the point does not exist within the bounds of the color wheel.
|
||||
*/
|
||||
public func pixelColor(at point: CGPoint) -> UIColor? {
|
||||
guard bounds.contains(point) else { return nil }
|
||||
|
||||
let distanceFromCenter: CGFloat = hypot(center.x - point.x, center.y - point.y)
|
||||
let pointExistsInRadius: Bool = distanceFromCenter <= radius
|
||||
guard pointExistsInRadius else { return nil }
|
||||
|
||||
let pixel = UnsafeMutablePointer<CUnsignedChar>.allocate(capacity: 4)
|
||||
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue)
|
||||
guard let context = CGContext(
|
||||
data: pixel,
|
||||
width: 1,
|
||||
height: 1,
|
||||
bitsPerComponent: 8,
|
||||
bytesPerRow: 4,
|
||||
space: colorSpace,
|
||||
bitmapInfo: bitmapInfo.rawValue
|
||||
) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
context.translateBy(x: -point.x, y: -point.y)
|
||||
layer.render(in: context)
|
||||
let color = UIColor(
|
||||
red: CGFloat(pixel[0]) / 255.0,
|
||||
green: CGFloat(pixel[1]) / 255.0,
|
||||
blue: CGFloat(pixel[2]) / 255.0,
|
||||
alpha: CGFloat(pixel[3]) / 255.0
|
||||
)
|
||||
|
||||
pixel.deallocate()
|
||||
return color
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
internal let imageView = UIImageView()
|
||||
|
||||
internal var radius: CGFloat {
|
||||
return bounds.width / 2.0
|
||||
}
|
||||
|
||||
internal func commonInit() {
|
||||
backgroundColor = .clear
|
||||
setupImageView()
|
||||
}
|
||||
|
||||
internal func setupImageView() {
|
||||
imageView.contentMode = .scaleAspectFit
|
||||
|
||||
imageView.translatesAutoresizingMaskIntoConstraints = false
|
||||
addSubview(imageView)
|
||||
NSLayoutConstraint.activate([
|
||||
imageView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||
imageView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
||||
imageView.topAnchor.constraint(equalTo: topAnchor),
|
||||
imageView.bottomAnchor.constraint(equalTo: bottomAnchor),
|
||||
])
|
||||
}
|
||||
|
||||
/**
|
||||
Generates a color wheel image from a given radius.
|
||||
- Parameters:
|
||||
- radius: The radius of the wheel in points. A radius of 100 would generate an
|
||||
image of 200x200 (400x400 pixels on a device with 2x scaling.)
|
||||
*/
|
||||
internal func makeColorWheelImage(radius: CGFloat) -> CIImage? {
|
||||
let filter = CIFilter(name: "CIHueSaturationValueGradient", parameters: [
|
||||
"inputColorSpace": CGColorSpaceCreateDeviceRGB(),
|
||||
"inputDither": 0,
|
||||
"inputRadius": radius,
|
||||
"inputSoftness": 0,
|
||||
"inputValue": 1
|
||||
])
|
||||
return filter?.outputImage
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user