From 3d72669c7394a2cac0247f20ad5006bd89ab3ada Mon Sep 17 00:00:00 2001 From: Raymond Yang Date: Thu, 5 May 2022 12:02:50 +0800 Subject: [PATCH] first commit --- .gitignore | 70 +++++++++++++ ColorWheel.xcodeproj/project.pbxproj | 14 ++- ColorWheel/ColorWheelView.swift | 146 +++++++++++++++++++++++++++ ColorWheel/ViewController.swift | 9 +- ColorWheel/ViewController.xib | 18 ++++ 5 files changed, 254 insertions(+), 3 deletions(-) create mode 100644 .gitignore create mode 100644 ColorWheel/ColorWheelView.swift create mode 100644 ColorWheel/ViewController.xib diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a4beec7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,70 @@ +# Created by https://www.gitignore.io/api/swift + +### Swift ### +# Xcode +# +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +## Build generated +build/ +DerivedData/ + +## Various settings +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata/ + +## Other +*.moved-aside +*.xcuserstate + +## Obj-C/Swift specific +*.hmap +*.ipa +*.dSYM.zip +*.dSYM + +## Playgrounds +timeline.xctimeline +playground.xcworkspace + +# Swift Package Manager +# +# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. +# Packages/ +.build/ + +# CocoaPods +# +# We recommend against adding the Pods directory to your .gitignore. However +# you should judge for yourself, the pros and cons are mentioned at: +# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control +# +# Pods/ + +# Carthage +# +# Add this line if you want to avoid checking in source code from Carthage dependencies. +# Carthage/Checkouts + +Carthage/Build + +# fastlane +# +# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the +# screenshots whenever they are needed. +# For more information about the recommended setup visit: +# https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md + +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots +fastlane/test_output + +# End of https://www.gitignore.io/api/swift diff --git a/ColorWheel.xcodeproj/project.pbxproj b/ColorWheel.xcodeproj/project.pbxproj index c4495e5..54e73d1 100644 --- a/ColorWheel.xcodeproj/project.pbxproj +++ b/ColorWheel.xcodeproj/project.pbxproj @@ -16,6 +16,8 @@ 5282F0A3282270E000ADE765 /* ColorWheelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5282F0A2282270E000ADE765 /* ColorWheelTests.swift */; }; 5282F0AD282270E000ADE765 /* ColorWheelUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5282F0AC282270E000ADE765 /* ColorWheelUITests.swift */; }; 5282F0AF282270E000ADE765 /* ColorWheelUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5282F0AE282270E000ADE765 /* ColorWheelUITestsLaunchTests.swift */; }; + 5282F0BC2822717E00ADE765 /* ColorWheelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5282F0BB2822717E00ADE765 /* ColorWheelView.swift */; }; + 5282F0C028227A4A00ADE765 /* ViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5282F0BF28227A4A00ADE765 /* ViewController.xib */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -49,6 +51,8 @@ 5282F0A8282270E000ADE765 /* ColorWheelUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ColorWheelUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 5282F0AC282270E000ADE765 /* ColorWheelUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorWheelUITests.swift; sourceTree = ""; }; 5282F0AE282270E000ADE765 /* ColorWheelUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorWheelUITestsLaunchTests.swift; sourceTree = ""; }; + 5282F0BB2822717E00ADE765 /* ColorWheelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorWheelView.swift; sourceTree = ""; }; + 5282F0BF28227A4A00ADE765 /* ViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ViewController.xib; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -106,6 +110,8 @@ 5282F094282270E000ADE765 /* Assets.xcassets */, 5282F096282270E000ADE765 /* LaunchScreen.storyboard */, 5282F099282270E000ADE765 /* Info.plist */, + 5282F0BB2822717E00ADE765 /* ColorWheelView.swift */, + 5282F0BF28227A4A00ADE765 /* ViewController.xib */, ); path = ColorWheel; sourceTree = ""; @@ -233,6 +239,7 @@ files = ( 5282F098282270E000ADE765 /* LaunchScreen.storyboard in Resources */, 5282F095282270E000ADE765 /* Assets.xcassets in Resources */, + 5282F0C028227A4A00ADE765 /* ViewController.xib in Resources */, 5282F093282270DE00ADE765 /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -260,6 +267,7 @@ files = ( 5282F090282270DE00ADE765 /* ViewController.swift in Sources */, 5282F08C282270DE00ADE765 /* AppDelegate.swift in Sources */, + 5282F0BC2822717E00ADE765 /* ColorWheelView.swift in Sources */, 5282F08E282270DE00ADE765 /* SceneDelegate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -443,6 +451,7 @@ INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; INFOPLIST_KEY_UIMainStoryboardFile = Main; + INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; LD_RUNPATH_SEARCH_PATHS = ( @@ -454,7 +463,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; + TARGETED_DEVICE_FAMILY = 1; }; name = Debug; }; @@ -471,6 +480,7 @@ INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; INFOPLIST_KEY_UIMainStoryboardFile = Main; + INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; LD_RUNPATH_SEARCH_PATHS = ( @@ -482,7 +492,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; + TARGETED_DEVICE_FAMILY = 1; }; name = Release; }; diff --git a/ColorWheel/ColorWheelView.swift b/ColorWheel/ColorWheelView.swift new file mode 100644 index 0000000..1eca480 --- /dev/null +++ b/ColorWheel/ColorWheelView.swift @@ -0,0 +1,146 @@ +// +// ColorWheelView.swift +// ColorWheel +// +// Created by 楊昀羲 on 2019/3/8. +// + +import Foundation +import UIKit + +// 建立 RGB 資料型別 +typealias RGB = (red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) + +// 建立 HSV 資料型別 +typealias HSV = (hue: CGFloat, saturation: CGFloat, brightness: CGFloat, alpha: CGFloat) + +class ColorWheelView: UIView { + + // 邊距 + private let BORDER = 20.0 + + var wheelLayer: CALayer! + + // 取得螢幕解析度縮放 + let scale: CGFloat = UIScreen.main.scale + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder); + } + + override init(frame: CGRect) { + super.init(frame: frame) + + // 色相-飽和度圓盤圖層 + let width = self.frame.width - (BORDER * 2.0) + let height = self.frame.height - (BORDER * 2.0) + + wheelLayer = CALayer() + wheelLayer.frame = CGRect(x: BORDER, y: BORDER, width: width, height: height) + wheelLayer.contents = createColorWheel(wheelLayer.frame.size) + self.layer.addSublayer(wheelLayer) + } + + private func createColorWheel(_ size: CGSize) -> CGImage { + // 建立色相-飽和度圓盤 + let originalWidth = size.width + let originalHeight = size.height + let dimension = min(originalWidth * scale, originalHeight * scale) + let bufferLength = Int(dimension * dimension * 4) // 寬 * 高 * 色彩像素(RGBA) + + // 建立點陣圖資料 + let bitmapData = CFDataCreateMutable(nil, 0) + CFDataSetLength(bitmapData, CFIndex(bufferLength)) + let bitmap = CFDataGetMutableBytePtr(bitmapData) + + // 塞入資料 + for y in stride(from: CGFloat(0), to: dimension, by: CGFloat(1)) { + for x in stride(from: CGFloat(0), to: dimension, by: CGFloat(1)) { + var hsv: HSV = (hue: 0, saturation: 0, brightness: 0, alpha: 0) + var rgb: RGB = (red: 0, green: 0, blue: 0, alpha: 0) + + let color = hueSaturationAtPoint(CGPoint(x: x, y: y)) + let hue = color.hue + let saturation = color.saturation + var a: CGFloat = 0.0 + if (saturation < 1.0) { + // 將邊緣進行反鋸齒處理 + if (saturation > 0.99) { + a = (1.0 - saturation) * 100 + } else { + a = 1.0; + } + + hsv.hue = hue + hsv.saturation = saturation + hsv.brightness = 1.0 + hsv.alpha = a + rgb = hsv2rgb(hsv) + } + let offset = Int(4 * (x + y * dimension)) + bitmap?[offset] = UInt8(rgb.red*255) + bitmap?[offset + 1] = UInt8(rgb.green*255) + bitmap?[offset + 2] = UInt8(rgb.blue*255) + bitmap?[offset + 3] = UInt8(rgb.alpha*255) + } + } + + // 將 bitmap 資料輸出到 CGImage + let colorSpace = CGColorSpaceCreateDeviceRGB() + let dataProvider = CGDataProvider(data: bitmapData!) + let bitmapInfo = CGBitmapInfo(rawValue: CGBitmapInfo().rawValue | CGImageAlphaInfo.last.rawValue) + let imageRef = CGImage(width: Int(dimension), height: Int(dimension), bitsPerComponent: 8, bitsPerPixel: 32, bytesPerRow: Int(dimension) * 4, space: colorSpace, bitmapInfo: bitmapInfo, provider: dataProvider!, decode: nil, shouldInterpolate: false, intent: CGColorRenderingIntent.defaultIntent) + return imageRef! + } + + // 透過座標取得色相和飽和度 + func hueSaturationAtPoint(_ position: CGPoint) -> (hue: CGFloat, saturation: CGFloat) { + let c = wheelLayer.frame.width * scale / 2 + let dx = CGFloat(position.x - c) / c + let dy = CGFloat(position.y - c) / c + let d = sqrt(CGFloat (dx * dx + dy * dy)) + + let saturation: CGFloat = d + + var hue: CGFloat + if (d == 0) { + hue = 0; + } else { + hue = acos(dx/d) / CGFloat(Double.pi) / 2.0 + if (dy < 0) { + hue = 1.0 - hue + } + } + return (hue, saturation) + } + + // 將 HSV 轉換成 RGB + func hsv2rgb(_ hsv: HSV) -> RGB { + var rgb: RGB = (red: 0.0, green: 0.0, blue: 0.0, alpha: 0.0) + var r: CGFloat + var g: CGFloat + var b: CGFloat + + let i = Int(hsv.hue * 6) + let f = hsv.hue * 6 - CGFloat(i) + let p = hsv.brightness * (1 - hsv.saturation) + let q = hsv.brightness * (1 - f * hsv.saturation) + let t = hsv.brightness * (1 - (1 - f) * hsv.saturation) + switch (i % 6) { + case 0: r = hsv.brightness; g = t; b = p; break; + case 1: r = q; g = hsv.brightness; b = p; break; + case 2: r = p; g = hsv.brightness; b = t; break; + case 3: r = p; g = q; b = hsv.brightness; break; + case 4: r = t; g = p; b = hsv.brightness; break; + case 5: r = hsv.brightness; g = p; b = q; break; + default: r = hsv.brightness; g = t; b = p; + } + + rgb.red = r + rgb.green = g + rgb.blue = b + rgb.alpha = hsv.alpha + return rgb + } +} + diff --git a/ColorWheel/ViewController.swift b/ColorWheel/ViewController.swift index 80d31d9..d4ff412 100644 --- a/ColorWheel/ViewController.swift +++ b/ColorWheel/ViewController.swift @@ -2,16 +2,23 @@ // ViewController.swift // ColorWheel // -// Created by 楊昀羲 on 2022/5/4. +// Created by 楊昀羲 on 2019/3/8. // import UIKit class ViewController: UIViewController { + + var colorWheelView: ColorWheelView? override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. + let size = self.view.frame.size.width + let frame = CGRect(x: 0, y: 84, width: size, height: size) + + colorWheelView = ColorWheelView(frame: frame) + self.view.addSubview(colorWheelView!) } diff --git a/ColorWheel/ViewController.xib b/ColorWheel/ViewController.xib new file mode 100644 index 0000000..8b759e7 --- /dev/null +++ b/ColorWheel/ViewController.xib @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + +