diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..a4c250f --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,9 @@ +FROM mcr.microsoft.com/devcontainers/base:ubuntu24.04 +# These dependencies were recommended by the Swiftly installer (2025-06) +RUN apt-get update && apt-get -y install libpython3-dev libxml2-dev libncurses-dev libz3-dev pkg-config +# Ugly installer: https://www.swift.org/install/linux/ +WORKDIR / +RUN curl -L https://download.swift.org/swiftly/linux/swiftly-$(uname -m).tar.gz | tar zx +USER vscode +# Latest version is fine in dev, ci.yml will test older versions +RUN ./swiftly init --assume-yes diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..f177466 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,14 @@ +{ + "build": { + "dockerfile": "Dockerfile" + }, + "customizations": { + "vscode": { + "extensions": [ + "swiftlang.swift-vscode", + "github.vscode-github-actions", + "mhutchie.git-graph" + ] + } + } +} \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index abbe926..06adff0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,20 +1,25 @@ -name: Test +name: CI on: - workflow_dispatch - - push + - pull_request jobs: + # https://github.com/swift-actions/setup-swift?tab=readme-ov-file#usage test: - runs-on: macos-13 + name: Swift ${{ matrix.swift }} on ${{ matrix.os }} + runs-on: ${{ matrix.os }} strategy: matrix: - params: [ '-scheme HSLuvMac -sdk macosx', '-scheme HSLuviOS -sdk iphonesimulator -destination "platform=iOS Simulator,name=iPhone 14"' ] + os: [ubuntu-latest, macos-latest, windows-latest] + swift: ["5", "6"] steps: + # Perhaps we should switch to swift-actions/setup-swift, but they are undergoing a major refactor + # I tested swift-actions/setup-swift@next but got gpg errors, so we should wait (2025-06-14) + # For now this seems to be the best third party: https://github.com/SwiftyLab/setup-swift + - uses: SwiftyLab/setup-swift@v1 + with: + swift-version: ${{ matrix.swift }} - uses: actions/checkout@v4 - # https://github.com/actions/runner-images/discussions/8367#discussioncomment-12685592 - # https://github.com/actions/runner-images/blob/main/images/macos/macos-13-Readme.md - - name: Select XCode - run: sudo xcode-select -s /Applications/Xcode_14.1.app - - name: Test - run: xcodebuild ${{ matrix.params }} CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO test + - name: Build and Test + run: swift test diff --git a/.gitignore b/.gitignore index 553153a..e485cef 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,6 @@ DerivedData Carthage.checkout Carthage.build Carthage/Build + +.build +.index-build diff --git a/.swiftlint.yml b/.swiftlint.yml deleted file mode 100644 index aa42cc7..0000000 --- a/.swiftlint.yml +++ /dev/null @@ -1,14 +0,0 @@ -disabled_rules: # rule identifiers to exclude from running - - identifier_name - - large_tuple - -opt_in_rules: # some rules are only opt-in - - empty_count - -excluded: # paths to ignore during linting. Takes precedence over `included`. - - Carthage - - Pods - -force_cast: warning # implicitly -force_try: - severity: warning # explicitly diff --git a/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata b/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 919434a..0000000 --- a/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/.swiftpm/xcode/package.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/.swiftpm/xcode/package.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings deleted file mode 100644 index 08de0be..0000000 --- a/.swiftpm/xcode/package.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded - - - diff --git a/.xctool-args b/.xctool-args deleted file mode 100644 index 233de07..0000000 --- a/.xctool-args +++ /dev/null @@ -1,4 +0,0 @@ -[ - "-workspace", "HSLuvSwift.xcworkspace", - "-configuration", "Debug", -] diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..dd12d64 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,63 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [3.0.0] - Unreleased +### Added +- New cross-platform API with no heap allocation +- DevContainer support for development + +### Removed +- **BREAKING**: UIKit/AppKit specific extensions +- CocoaPods support +- Carthage support +- SwiftLint configuration +- Xcode build automation +- Playground examples + +## [2.1.0] - 2020-09-03 +### Added +- Swift Package Manager support +- HPLuv color initializers +- Protocol conformances to UIColor extension + +### Changed +- Updated for Xcode 12 and Swift 5 + +## [2.0.0] - 2017-02-19 +### Changed +- **BREAKING**: Renamed project from HUSL to HSLuv +- Updated for Swift 3 + +## [1.2.0] - 2015-10-20 +### Changed +- Updated for Xcode 7 and Swift 2 +- Uses new Mirror syntax +- Fixed Xcode warnings for var arguments and 'Always Search User Paths' + +## [1.1.0] - 2015-07-03 +### Changed +- Removed CoreGraphics dependency +- Made NS/UIColor initialization non-optional +- Simplified CocoaPods by removing subspecs + +## [1.0.1] - 2015-07-02 +### Changed +- Lowered deployment target in podspec + +## [1.0.0] - 2015-06-23 +### Added +- Initial release of HSLuv for Swift +- NS/UIColor extensions for macOS and iOS +- CocoaPods and Carthage support +- Playground examples + +[3.0.0]: https://github.com/hsluv/hsluv-swift/compare/v2.1.0...HEAD +[2.1.0]: https://github.com/hsluv/hsluv-swift/compare/v2.0.0...v2.1.0 +[2.0.0]: https://github.com/hsluv/hsluv-swift/compare/v1.2.0...v2.0.0 +[1.2.0]: https://github.com/hsluv/hsluv-swift/compare/v1.1.0...v1.2.0 +[1.1.0]: https://github.com/hsluv/hsluv-swift/compare/v1.0.1...v1.1.0 +[1.0.1]: https://github.com/hsluv/hsluv-swift/compare/v1.0.0...v1.0.1 +[1.0.0]: https://github.com/hsluv/hsluv-swift/releases/tag/v1.0.0 \ No newline at end of file diff --git a/HSLuvSwift.podspec b/HSLuvSwift.podspec deleted file mode 100644 index bae13b1..0000000 --- a/HSLuvSwift.podspec +++ /dev/null @@ -1,23 +0,0 @@ -Pod::Spec.new do |s| - s.name = "HSLuvSwift" - - s.version = "2.2.0" - - s.summary = "Swift port of HSLuv, a human-friendly alternative to HSL" - s.homepage = "https://github.com/hsluv/hsluv-swift" - s.license = { :type => 'MIT', :text => '@see LICENSE' } - s.author = { "Clay Smith" => "s.clay.smith@gmail.com", "Alexei Boronine" => "alexei@boronine.com" } - s.source = { :git => "https://github.com/hsluv/hsluv-swift.git", :tag => "v" + s.version.to_s } - s.requires_arc = true - s.xcconfig = { 'SWIFT_INSTALL_OBJC_HEADER' => 'NO' } - - s.source_files = 'Sources/HSLuvSwift/*.{swift}' - s.frameworks = 'Foundation' - s.swift_version = '5.0' - - s.ios.deployment_target = '10.0' - s.ios.frameworks = 'UIKit' - - s.osx.deployment_target = '10.11' - s.osx.frameworks = 'AppKit' -end diff --git a/HSLuvSwift.xcodeproj/project.pbxproj b/HSLuvSwift.xcodeproj/project.pbxproj deleted file mode 100644 index cd5a539..0000000 --- a/HSLuvSwift.xcodeproj/project.pbxproj +++ /dev/null @@ -1,917 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 47; - objects = { - -/* Begin PBXBuildFile section */ - 3A7F6F7725019B0600B57E5B /* Protocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A7F6F7625019B0600B57E5B /* Protocols.swift */; }; - 3A7F6F7825019B0600B57E5B /* Protocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A7F6F7625019B0600B57E5B /* Protocols.swift */; }; - 3ABA53B82402EE2300F37E85 /* Color+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ABA53B22402EE2300F37E85 /* Color+Extensions.swift */; }; - 3ABA53B92402EE2300F37E85 /* Color+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ABA53B22402EE2300F37E85 /* Color+Extensions.swift */; }; - 3ABA53BA2402EE2300F37E85 /* ColorEncodings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ABA53B32402EE2300F37E85 /* ColorEncodings.swift */; }; - 3ABA53BB2402EE2300F37E85 /* ColorEncodings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ABA53B32402EE2300F37E85 /* ColorEncodings.swift */; }; - 3ABA53BC2402EE2300F37E85 /* Math.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ABA53B42402EE2300F37E85 /* Math.swift */; }; - 3ABA53BD2402EE2300F37E85 /* Math.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ABA53B42402EE2300F37E85 /* Math.swift */; }; - 3ABA53BE2402EE2300F37E85 /* Constant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ABA53B52402EE2300F37E85 /* Constant.swift */; }; - 3ABA53BF2402EE2300F37E85 /* Constant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ABA53B52402EE2300F37E85 /* Constant.swift */; }; - CB24CBB51B394AD800091467 /* Snapshot.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB328E391B3205050061A872 /* Snapshot.swift */; }; - CB24CBB61B394ADB00091467 /* Snapshot.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB328E391B3205050061A872 /* Snapshot.swift */; }; - CB328E3B1B32050C0061A872 /* Snapshot.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB328E391B3205050061A872 /* Snapshot.swift */; }; - CB339EEC1B31EE2D0067FDAF /* snapshot-rev4.json in Resources */ = {isa = PBXBuildFile; fileRef = CB339EEB1B31EE210067FDAF /* snapshot-rev4.json */; }; - CB6AB2841B3083890075AF04 /* HSLuvSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CBDA801C1B2D2C740010C653 /* HSLuvSwift.framework */; }; - CB6AB28B1B3084020075AF04 /* AppKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB783AD11B3078D5001F8BCB /* AppKit.swift */; }; - CB6AB2A61B308A6F0075AF04 /* HSLuvSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CB37B9051B2E7994009E63A5 /* HSLuvSwift.framework */; }; - CB6AB2AD1B308B520075AF04 /* UIKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB783AD01B3078CF001F8BCB /* UIKit.swift */; }; - CB98BFB91B307B070027506A /* HSLuvSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CBDA801C1B2D2C740010C653 /* HSLuvSwift.framework */; }; - CB98BFBF1B307B1D0027506A /* HSLuvTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB604FB91B30734C009A99D1 /* HSLuvTests.swift */; }; -/* End PBXBuildFile section */ - -/* Begin PBXContainerItemProxy section */ - CB339EED1B31F9D20067FDAF /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = CBDA80131B2D2C740010C653 /* Project object */; - proxyType = 1; - remoteGlobalIDString = CBDA801B1B2D2C740010C653; - remoteInfo = HSLuvMac; - }; - CB339EEF1B31F9DA0067FDAF /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = CBDA80131B2D2C740010C653 /* Project object */; - proxyType = 1; - remoteGlobalIDString = CBDA801B1B2D2C740010C653; - remoteInfo = HSLuvMac; - }; - CB339EF11B31F9E10067FDAF /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = CBDA80131B2D2C740010C653 /* Project object */; - proxyType = 1; - remoteGlobalIDString = CB37B9041B2E7994009E63A5; - remoteInfo = HSLuviOS; - }; -/* End PBXContainerItemProxy section */ - -/* Begin PBXFileReference section */ - 3A41989C2502248100DD4ED1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 3A7F6F7625019B0600B57E5B /* Protocols.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Protocols.swift; sourceTree = ""; }; - 3ABA53B12402EE2300F37E85 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 3ABA53B22402EE2300F37E85 /* Color+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Color+Extensions.swift"; sourceTree = ""; }; - 3ABA53B32402EE2300F37E85 /* ColorEncodings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ColorEncodings.swift; sourceTree = ""; }; - 3ABA53B42402EE2300F37E85 /* Math.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Math.swift; sourceTree = ""; }; - 3ABA53B52402EE2300F37E85 /* Constant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Constant.swift; sourceTree = ""; }; - CB328E391B3205050061A872 /* Snapshot.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Snapshot.swift; path = Tests/HSLuvSwiftTests/Snapshot.swift; sourceTree = SOURCE_ROOT; }; - CB339EEB1B31EE210067FDAF /* snapshot-rev4.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; name = "snapshot-rev4.json"; path = "Tests/HSLuvSwiftTests/Resources/snapshot-rev4.json"; sourceTree = SOURCE_ROOT; }; - CB37B9051B2E7994009E63A5 /* HSLuvSwift.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = HSLuvSwift.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - CB604FB91B30734C009A99D1 /* HSLuvTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = HSLuvTests.swift; path = Tests/HSLuvSwiftTests/HSLuvTests.swift; sourceTree = SOURCE_ROOT; }; - CB6AB27F1B3083890075AF04 /* HSLuvMacTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = HSLuvMacTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - CB6AB2A11B308A6F0075AF04 /* HSLuviOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = HSLuviOSTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - CB783AD01B3078CF001F8BCB /* UIKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = UIKit.swift; path = Tests/HSLuvSwiftTests/iOSExtensions/UIKit.swift; sourceTree = SOURCE_ROOT; }; - CB783AD11B3078D5001F8BCB /* AppKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppKit.swift; path = Tests/HSLuvSwiftTests/MacExtensions/AppKit.swift; sourceTree = SOURCE_ROOT; }; - CB98BFB41B307B070027506A /* HSLuvTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = HSLuvTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - CBCAD5371B2FBEBD00D0366A /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; - CBCAD53B1B2FBF0600D0366A /* HSLuvSwift.podspec */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = HSLuvSwift.podspec; sourceTree = ""; }; - CBDA801C1B2D2C740010C653 /* HSLuvSwift.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = HSLuvSwift.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - CBDD2B131B461C3000E4548F /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - CB37B9011B2E7994009E63A5 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - CB6AB27C1B3083890075AF04 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - CB6AB2841B3083890075AF04 /* HSLuvSwift.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - CB6AB29E1B308A6F0075AF04 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - CB6AB2A61B308A6F0075AF04 /* HSLuvSwift.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - CB98BFB11B307B070027506A /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - CB98BFB91B307B070027506A /* HSLuvSwift.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - CBDA80181B2D2C740010C653 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 3A46031B2409BF92008B7C02 /* HSLuvSwiftTests */ = { - isa = PBXGroup; - children = ( - CB604FB91B30734C009A99D1 /* HSLuvTests.swift */, - CB328E391B3205050061A872 /* Snapshot.swift */, - CBDA802A1B2D2C740010C653 /* AppKit */, - CB604FAA1B3071DC009A99D1 /* iOS */, - CBD1E3061B30B2C800E49F9B /* Resources */, - 3A46031C2409C03A008B7C02 /* Supporting Files */, - ); - path = HSLuvSwiftTests; - sourceTree = ""; - }; - 3A46031C2409C03A008B7C02 /* Supporting Files */ = { - isa = PBXGroup; - children = ( - 3A41989C2502248100DD4ED1 /* Info.plist */, - ); - name = "Supporting Files"; - sourceTree = ""; - }; - 3ABA53AE2402EC0200F37E85 /* HSLuvSwift */ = { - isa = PBXGroup; - children = ( - 3ABA53B32402EE2300F37E85 /* ColorEncodings.swift */, - 3ABA53B52402EE2300F37E85 /* Constant.swift */, - 3ABA53B42402EE2300F37E85 /* Math.swift */, - 3A7F6F7625019B0600B57E5B /* Protocols.swift */, - 3ABA53AF2402EC1900F37E85 /* Extensions */, - 3ABA53B02402ED9900F37E85 /* Supporting Files */, - ); - path = HSLuvSwift; - sourceTree = ""; - }; - 3ABA53AF2402EC1900F37E85 /* Extensions */ = { - isa = PBXGroup; - children = ( - 3ABA53B22402EE2300F37E85 /* Color+Extensions.swift */, - ); - name = Extensions; - sourceTree = ""; - }; - 3ABA53B02402ED9900F37E85 /* Supporting Files */ = { - isa = PBXGroup; - children = ( - 3ABA53B12402EE2300F37E85 /* Info.plist */, - ); - name = "Supporting Files"; - sourceTree = ""; - }; - CB37B9121B2E7994009E63A5 /* Tests */ = { - isa = PBXGroup; - children = ( - 3A46031B2409BF92008B7C02 /* HSLuvSwiftTests */, - ); - path = Tests; - sourceTree = ""; - }; - CB604FAA1B3071DC009A99D1 /* iOS */ = { - isa = PBXGroup; - children = ( - CB783AD01B3078CF001F8BCB /* UIKit.swift */, - ); - name = iOS; - path = ..; - sourceTree = ""; - }; - CBCAD5361B2FBEAF00D0366A /* Podspec Metadata */ = { - isa = PBXGroup; - children = ( - CBCAD53B1B2FBF0600D0366A /* HSLuvSwift.podspec */, - CBCAD5371B2FBEBD00D0366A /* README.md */, - CBDD2B131B461C3000E4548F /* LICENSE */, - ); - name = "Podspec Metadata"; - sourceTree = ""; - }; - CBD1E3061B30B2C800E49F9B /* Resources */ = { - isa = PBXGroup; - children = ( - CB339EEB1B31EE210067FDAF /* snapshot-rev4.json */, - ); - path = Resources; - sourceTree = ""; - }; - CBDA80121B2D2C740010C653 = { - isa = PBXGroup; - children = ( - CBDA801E1B2D2C740010C653 /* Sources */, - CB37B9121B2E7994009E63A5 /* Tests */, - CBCAD5361B2FBEAF00D0366A /* Podspec Metadata */, - CBDA801D1B2D2C740010C653 /* Products */, - ); - sourceTree = ""; - }; - CBDA801D1B2D2C740010C653 /* Products */ = { - isa = PBXGroup; - children = ( - CBDA801C1B2D2C740010C653 /* HSLuvSwift.framework */, - CB37B9051B2E7994009E63A5 /* HSLuvSwift.framework */, - CB98BFB41B307B070027506A /* HSLuvTests.xctest */, - CB6AB27F1B3083890075AF04 /* HSLuvMacTests.xctest */, - CB6AB2A11B308A6F0075AF04 /* HSLuviOSTests.xctest */, - ); - name = Products; - sourceTree = ""; - }; - CBDA801E1B2D2C740010C653 /* Sources */ = { - isa = PBXGroup; - children = ( - 3ABA53AE2402EC0200F37E85 /* HSLuvSwift */, - ); - path = Sources; - sourceTree = ""; - }; - CBDA802A1B2D2C740010C653 /* AppKit */ = { - isa = PBXGroup; - children = ( - CB783AD11B3078D5001F8BCB /* AppKit.swift */, - ); - name = AppKit; - path = ..; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXHeadersBuildPhase section */ - CB37B9021B2E7994009E63A5 /* Headers */ = { - isa = PBXHeadersBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - CBDA80191B2D2C740010C653 /* Headers */ = { - isa = PBXHeadersBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXHeadersBuildPhase section */ - -/* Begin PBXNativeTarget section */ - CB37B9041B2E7994009E63A5 /* HSLuviOS */ = { - isa = PBXNativeTarget; - buildConfigurationList = CB37B9161B2E7995009E63A5 /* Build configuration list for PBXNativeTarget "HSLuviOS" */; - buildPhases = ( - CB37B9001B2E7994009E63A5 /* Sources */, - CB37B9011B2E7994009E63A5 /* Frameworks */, - CB37B9021B2E7994009E63A5 /* Headers */, - CB37B9031B2E7994009E63A5 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = HSLuviOS; - productName = "HSLuvSwift iOS"; - productReference = CB37B9051B2E7994009E63A5 /* HSLuvSwift.framework */; - productType = "com.apple.product-type.framework"; - }; - CB6AB27E1B3083890075AF04 /* HSLuvMacTests */ = { - isa = PBXNativeTarget; - buildConfigurationList = CB6AB2891B3083890075AF04 /* Build configuration list for PBXNativeTarget "HSLuvMacTests" */; - buildPhases = ( - CB6AB27B1B3083890075AF04 /* Sources */, - CB6AB27C1B3083890075AF04 /* Frameworks */, - CB6AB27D1B3083890075AF04 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - CB339EF01B31F9DA0067FDAF /* PBXTargetDependency */, - ); - name = HSLuvMacTests; - productName = HSLuvMacTests; - productReference = CB6AB27F1B3083890075AF04 /* HSLuvMacTests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; - CB6AB2A01B308A6F0075AF04 /* HSLuviOSTests */ = { - isa = PBXNativeTarget; - buildConfigurationList = CB6AB2A91B308A6F0075AF04 /* Build configuration list for PBXNativeTarget "HSLuviOSTests" */; - buildPhases = ( - CB6AB29D1B308A6F0075AF04 /* Sources */, - CB6AB29E1B308A6F0075AF04 /* Frameworks */, - CB6AB29F1B308A6F0075AF04 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - CB339EF21B31F9E10067FDAF /* PBXTargetDependency */, - ); - name = HSLuviOSTests; - productName = HSLuviOSTests; - productReference = CB6AB2A11B308A6F0075AF04 /* HSLuviOSTests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; - CB98BFB31B307B070027506A /* HSLuvTests */ = { - isa = PBXNativeTarget; - buildConfigurationList = CB98BFBC1B307B070027506A /* Build configuration list for PBXNativeTarget "HSLuvTests" */; - buildPhases = ( - CB98BFB01B307B070027506A /* Sources */, - CB98BFB11B307B070027506A /* Frameworks */, - CB98BFB21B307B070027506A /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - CB339EEE1B31F9D20067FDAF /* PBXTargetDependency */, - ); - name = HSLuvTests; - productName = HSLuvTests; - productReference = CB98BFB41B307B070027506A /* HSLuvTests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; - CBDA801B1B2D2C740010C653 /* HSLuvMac */ = { - isa = PBXNativeTarget; - buildConfigurationList = CBDA80301B2D2C740010C653 /* Build configuration list for PBXNativeTarget "HSLuvMac" */; - buildPhases = ( - CBDA80171B2D2C740010C653 /* Sources */, - CBDA80181B2D2C740010C653 /* Frameworks */, - CBDA80191B2D2C740010C653 /* Headers */, - CBDA801A1B2D2C740010C653 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = HSLuvMac; - productName = HSLuvSwift; - productReference = CBDA801C1B2D2C740010C653 /* HSLuvSwift.framework */; - productType = "com.apple.product-type.framework"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - CBDA80131B2D2C740010C653 /* Project object */ = { - isa = PBXProject; - attributes = { - LastSwiftMigration = 0700; - LastSwiftUpdateCheck = 0700; - LastUpgradeCheck = 1200; - ORGANIZATIONNAME = "Clay Smith"; - TargetAttributes = { - CB37B9041B2E7994009E63A5 = { - CreatedOnToolsVersion = 7.0; - LastSwiftMigration = 1130; - ProvisioningStyle = Automatic; - }; - CB6AB27E1B3083890075AF04 = { - CreatedOnToolsVersion = 7.0; - LastSwiftMigration = 1130; - ProvisioningStyle = Automatic; - }; - CB6AB2A01B308A6F0075AF04 = { - CreatedOnToolsVersion = 7.0; - LastSwiftMigration = 1130; - }; - CB98BFB31B307B070027506A = { - CreatedOnToolsVersion = 7.0; - LastSwiftMigration = 1130; - }; - CBDA801B1B2D2C740010C653 = { - CreatedOnToolsVersion = 7.0; - LastSwiftMigration = 1130; - ProvisioningStyle = Automatic; - }; - }; - }; - buildConfigurationList = CBDA80161B2D2C740010C653 /* Build configuration list for PBXProject "HSLuvSwift" */; - compatibilityVersion = "Xcode 6.3"; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - Base, - en, - ); - mainGroup = CBDA80121B2D2C740010C653; - productRefGroup = CBDA801D1B2D2C740010C653 /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - CBDA801B1B2D2C740010C653 /* HSLuvMac */, - CB37B9041B2E7994009E63A5 /* HSLuviOS */, - CB98BFB31B307B070027506A /* HSLuvTests */, - CB6AB27E1B3083890075AF04 /* HSLuvMacTests */, - CB6AB2A01B308A6F0075AF04 /* HSLuviOSTests */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - CB37B9031B2E7994009E63A5 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - CB6AB27D1B3083890075AF04 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - CB6AB29F1B308A6F0075AF04 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - CB98BFB21B307B070027506A /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - CB339EEC1B31EE2D0067FDAF /* snapshot-rev4.json in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - CBDA801A1B2D2C740010C653 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - CB37B9001B2E7994009E63A5 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 3A7F6F7825019B0600B57E5B /* Protocols.swift in Sources */, - 3ABA53BB2402EE2300F37E85 /* ColorEncodings.swift in Sources */, - 3ABA53B92402EE2300F37E85 /* Color+Extensions.swift in Sources */, - 3ABA53BD2402EE2300F37E85 /* Math.swift in Sources */, - 3ABA53BF2402EE2300F37E85 /* Constant.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - CB6AB27B1B3083890075AF04 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - CB6AB28B1B3084020075AF04 /* AppKit.swift in Sources */, - CB24CBB51B394AD800091467 /* Snapshot.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - CB6AB29D1B308A6F0075AF04 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - CB6AB2AD1B308B520075AF04 /* UIKit.swift in Sources */, - CB24CBB61B394ADB00091467 /* Snapshot.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - CB98BFB01B307B070027506A /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - CB98BFBF1B307B1D0027506A /* HSLuvTests.swift in Sources */, - CB328E3B1B32050C0061A872 /* Snapshot.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - CBDA80171B2D2C740010C653 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 3A7F6F7725019B0600B57E5B /* Protocols.swift in Sources */, - 3ABA53BA2402EE2300F37E85 /* ColorEncodings.swift in Sources */, - 3ABA53B82402EE2300F37E85 /* Color+Extensions.swift in Sources */, - 3ABA53BC2402EE2300F37E85 /* Math.swift in Sources */, - 3ABA53BE2402EE2300F37E85 /* Constant.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXTargetDependency section */ - CB339EEE1B31F9D20067FDAF /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = CBDA801B1B2D2C740010C653 /* HSLuvMac */; - targetProxy = CB339EED1B31F9D20067FDAF /* PBXContainerItemProxy */; - }; - CB339EF01B31F9DA0067FDAF /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = CBDA801B1B2D2C740010C653 /* HSLuvMac */; - targetProxy = CB339EEF1B31F9DA0067FDAF /* PBXContainerItemProxy */; - }; - CB339EF21B31F9E10067FDAF /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = CB37B9041B2E7994009E63A5 /* HSLuviOS */; - targetProxy = CB339EF11B31F9E10067FDAF /* PBXContainerItemProxy */; - }; -/* End PBXTargetDependency section */ - -/* Begin XCBuildConfiguration section */ - CB37B9171B2E7995009E63A5 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_IDENTITY = ""; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; - CODE_SIGN_STYLE = Automatic; - DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = ""; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = "$(SRCROOT)/Sources/$(PROJECT_NAME)/Info.plist"; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; - MARKETING_VERSION = 2.2; - PRODUCT_BUNDLE_IDENTIFIER = com.claysmith.HSLuvSwiftiOS; - PRODUCT_NAME = HSLuvSwift; - PROVISIONING_PROFILE_SPECIFIER = ""; - SDKROOT = iphoneos; - SKIP_INSTALL = YES; - SUPPORTED_PLATFORMS = "iphonesimulator iphoneos"; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - CB37B9181B2E7995009E63A5 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_IDENTITY = ""; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; - CODE_SIGN_STYLE = Automatic; - DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = ""; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = "$(SRCROOT)/Sources/$(PROJECT_NAME)/Info.plist"; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; - MARKETING_VERSION = 2.2; - PRODUCT_BUNDLE_IDENTIFIER = com.claysmith.HSLuvSwiftiOS; - PRODUCT_NAME = HSLuvSwift; - PROVISIONING_PROFILE_SPECIFIER = ""; - SDKROOT = iphoneos; - SKIP_INSTALL = YES; - SUPPORTED_PLATFORMS = "iphonesimulator iphoneos"; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - CB6AB2871B3083890075AF04 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - ALWAYS_SEARCH_USER_PATHS = NO; - CODE_SIGN_IDENTITY = "-"; - CODE_SIGN_STYLE = Automatic; - COMBINE_HIDPI_IMAGES = YES; - DEVELOPMENT_TEAM = ""; - INFOPLIST_FILE = Tests/HSLuvSwiftTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = com.claysmith.HSLuvMacTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 5.0; - }; - name = Debug; - }; - CB6AB2881B3083890075AF04 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - ALWAYS_SEARCH_USER_PATHS = NO; - CODE_SIGN_IDENTITY = "-"; - CODE_SIGN_STYLE = Automatic; - COMBINE_HIDPI_IMAGES = YES; - DEVELOPMENT_TEAM = ""; - INFOPLIST_FILE = Tests/HSLuvSwiftTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = com.claysmith.HSLuvMacTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 5.0; - }; - name = Release; - }; - CB6AB2AA1B308A6F0075AF04 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - ALWAYS_SEARCH_USER_PATHS = NO; - CODE_SIGN_IDENTITY = "iPhone Developer"; - "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; - DEVELOPMENT_TEAM = ""; - INFOPLIST_FILE = Tests/HSLuvSwiftTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = com.claysmith.HSLuviOSTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = iphoneos; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 5.0; - }; - name = Debug; - }; - CB6AB2AB1B308A6F0075AF04 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - ALWAYS_SEARCH_USER_PATHS = NO; - CODE_SIGN_IDENTITY = "iPhone Developer"; - "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; - DEVELOPMENT_TEAM = ""; - INFOPLIST_FILE = Tests/HSLuvSwiftTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = com.claysmith.HSLuviOSTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = iphoneos; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 5.0; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - CB98BFBD1B307B070027506A /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - ALWAYS_SEARCH_USER_PATHS = NO; - CODE_SIGN_IDENTITY = "-"; - COMBINE_HIDPI_IMAGES = YES; - DEVELOPMENT_TEAM = ""; - INFOPLIST_FILE = Tests/HSLuvSwiftTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = com.claysmith.HSLuvTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 5.0; - }; - name = Debug; - }; - CB98BFBE1B307B070027506A /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - ALWAYS_SEARCH_USER_PATHS = NO; - CODE_SIGN_IDENTITY = "-"; - COMBINE_HIDPI_IMAGES = YES; - DEVELOPMENT_TEAM = ""; - INFOPLIST_FILE = Tests/HSLuvSwiftTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = com.claysmith.HSLuvTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 5.0; - }; - name = Release; - }; - CBDA802E1B2D2C740010C653 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; - MACOSX_DEPLOYMENT_TARGET = 10.10; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = macosx; - SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator"; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = Debug; - }; - CBDA802F1B2D2C740010C653 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; - MACOSX_DEPLOYMENT_TARGET = 10.10; - MTL_ENABLE_DEBUG_INFO = NO; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = macosx; - SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator"; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = Release; - }; - CBDA80311B2D2C740010C653 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_IDENTITY = ""; - COMBINE_HIDPI_IMAGES = YES; - DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = ""; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - GCC_GENERATE_TEST_COVERAGE_FILES = YES; - INFOPLIST_FILE = "$(SRCROOT)/Sources/$(PROJECT_NAME)/Info.plist"; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; - MARKETING_VERSION = 2.2; - PRODUCT_BUNDLE_IDENTIFIER = com.claysmith.HSLuvSwiftMac; - PRODUCT_NAME = HSLuvSwift; - SKIP_INSTALL = YES; - SUPPORTED_PLATFORMS = macosx; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 5.0; - }; - name = Debug; - }; - CBDA80321B2D2C740010C653 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_IDENTITY = ""; - COMBINE_HIDPI_IMAGES = YES; - DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = ""; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - GCC_GENERATE_TEST_COVERAGE_FILES = YES; - INFOPLIST_FILE = "$(SRCROOT)/Sources/$(PROJECT_NAME)/Info.plist"; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; - MARKETING_VERSION = 2.2; - PRODUCT_BUNDLE_IDENTIFIER = com.claysmith.HSLuvSwiftMac; - PRODUCT_NAME = HSLuvSwift; - SKIP_INSTALL = YES; - SUPPORTED_PLATFORMS = macosx; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 5.0; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - CB37B9161B2E7995009E63A5 /* Build configuration list for PBXNativeTarget "HSLuviOS" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - CB37B9171B2E7995009E63A5 /* Debug */, - CB37B9181B2E7995009E63A5 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - CB6AB2891B3083890075AF04 /* Build configuration list for PBXNativeTarget "HSLuvMacTests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - CB6AB2871B3083890075AF04 /* Debug */, - CB6AB2881B3083890075AF04 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - CB6AB2A91B308A6F0075AF04 /* Build configuration list for PBXNativeTarget "HSLuviOSTests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - CB6AB2AA1B308A6F0075AF04 /* Debug */, - CB6AB2AB1B308A6F0075AF04 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - CB98BFBC1B307B070027506A /* Build configuration list for PBXNativeTarget "HSLuvTests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - CB98BFBD1B307B070027506A /* Debug */, - CB98BFBE1B307B070027506A /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - CBDA80161B2D2C740010C653 /* Build configuration list for PBXProject "HSLuvSwift" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - CBDA802E1B2D2C740010C653 /* Debug */, - CBDA802F1B2D2C740010C653 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - CBDA80301B2D2C740010C653 /* Build configuration list for PBXNativeTarget "HSLuvMac" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - CBDA80311B2D2C740010C653 /* Debug */, - CBDA80321B2D2C740010C653 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = CBDA80131B2D2C740010C653 /* Project object */; -} diff --git a/HSLuvSwift.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/HSLuvSwift.xcodeproj/project.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 88159d8..0000000 --- a/HSLuvSwift.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/HSLuvSwift.xcodeproj/xcshareddata/xcschemes/HSLuvMac.xcscheme b/HSLuvSwift.xcodeproj/xcshareddata/xcschemes/HSLuvMac.xcscheme deleted file mode 100644 index 3917b9b..0000000 --- a/HSLuvSwift.xcodeproj/xcshareddata/xcschemes/HSLuvMac.xcscheme +++ /dev/null @@ -1,96 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/HSLuvSwift.xcodeproj/xcshareddata/xcschemes/HSLuviOS.xcscheme b/HSLuvSwift.xcodeproj/xcshareddata/xcschemes/HSLuviOS.xcscheme deleted file mode 100644 index 709a732..0000000 --- a/HSLuvSwift.xcodeproj/xcshareddata/xcschemes/HSLuviOS.xcscheme +++ /dev/null @@ -1,86 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/HSLuvSwift.xcworkspace/contents.xcworkspacedata b/HSLuvSwift.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 6734618..0000000 --- a/HSLuvSwift.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - diff --git a/HSLuvSwift.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/HSLuvSwift.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d9810..0000000 --- a/HSLuvSwift.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/HSLuviOS.playground/Contents.swift b/HSLuviOS.playground/Contents.swift deleted file mode 100644 index f2b5d1d..0000000 --- a/HSLuviOS.playground/Contents.swift +++ /dev/null @@ -1,30 +0,0 @@ -//: # HSLuvSwift -//: -//: Swift port of HSLuv, a human-friendly alternative to HSL - -import UIKit -import HSLuvSwift - -//: ## Examples -//: -//: The new UIColor initializer works exactly as you would expect. -let color = UIColor(hue: 360, saturation: 100.0, lightness: 50.0, alpha: 1.0) -let colorView = UIView(frame: squareRect) -colorView.backgroundColor = color - -//: Alpha transparency is supported -let fiftyPc = UIColor(hue: 200, saturation: 100, lightness: 50, alpha: 0.25) -let fiftyPcView = UIView(frame: squareRect) -fiftyPcView.backgroundColor = fiftyPc - -//: ## Let's make a rainbow! -var colors = [UIColor?]() -for H in stride(from: 0, through: 360, by: 5.0) { - colors.append(UIColor(hue: H, saturation: 100.0, lightness: 50.0, alpha: 1.0)) -} - -if #available(iOS 9.0, *) { - var palette = PaletteView(frame: CGRect(x: 0, y: 0, width: 500, height: 100), numberOfColors: 37) - - palette.addColors(colors) -} diff --git a/HSLuviOS.playground/Sources/Palette.swift b/HSLuviOS.playground/Sources/Palette.swift deleted file mode 100644 index 1ef5e56..0000000 --- a/HSLuviOS.playground/Sources/Palette.swift +++ /dev/null @@ -1,44 +0,0 @@ -import UIKit - -public let squareRect = CGRect(x: 0, y: 0, width: 75, height: 75) - -@available(iOS 9.0, *) -public class PaletteView: UIStackView { - var subviewRect: CGRect! - - required public init(coder aDecoder: NSCoder) { - fatalError() - } - - public init(frame: CGRect, numberOfColors colors: Int) { - super.init(frame: frame) - - if frame.width > frame.height { - axis = .horizontal - subviewRect = CGRect(x: 0, y: 0, width: frame.width / CGFloat(colors), height: frame.height) - } else { - axis = .vertical - subviewRect = CGRect(x: 0, y: 0, width: frame.width, height: frame.height / CGFloat(colors)) - } - - distribution = .fillEqually - } - - public func addColor(_ color: UIColor?) { - guard let color = color else { - print("Color is missing for view") - return - } - - let view = UIView(frame: subviewRect) - view.backgroundColor = color - - self.addArrangedSubview(view) - } - - public func addColors(_ colors: [UIColor?]) { - for color in colors { - addColor(color) - } - } -} diff --git a/HSLuviOS.playground/contents.xcplayground b/HSLuviOS.playground/contents.xcplayground deleted file mode 100644 index a39f3b2..0000000 --- a/HSLuviOS.playground/contents.xcplayground +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/HSLuviOS.playground/timeline.xctimeline b/HSLuviOS.playground/timeline.xctimeline deleted file mode 100644 index 94368c7..0000000 --- a/HSLuviOS.playground/timeline.xctimeline +++ /dev/null @@ -1,73 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/LICENSE b/LICENSE index c6ef260..10a7ae7 100644 --- a/LICENSE +++ b/LICENSE @@ -1,34 +1,7 @@ -License -======= The MIT License (MIT) -Original HSLuv implementation -Copyright (c) 2015 Alexei Boronine - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - --- - -The MIT License (MIT) - -Swift implementation and supporting code Copyright (c) 2015 Clay Smith +Copyright (c) 2015, 2025 Alexei Boronine Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Package.swift b/Package.swift index 8b5b9ec..37dd0af 100644 --- a/Package.swift +++ b/Package.swift @@ -1,37 +1,17 @@ // swift-tools-version:5.3 -// The swift-tools-version declares the minimum version of Swift required to build this package. - import PackageDescription let package = Package( name: "HSLuvSwift", - platforms: [ - .iOS(.v10), - .macOS(.v10_11), - ], products: [ - // Products define the executables and libraries produced by a package, and make them visible to other packages. - .library( - name: "HSLuvSwift", - targets: ["HSLuvSwift"] - ), - ], - dependencies: [ - // Dependencies declare other packages that this package depends on. - // .package(url: /* package url */, from: "1.0.0"), + .library(name: "HSLuv", targets: ["HSLuv"]) ], targets: [ - // Targets are the basic building blocks of a package. A target can define a module or a test suite. - // Targets can depend on other targets in this package, and on products in packages which this package depends on. - .target( - name: "HSLuvSwift", - dependencies: [], - exclude: ["Info.plist"] - ), + .target(name: "HSLuv", path: "Sources"), .testTarget( - name: "HSLuvSwiftTests", - dependencies: ["HSLuvSwift"], - exclude: ["Info.plist"], + name: "HSLuvTests", + dependencies: ["HSLuv"], + path: "Tests", resources: [.copy("Resources/snapshot-rev4.json")], swiftSettings: [.define("SPM")] ) diff --git a/README.md b/README.md index b4d40e9..19b4827 100644 --- a/README.md +++ b/README.md @@ -1,73 +1,70 @@ -# HSLuvSwift +# HSLuv.swift -[![SwiftPM compatible](https://img.shields.io/badge/SwiftPM-compatible-brightgreen.svg)](https://swift.org/package-manager/) -[![Cocoapod compatible](https://img.shields.io/cocoapods/v/HSLuvSwift.svg)](https://cocoapods.org/pods/HSLuvSwift) -[![Carthage compatible](https://img.shields.io/badge/carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) [![Build Status](https://github.com/hsluv/hsluv-swift/actions/workflows/ci.yml/badge.svg)](https://github.com/hsluv/hsluv-swift/actions/workflows/ci.yml) -[![MIT License](https://img.shields.io/badge/license-MIT%20License-blue.svg)](LICENSE) -Swift port of [HSLuv](http://www.hsluv.org) (revision 4), courtesy -of [Clay Smith](https://github.com/stphnclysmth) +Swift port of [HSLuv](http://www.hsluv.org) (revision 4). Supports Swift 5+ on Apple platforms, Linux and Windows. -[Explanation, demo, ports etc.](http://www.hsluv.org) +[Explanation, demo, etc.](http://www.hsluv.org) +## INSTALL -## USAGE - -This framework adds a single initializer on the OS-specific color class to create a color from HSLuv parameters. The initializer takes the same parameters on both macOS and iOS. +Install HSLuv using [Swift Package Manager](https://www.swift.org/documentation/package-manager/) by adding the following to your [`Package.swift`](https://developer.apple.com/documentation/packagedescription): ```swift -// macOS -let color = NSColor(hue: 360.0, saturation: 100.0, lightness: 100.0, alpha: 1.0) - -// iOS -let color = UIColor(hue: 360.0, saturation: 100.0, lightness: 100.0, alpha: 1.0) +.package(url: "https://github.com/hsluv/hsluv-swift.git", from: "3.0.0"), ``` +CocoaPods and Carthage are no longer supported, but we recommend you simply vendor the [source file](./Sources/HSLuv.swift) if SPM is not available. -## INSTALL +## USAGE -This project is compatible with Swift Package Manager, CocoaPods and Carthage. (These instructions assume that your chosen method is already installed.) +The API is designed to avoid heap allocation. The `HSLuv` class defines the following public fields: -#### Swift Package Manager +- RGB: `hex:String`, `rgb_r:Float` [0;1], `rgb_g:Float` [0;1], `rgb_r:Float` [0;1] +- CIE XYZ: `xyz_x:Float`, `xyz_y:Float`, `xyz_z:Float` +- CIE LUV: `luv_l:Float`, `luv_u:Float`, `luv_v:Float` +- CIE LUV LCh: `lch_l:Float`, `lch_c:Float`, `lch_h:Float` +- HSLuv: `hsluv_h:Float` [0;360], `hsluv_s:Float` [0;100], `hsluv_l:Float` [0;100] +- HPLuv: `hpluv_h:Float` [0;360], `hpluv_p:Float` [0;100], `hpluv_l:Float` [0;100] -As of version 2.1.0, you can use the Swift Package Manager as integration method. -If you want to use the Swift Package Manager as integration method, either use Xcode to add the package dependency or add the following dependency to your Package.swift: +To convert between color spaces, simply set the properties of the source color space, run the +conversion methods, then read the properties of the target color space. -```swift -.package(url: "https://github.com/hsluv/hsluv-swift.git", from: "2.1.0"), -``` +Use the following methods to convert to and from RGB: -### CocoaPods +- HSLuv: `hsluvToRgb()`, `hsluvToHex()`, `rgbToHsluv()`, `hexToHsluv()` +- HPLuv: `hpluvToRgb()`, `hpluvToHex()`, `rgbToHpluv()`, `hexToHpluv()` -Add `pod 'HSLuvSwift'` to your target. Since this is a Swift dynamic framework, you must also tell CocoaPods to `use_frameworks!` instead of static libraries. +Use the following methods to do step-by-step conversion: -```ruby -platform :ios, '10.0' # or, :osx, '10.10' -use_frameworks! +- Forward: `hsluvToLch()` (or `hpluvToLch()`), `lchToLuv()`, `luvToXyz()`, `xyzToRgb()`, `rgbToHex()` +- Backward: `hexToRgb()`, `rgbToXyz()`, `xyzToLuv()`, `luvToLch()`, `lchToHsluv()` (or `lchToHpluv()`) -target 'YourProject' do -pod 'HSLuvSwift', '~> 2.0.0' -end -``` +For advanced usage, we also export the [bounding lines](https://www.hsluv.org/math/) in slope-intercept +format, two for each RGB channel representing the limit of the gamut. -### Carthage +- R < 0: `r0s`, `r0i` +- R > 1: `r1s`, `r1i` +- G < 0: `g0s`, `g0i` +- G > 1: `g1s`, `g1i` +- B < 0: `b0s`, `b0i` +- B > 1: `b1s`, `b1i` -Add `github "hsluv/hsluv-swift" ~> 2.0.0` to your Cartfile and run `carthage bootstrap`. This builds frameworks for Mac and iOS targets. +Example: -```sh -> echo 'github "hsluv/hsluv-swift" ~> 2.0.0' >> Cartfile -> carthage bootstrap +```swift +let conv = Hsluv() +conv.hsluv_h = 10 +conv.hsluv_s = 75 +conv.hsluv_l = 65 +conv.hsluvToHex() +print(conv.hex) // Will print "#ec7d82" ``` +## CHANGELOG -## TODO - -* Finish HPLuv implementation -* Improve tests and add continuous integration testing -* Add usage documentation - +See [CHANGELOG](CHANGELOG.md) for version history and release notes. -## License +## LICENSE -See [License](LICENSE) +See [LICENSE](LICENSE) diff --git a/Sources/HSLuv.swift b/Sources/HSLuv.swift new file mode 100644 index 0000000..cbd6454 --- /dev/null +++ b/Sources/HSLuv.swift @@ -0,0 +1,450 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2015 Clay Smith + * Copyright (c) 2015, 2025 Alexei Boronine + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import Foundation + +public class Hsluv { + private static let hexChars: String = "0123456789abcdef" + private static let refY: Double = 1.0 + private static let refU: Double = 0.19783000664283 + private static let refV: Double = 0.46831999493879 + private static let kappa: Double = 903.2962962 + private static let epsilon: Double = 0.0088564516 + private static let m_r0: Double = 3.240969941904521 + private static let m_r1: Double = -1.537383177570093 + private static let m_r2: Double = -0.498610760293 + private static let m_g0: Double = -0.96924363628087 + private static let m_g1: Double = 1.87596750150772 + private static let m_g2: Double = 0.041555057407175 + private static let m_b0: Double = 0.055630079696993 + private static let m_b1: Double = -0.20397695888897 + private static let m_b2: Double = 1.056971514242878 + + // RGB + public var hex: String = "#000000" + public var rgb_r: Double = 0 + public var rgb_g: Double = 0 + public var rgb_b: Double = 0 + + // CIE XYZ + public var xyz_x: Double = 0 + public var xyz_y: Double = 0 + public var xyz_z: Double = 0 + + // CIE LUV + public var luv_l: Double = 0 + public var luv_u: Double = 0 + public var luv_v: Double = 0 + + // CIE LUV LCh + public var lch_l: Double = 0 + public var lch_c: Double = 0 + public var lch_h: Double = 0 + + // HSLuv + public var hsluv_h: Double = 0 + public var hsluv_s: Double = 0 + public var hsluv_l: Double = 0 + + // HPLuv + public var hpluv_h: Double = 0 + public var hpluv_p: Double = 0 + public var hpluv_l: Double = 0 + + // 6 lines in slope-intercept format: R < 0, R > 1, G < 0, G > 1, B < 0, B > 1 + public var r0s: Double = 0 + public var r0i: Double = 0 + public var r1s: Double = 0 + public var r1i: Double = 0 + + public var g0s: Double = 0 + public var g0i: Double = 0 + public var g1s: Double = 0 + public var g1i: Double = 0 + + public var b0s: Double = 0 + public var b0i: Double = 0 + public var b1s: Double = 0 + public var b1i: Double = 0 + + public init() {} + + private static func fromLinear(_ c: Double) -> Double { + if c <= 0.0031308 { + return 12.92 * c + } else { + return 1.055 * pow(c, 1 / 2.4) - 0.055 + } + } + + private static func toLinear(_ c: Double) -> Double { + if c > 0.04045 { + return pow((c + 0.055) / 1.055, 2.4) + } else { + return c / 12.92 + } + } + + private static func yToL(_ Y: Double) -> Double { + if Y <= epsilon { + return Y / refY * kappa + } else { + return 116 * pow(Y / refY, 1 / 3) - 16 + } + } + + private static func lToY(_ L: Double) -> Double { + if L <= 8 { + return refY * L / kappa + } else { + return refY * pow((L + 16) / 116, 3) + } + } + + private static func rgbChannelToHex(_ chan: Double) -> String { + let c = Int(round(chan * 255)) + let digit2 = c % 16 + let digit1 = (c - digit2) / 16 + let char1 = hexChars[hexChars.index(hexChars.startIndex, offsetBy: digit1)] + let char2 = hexChars[hexChars.index(hexChars.startIndex, offsetBy: digit2)] + return String(char1) + String(char2) + } + + private static func hexToRgbChannel(_ hex: String, _ offset: Int) -> Double { + let char1 = hex[hex.index(hex.startIndex, offsetBy: offset)] + let char2 = hex[hex.index(hex.startIndex, offsetBy: offset + 1)] + let digit1 = hexChars.firstIndex(of: char1)!.utf16Offset(in: hexChars) + let digit2 = hexChars.firstIndex(of: char2)!.utf16Offset(in: hexChars) + let n = digit1 * 16 + digit2 + return Double(n) / 255.0 + } + + private static func distanceFromOriginAngle(_ slope: Double, _ intercept: Double, _ angle: Double) -> Double { + let d = intercept / (sin(angle) - slope * cos(angle)) + if d < 0 { + return Double.infinity + } else { + return d + } + } + + private static func distanceFromOrigin(_ slope: Double, _ intercept: Double) -> Double { + return abs(intercept) / sqrt(pow(slope, 2) + 1) + } + + private static func min6(_ f1: Double, _ f2: Double, _ f3: Double, _ f4: Double, _ f5: Double, _ f6: Double) -> Double { + return min(f1, min(f2, min(f3, min(f4, min(f5, f6))))) + } + + public func rgbToHex() { + hex = "#" + hex += Hsluv.rgbChannelToHex(rgb_r) + hex += Hsluv.rgbChannelToHex(rgb_g) + hex += Hsluv.rgbChannelToHex(rgb_b) + } + + public func hexToRgb() { + let lowercaseHex = hex.lowercased() + rgb_r = Hsluv.hexToRgbChannel(lowercaseHex, 1) + rgb_g = Hsluv.hexToRgbChannel(lowercaseHex, 3) + rgb_b = Hsluv.hexToRgbChannel(lowercaseHex, 5) + } + + public func xyzToRgb() { + rgb_r = Hsluv.fromLinear(Hsluv.m_r0 * xyz_x + Hsluv.m_r1 * xyz_y + Hsluv.m_r2 * xyz_z) + rgb_g = Hsluv.fromLinear(Hsluv.m_g0 * xyz_x + Hsluv.m_g1 * xyz_y + Hsluv.m_g2 * xyz_z) + rgb_b = Hsluv.fromLinear(Hsluv.m_b0 * xyz_x + Hsluv.m_b1 * xyz_y + Hsluv.m_b2 * xyz_z) + } + + public func rgbToXyz() { + let lr = Hsluv.toLinear(rgb_r) + let lg = Hsluv.toLinear(rgb_g) + let lb = Hsluv.toLinear(rgb_b) + xyz_x = 0.41239079926595 * lr + 0.35758433938387 * lg + 0.18048078840183 * lb + xyz_y = 0.21263900587151 * lr + 0.71516867876775 * lg + 0.072192315360733 * lb + xyz_z = 0.019330818715591 * lr + 0.11919477979462 * lg + 0.95053215224966 * lb + } + + public func xyzToLuv() { + let divider = xyz_x + 15 * xyz_y + 3 * xyz_z + var varU = 4 * xyz_x + var varV = 9 * xyz_y + if divider != 0 { + varU /= divider + varV /= divider + } else { + varU = Double.nan + varV = Double.nan + } + luv_l = Hsluv.yToL(xyz_y) + if luv_l == 0 { + luv_u = 0 + luv_v = 0 + } else { + luv_u = 13 * luv_l * (varU - Hsluv.refU) + luv_v = 13 * luv_l * (varV - Hsluv.refV) + } + } + + public func luvToXyz() { + if luv_l == 0 { + xyz_x = 0 + xyz_y = 0 + xyz_z = 0 + return + } + let varU = luv_u / (13 * luv_l) + Hsluv.refU + let varV = luv_v / (13 * luv_l) + Hsluv.refV + xyz_y = Hsluv.lToY(luv_l) + xyz_x = 0 - 9 * xyz_y * varU / ((varU - 4) * varV - varU * varV) + xyz_z = (9 * xyz_y - 15 * varV * xyz_y - varV * xyz_x) / (3 * varV) + } + + public func luvToLch() { + lch_l = luv_l + lch_c = sqrt(luv_u * luv_u + luv_v * luv_v) + if lch_c < 0.00000001 { + lch_h = 0 + } else { + let hrad = atan2(luv_v, luv_u) + lch_h = hrad * 180.0 / Double.pi + if lch_h < 0 { + lch_h = 360 + lch_h + } + } + } + + public func lchToLuv() { + let hrad = lch_h / 180.0 * Double.pi + luv_l = lch_l + luv_u = cos(hrad) * lch_c + luv_v = sin(hrad) * lch_c + } + + public func calculateBoundingLines(_ l: Double) { + let sub1 = pow(l + 16, 3) / 1560896 + let sub2 = sub1 > Hsluv.epsilon ? sub1 : l / Hsluv.kappa + let s1r = sub2 * (284517 * Hsluv.m_r0 - 94839 * Hsluv.m_r2) + let s2r = sub2 * (838422 * Hsluv.m_r2 + 769860 * Hsluv.m_r1 + 731718 * Hsluv.m_r0) + let s3r = sub2 * (632260 * Hsluv.m_r2 - 126452 * Hsluv.m_r1) + let s1g = sub2 * (284517 * Hsluv.m_g0 - 94839 * Hsluv.m_g2) + let s2g = sub2 * (838422 * Hsluv.m_g2 + 769860 * Hsluv.m_g1 + 731718 * Hsluv.m_g0) + let s3g = sub2 * (632260 * Hsluv.m_g2 - 126452 * Hsluv.m_g1) + let s1b = sub2 * (284517 * Hsluv.m_b0 - 94839 * Hsluv.m_b2) + let s2b = sub2 * (838422 * Hsluv.m_b2 + 769860 * Hsluv.m_b1 + 731718 * Hsluv.m_b0) + let s3b = sub2 * (632260 * Hsluv.m_b2 - 126452 * Hsluv.m_b1) + r0s = s1r / s3r + r0i = s2r * l / s3r + r1s = s1r / (s3r + 126452) + r1i = (s2r - 769860) * l / (s3r + 126452) + g0s = s1g / s3g + g0i = s2g * l / s3g + g1s = s1g / (s3g + 126452) + g1i = (s2g - 769860) * l / (s3g + 126452) + b0s = s1b / s3b + b0i = s2b * l / s3b + b1s = s1b / (s3b + 126452) + b1i = (s2b - 769860) * l / (s3b + 126452) + } + + public func calcMaxChromaHpluv() -> Double { + let r0 = Hsluv.distanceFromOrigin(r0s, r0i) + let r1 = Hsluv.distanceFromOrigin(r1s, r1i) + let g0 = Hsluv.distanceFromOrigin(g0s, g0i) + let g1 = Hsluv.distanceFromOrigin(g1s, g1i) + let b0 = Hsluv.distanceFromOrigin(b0s, b0i) + let b1 = Hsluv.distanceFromOrigin(b1s, b1i) + return Hsluv.min6(r0, r1, g0, g1, b0, b1) + } + + public func calcMaxChromaHsluv(_ h: Double) -> Double { + let hueRad = h / 360 * Double.pi * 2 + let r0 = Hsluv.distanceFromOriginAngle(r0s, r0i, hueRad) + let r1 = Hsluv.distanceFromOriginAngle(r1s, r1i, hueRad) + let g0 = Hsluv.distanceFromOriginAngle(g0s, g0i, hueRad) + let g1 = Hsluv.distanceFromOriginAngle(g1s, g1i, hueRad) + let b0 = Hsluv.distanceFromOriginAngle(b0s, b0i, hueRad) + let b1 = Hsluv.distanceFromOriginAngle(b1s, b1i, hueRad) + return Hsluv.min6(r0, r1, g0, g1, b0, b1) + } + + public func hsluvToLch() { + if hsluv_l > 99.9999999 { + lch_l = 100 + lch_c = 0 + } else if hsluv_l < 0.00000001 { + lch_l = 0 + lch_c = 0 + } else { + lch_l = hsluv_l + calculateBoundingLines(hsluv_l) + let max = calcMaxChromaHsluv(hsluv_h) + lch_c = max / 100 * hsluv_s + } + lch_h = hsluv_h + } + + public func lchToHsluv() { + if lch_l > 99.9999999 { + hsluv_s = 0 + hsluv_l = 100 + } else if lch_l < 0.00000001 { + hsluv_s = 0 + hsluv_l = 0 + } else { + calculateBoundingLines(lch_l) + let max = calcMaxChromaHsluv(lch_h) + hsluv_s = lch_c / max * 100 + hsluv_l = lch_l + } + hsluv_h = lch_h + } + + public func hpluvToLch() { + if hpluv_l > 99.9999999 { + lch_l = 100 + lch_c = 0 + } else if hpluv_l < 0.00000001 { + lch_l = 0 + lch_c = 0 + } else { + lch_l = hpluv_l + calculateBoundingLines(hpluv_l) + let max = calcMaxChromaHpluv() + lch_c = max / 100 * hpluv_p + } + lch_h = hpluv_h + } + + public func lchToHpluv() { + if lch_l > 99.9999999 { + hpluv_p = 0 + hpluv_l = 100 + } else if lch_l < 0.00000001 { + hpluv_p = 0 + hpluv_l = 0 + } else { + calculateBoundingLines(lch_l) + let max = calcMaxChromaHpluv() + hpluv_p = lch_c / max * 100 + hpluv_l = lch_l + } + hpluv_h = lch_h + } + + public func hsluvToRgb() { + hsluvToLch() + lchToLuv() + luvToXyz() + xyzToRgb() + } + + public func hpluvToRgb() { + hpluvToLch() + lchToLuv() + luvToXyz() + xyzToRgb() + } + + public func hsluvToHex() { + hsluvToRgb() + rgbToHex() + } + + public func hpluvToHex() { + hpluvToRgb() + rgbToHex() + } + + public func rgbToHsluv() { + rgbToXyz() + xyzToLuv() + luvToLch() + lchToHpluv() + lchToHsluv() + } + + public func rgbToHpluv() { + rgbToXyz() + xyzToLuv() + luvToLch() + lchToHpluv() + } + + public func hexToHsluv() { + hexToRgb() + rgbToHsluv() + } + + public func hexToHpluv() { + hexToRgb() + rgbToHpluv() + } +} + +/// Clamps a value between 0 and 1 +private func clamp(_ value: Double) -> Double { + return max(0.0, min(1.0, value)) +} + +/// Converts HSLuv color values to RGB tuple +/// - Parameters: +/// - h: Hue in degrees (0-360) +/// - s: Saturation percentage (0-100) +/// - l: Lightness percentage (0-100) +/// - Returns: Named tuple with red, green, blue values in range 0-1 +public func hsluvToRgb(_ h: Double, _ s: Double, _ l: Double) -> (red: Double, green: Double, blue: Double) { + let converter = Hsluv() + converter.hsluv_h = h + converter.hsluv_s = s + converter.hsluv_l = l + converter.hsluvToRgb() + + return ( + red: clamp(converter.rgb_r), + green: clamp(converter.rgb_g), + blue: clamp(converter.rgb_b) + ) +} + +/// Converts HPLuv color values to RGB tuple +/// - Parameters: +/// - h: Hue in degrees (0-360) +/// - p: Pastel percentage (0-100) +/// - l: Lightness percentage (0-100) +/// - Returns: Named tuple with red, green, blue values in range 0-1 +public func hpluvToRgb(_ h: Double, _ p: Double, _ l: Double) -> (red: Double, green: Double, blue: Double) { + let converter = Hsluv() + converter.hpluv_h = h + converter.hpluv_p = p + converter.hpluv_l = l + converter.hpluvToRgb() + + return ( + red: clamp(converter.rgb_r), + green: clamp(converter.rgb_g), + blue: clamp(converter.rgb_b) + ) +} diff --git a/Sources/HSLuvSwift/Color+Extensions.swift b/Sources/HSLuvSwift/Color+Extensions.swift deleted file mode 100644 index 3891993..0000000 --- a/Sources/HSLuvSwift/Color+Extensions.swift +++ /dev/null @@ -1,47 +0,0 @@ -// -// The MIT License (MIT) -// -// Copyright (c) 2015 Clay Smith -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -// - -#if os(iOS) -import UIKit - -private typealias SystemColor = UIColor -#elseif os(macOS) -import AppKit - -private typealias SystemColor = NSColor -#endif - -import CoreGraphics - -extension SystemColor: HSLuvInitializable, HSLuvConvertible { - /// Convenience function to wrap the behavior of getRed(red:green:blue:alpha:) - public func getRGB() -> (red: CGFloat, green: CGFloat, blue: CGFloat) { - var red: CGFloat = 0, green: CGFloat = 0, blue: CGFloat = 0, alpha: CGFloat = 0 - getRed(&red, green: &green, blue: &blue, alpha: &alpha) - return (red, green, blue) - } - -} - -extension SystemColor: HPLuvInitializable, HPLuvConvertible {} diff --git a/Sources/HSLuvSwift/ColorEncodings.swift b/Sources/HSLuvSwift/ColorEncodings.swift deleted file mode 100644 index cc7d003..0000000 --- a/Sources/HSLuvSwift/ColorEncodings.swift +++ /dev/null @@ -1,137 +0,0 @@ -// -// The MIT License (MIT) -// -// Copyright (c) 2015 Clay Smith -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -// - -// Using structs instead of tuples prevents implicit conversion, -// which was making debugging difficult - -import Foundation - -typealias Tuple = (Double, Double, Double) - -protocol TupleConverter { - var tuple: Tuple { get } -} - -/// Hexadecimal color -struct Hex { - let string: String - - init(_ string: String) { - self.string = string - } -} - -/// Red, Green, Blue (RGB) -struct RGBTuple: TupleConverter { - var R: Double - var G: Double - var B: Double - - init(_ R: Double, _ G: Double, _ B: Double) { - self.R = R - self.G = G - self.B = B - } - - var tuple: Tuple { - (R, G, B) - } -} - -/// Luminance, Blue-stimulation, Cone-response [CIE 1931] (XYZ) -struct XYZTuple: TupleConverter { - var X: Double - var Y: Double - var Z: Double - - init(_ X: Double, _ Y: Double, _ Z: Double) { - self.X = X - self.Y = Y - self.Z = Z - } - - var tuple: Tuple { - (X, Y, Z) - } -} - -/// L*, u*, v* [CIE 1976] (LUV) -struct LUVTuple { - var L: Double - var U: Double - var V: Double - - init(_ L: Double, _ U: Double, _ V: Double) { - self.L = L - self.U = U - self.V = V - } -} - -/// Lightness, Chroma, Hue (LCH) -struct LCHTuple { - var L: Double - var C: Double - var H: Double - - init(_ L: Double, _ C: Double, _ H: Double) { - self.L = L - self.C = C - self.H = H - } -} - -/// Hue(man), Saturation, Lightness (HSLuv) -struct HSLuvTuple: TupleConverter { - var H: Double - var S: Double - var L: Double - - init(_ H: Double, _ S: Double, _ L: Double) { - self.H = H - self.S = S - self.L = L - } - - var tuple: Tuple { - (H, S, L) - } -} - -/// Hue(man), Pastel saturation, Lightness (HPLuv) -struct HPLuvTuple: TupleConverter { - var H: Double - var P: Double - var L: Double - - init(_ H: Double, _ P: Double, _ L: Double) { - self.H = H - self.P = P - self.L = L - } - - var tuple: Tuple { - (H, P, L) - } -} diff --git a/Sources/HSLuvSwift/Constant.swift b/Sources/HSLuvSwift/Constant.swift deleted file mode 100644 index ec6527a..0000000 --- a/Sources/HSLuvSwift/Constant.swift +++ /dev/null @@ -1,50 +0,0 @@ -// -// The MIT License (MIT) -// -// Copyright (c) 2015 Alexei Boronine -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -// - -import Foundation - -struct Constant { - static var m = ( - R: Tuple(3.2409699419045214, -1.5373831775700935, -0.49861076029300328), - G: Tuple(-0.96924363628087983, 1.8759675015077207, 0.041555057407175613), - B: Tuple(0.055630079696993609, -0.20397695888897657, 1.0569715142428786) - ) - - static var mInv = ( - X: Tuple(0.41239079926595948, 0.35758433938387796, 0.18048078840183429), - Y: Tuple(0.21263900587151036, 0.71516867876775593, 0.072192315360733715), - Z: Tuple(0.019330818715591851, 0.11919477979462599, 0.95053215224966058) - ) - - // Hard-coded D65 standard illuminant - static var refU = 0.19783000664283681 - static var refV = 0.468319994938791 - - // CIE LUV constants - static var kappa = 903.2962962962963 - static var epsilon = 0.0088564516790356308 - - // Swift limitations - static var maxDouble = Double.greatestFiniteMagnitude -} diff --git a/Sources/HSLuvSwift/Info.plist b/Sources/HSLuvSwift/Info.plist deleted file mode 100644 index ca23c84..0000000 --- a/Sources/HSLuvSwift/Info.plist +++ /dev/null @@ -1,26 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - FMWK - CFBundleShortVersionString - $(MARKETING_VERSION) - CFBundleSignature - ???? - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - NSPrincipalClass - - - diff --git a/Sources/HSLuvSwift/Math.swift b/Sources/HSLuvSwift/Math.swift deleted file mode 100644 index 1a96841..0000000 --- a/Sources/HSLuvSwift/Math.swift +++ /dev/null @@ -1,369 +0,0 @@ -// -// The MIT License (MIT) -// -// Copyright (c) 2015 Alexei Boronine -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -// - -import Foundation - -// MARK: - Vector math -typealias Vector = (Double, Double) - -/// For a given lightness, return a list of 6 lines in slope-intercept -/// form that represent the bounds in CIELUV, stepping over which will -/// push a value out of the RGB gamut -/// -/// - parameter lightness: Double -func getBounds(lightness L: Double) -> [Vector] { - let sub1: Double = pow(L + 16, 3) / 1560896 - let sub2 = sub1 > Constant.epsilon ? sub1 : L / Constant.kappa - - var result = [Vector]() - - let mirror = Mirror(reflecting: Constant.m) - for (_, value) in mirror.children { - let (m1, m2, m3) = value as! Tuple - - for t in [0.0, 1.0] { - let top1 = (284517 * m1 - 94839 * m3) * sub2 - let top2 = (838422 * m3 + 769860 * m2 + 731718 * m1) * L * sub2 - 769860 * t * L - let bottom = (632260 * m3 - 126452 * m2) * sub2 + 126452 * t - - result.append((top1 / bottom, top2 / bottom)) - } - } - - return result -} - -func intersectLine(_ line1: Vector, _ line2: Vector) -> Double { - return (line1.1 - line2.1) / (line2.0 - line1.0) -} - -func distanceFromPole(_ point: Vector) -> Double { - return sqrt(pow(point.0, 2) + pow(point.1, 2)) -} - -func lengthOfRayUntilIntersect(theta: Double, line: Vector) -> Double? { - // theta -- angle of ray starting at (0, 0) - // m, b -- slope and intercept of line - // x1, y1 -- coordinates of intersection - // len -- length of ray until it intersects with line - // - // b + m * x1 = y1 - // len >= 0 - // len * cos(theta) = x1 - // len * sin(theta) = y1 - // - // - // b + m * (len * cos(theta)) = len * sin(theta) - // b = len * sin(hrad) - m * len * cos(theta) - // b = len * (sin(hrad) - m * cos(hrad)) - // len = b / (sin(hrad) - m * cos(hrad)) - - let (m1, b1) = line - let len = b1 / (sin(theta) - m1 * cos(theta)) - - if len < 0 { - return nil - } - - return len -} - -// MARK: RGB methods - -/// For given lightness, returns the maximum chroma. Keeping the chroma value -/// below this number will ensure that for any hue, the color is within the RGB -/// gamut. -func maxChroma(lightness L: Double) -> Double { - var lengths = [Double]() - - for (m1, b1) in getBounds(lightness: L) { - // x where line intersects with perpendicular running though (0, 0) - let x = intersectLine((m1, b1), (-1 / m1, 0)) - lengths.append(distanceFromPole((x, b1 + x * m1))) - } - - return lengths.reduce(Constant.maxDouble) { min($0, $1) } -} - -/// For a given lightness and hue, return the maximum chroma that fits in -/// the RGB gamut. -func maxChroma(lightness L: Double, hue H: Double) -> Double { - let hrad = H / 360 * Double.pi * 2 - - var lengths = [Double]() - for line in getBounds(lightness: L) { - if let l = lengthOfRayUntilIntersect(theta: hrad, line: line) { - lengths.append(l) - } - } - - return lengths.reduce(Constant.maxDouble) { min($0, $1) } -} - -func dotProduct(_ a: Tuple, b: T) -> Double { - let b = b.tuple - - var ret = 0.0 - - ret += a.0 * b.0 - ret += a.1 * b.1 - ret += a.2 * b.2 - - return ret -} - -// Used for RGB conversions -func fromLinear(_ c: Double) -> Double { - if c <= 0.0031308 { - return 12.92 * c - } - - return 1.055 * pow(c, 1 / 2.4) - 0.055 -} - -func toLinear(_ c: Double) -> Double { - let a = 0.055 - if c > 0.04045 { - return pow((c + a) / (1 + a), 2.4) - } - - return c / 12.92 -} - -// MARK: - CIELUVTuple - -// In these formulas, Yn refers to the reference white point. We are using -// illuminant D65, so Yn (see refY in Maxima file) equals 1. The formula is -// simplified accordingly. - -func yToL(_ Y: Double) -> Double { - if Y <= Constant.epsilon { - return Y * Constant.kappa - } - - return 116 * pow(Y, 1/3) - 16 -} - -func lToY(_ L: Double) -> Double { - if L <= 8 { - return L / Constant.kappa - } - - return pow((L + 16) / 116, 3) -} - -// MARK: - XYZ/RGB Conversion - -func xyzToRgb(_ xyz: XYZTuple) -> RGBTuple { - let R = fromLinear(dotProduct(Constant.m.R, b: xyz)) - let G = fromLinear(dotProduct(Constant.m.G, b: xyz)) - let B = fromLinear(dotProduct(Constant.m.B, b: xyz)) - - return RGBTuple(R, G, B) -} - -func rgbToXyz(_ rgb: RGBTuple) -> XYZTuple { - let rgbl = RGBTuple(toLinear(rgb.R), toLinear(rgb.G), toLinear(rgb.B)) - - let X = dotProduct(Constant.mInv.X, b: rgbl) - let Y = dotProduct(Constant.mInv.Y, b: rgbl) - let Z = dotProduct(Constant.mInv.Z, b: rgbl) - - return XYZTuple(X, Y, Z) -} - -// MARK: - XYZ/LUV Conversion - -func xyzToLuv(_ xyz: XYZTuple) -> LUVTuple { - let varU = (4 * xyz.X) / (xyz.X + (15 * xyz.Y) + (3 * xyz.Z)) - let varV = (9 * xyz.Y) / (xyz.X + (15 * xyz.Y) + (3 * xyz.Z)) - - let L = yToL(xyz.Y) - - guard L != 0 else { - // Black will create a divide-by-zero error - return LUVTuple(0, 0, 0) - } - - let U = 13 * L * (varU - Constant.refU) - let V = 13 * L * (varV - Constant.refV) - - return LUVTuple(L, U, V) -} - -func luvToXyz(_ luv: LUVTuple) -> XYZTuple { - guard luv.L != 0 else { - // Black will create a divide-by-zero error - return XYZTuple(0, 0, 0) - } - - let varU = luv.U / (13 * luv.L) + Constant.refU - let varV = luv.V / (13 * luv.L) + Constant.refV - - let Y = lToY(luv.L) - let X = 0 - (9 * Y * varU) / ((varU - 4) * varV - varU * varV) - let Z = (9 * Y - (15 * varV * Y) - (varV * X)) / (3 * varV) - - return XYZTuple(X, Y, Z) -} - -// MARK: - LUV/LCH Conversion - -func luvToLch(_ luv: LUVTuple) -> LCHTuple { - let C = sqrt(pow(luv.U, 2) + pow(luv.V, 2)) - - guard C >= 0.00000001 else { - // Greys: disambiguate hue - return LCHTuple(luv.L, C, 0) - } - - let Hrad = atan2(luv.V, luv.U) - var H = Hrad * 360 / 2 / Double.pi - - if H < 0 { - H = 360 + H - } - - return LCHTuple(luv.L, C, H) -} - -func lchToLuv(_ lch: LCHTuple) -> LUVTuple { - let Hrad = lch.H / 360 * 2 * Double.pi - let U = cos(Hrad) * lch.C - let V = sin(Hrad) * lch.C - - return LUVTuple(lch.L, U, V) -} - -// MARK: - HSLuv/LCH Conversion - -func hsluvToLch(_ hsluv: HSLuvTuple) -> LCHTuple { - guard hsluv.L <= 99.9999999 && hsluv.L >= 0.00000001 else { - // White and black: disambiguate chroma - return LCHTuple(hsluv.L, 0, hsluv.H) - } - - let max = maxChroma(lightness: hsluv.L, hue: hsluv.H) - let C = max / 100 * hsluv.S - - return LCHTuple(hsluv.L, C, hsluv.H) -} - -func lchToHsluv(_ lch: LCHTuple) -> HSLuvTuple { - guard lch.L <= 99.9999999 && lch.L >= 0.00000001 else { - // White and black: disambiguate saturation - return HSLuvTuple(lch.H, 0, lch.L) - } - - let max = maxChroma(lightness: lch.L, hue: lch.H) - let S = lch.C / max * 100 - - return HSLuvTuple(lch.H, S, lch.L) -} - -// MARK: - Pastel HSLuv/LCH Conversion - -func hpluvToLch(_ hsluv: HSLuvTuple) -> LCHTuple { - guard hsluv.L <= 99.9999999 && hsluv.L >= 0.00000001 else { - // White and black: disambiguate chroma - return LCHTuple(hsluv.L, 0, hsluv.H) - } - - let max = maxChroma(lightness: hsluv.L) - let C = max / 100 * hsluv.S - - return LCHTuple(hsluv.L, C, hsluv.H) -} - -func lchToHpluv(_ lch: LCHTuple) -> HPLuvTuple { - guard lch.L <= 99.9999999 && lch.L >= 0.00000001 else { - // White and black: disambiguate saturation - return HPLuvTuple(lch.H, 0, lch.L) - } - - let max = maxChroma(lightness: lch.L) - let S = lch.C / max * 100 - - return HPLuvTuple(lch.H, S, lch.L) -} - -// MARK: - RGB/Hex Conversion -func round(_ value: Double, places: Double) -> Double { - let divisor = pow(10.0, places) - return round(value * divisor) / divisor -} - -func getHexString(_ channel: Double) -> String { - var ch = round(channel, places: 6) - - if ch < 0 || ch > 1 { - // TODO: Implement Swift thrown errors - fatalError("Illegal RGB value: \(ch)") - } - - ch = round(ch * 255.0) - - return String(Int(ch), radix: 16, uppercase: false).padding(toLength: 2, withPad: "0", startingAt: 0) -} - -func rgbToHex(_ rgb: RGBTuple) -> Hex { - let R = getHexString(rgb.R) - let G = getHexString(rgb.G) - let B = getHexString(rgb.B) - - return Hex("#\(R)\(G)\(B)") -} - -// This function is based on a comment by mehawk on gist arshad/de147c42d7b3063ef7bc. -// It is so flippin' elegant. -func hexToRgb(_ hex: Hex) -> RGBTuple { - let string = hex.string.replacingOccurrences(of: "#", with: "") - - var rgbValue: UInt32 = 0 - Scanner(string: string).scanHexInt32(&rgbValue) - - return RGBTuple( - Double((rgbValue & 0xFF0000) >> 16) / 255.0, - Double((rgbValue & 0x00FF00) >> 8) / 255.0, - Double( rgbValue & 0x0000FF) / 255.0 - ) -} - -// MARK: - Conversion shortcuts - -func hsluvToRgb(_ hsl: HSLuvTuple) -> RGBTuple { - return xyzToRgb(luvToXyz(lchToLuv(hsluvToLch(hsl)))) -} - -func rgbToHsluv(_ rgb: RGBTuple) -> HSLuvTuple { - return lchToHsluv(luvToLch(xyzToLuv(rgbToXyz(rgb)))) -} - -func hpluvToRgb(_ hsl: HSLuvTuple) -> RGBTuple { - return xyzToRgb(luvToXyz(lchToLuv(hpluvToLch(hsl)))) -} - -func rgbToHpluv(_ rgb: RGBTuple) -> HPLuvTuple { - return lchToHpluv(luvToLch(xyzToLuv(rgbToXyz(rgb)))) -} diff --git a/Sources/HSLuvSwift/Protocols.swift b/Sources/HSLuvSwift/Protocols.swift deleted file mode 100644 index 1a4ea55..0000000 --- a/Sources/HSLuvSwift/Protocols.swift +++ /dev/null @@ -1,118 +0,0 @@ -// -// The MIT License (MIT) -// -// Copyright (c) 2015 Alexei Boronine -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -// - -import CoreGraphics - -// Abstracts NSColor / UIColor -public protocol Color { - - init(red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) - - /// Convenience function to wrap the behavior of getRed(red:green:blue:alpha:) - func getRGB() -> (red: CGFloat, green: CGFloat, blue: CGFloat) - -} - -extension Color { - - func getRGBTuple() -> RGBTuple { - let (red, green, blue) = getRGB() - return RGBTuple(Double(red), Double(green), Double(blue)) - } - -} - -// MARK: - Initialization protocols - -public protocol HSLuvInitializable: Color {} -extension HSLuvInitializable { - - /// Initializes and returns a color object using the specified opacity and - /// HSLuv color space component values. - /// - /// - parameter hue: Double - /// - parameter saturation: Double - /// - parameter lightness: Double - /// - parameter alpha: Double - public init(hue: Double, saturation: Double, lightness: Double, alpha: Double) { - let rgb = hsluvToRgb(HSLuvTuple(hue, saturation, lightness)) - self.init(red: CGFloat(rgb.R), green: CGFloat(rgb.G), blue: CGFloat(rgb.B), alpha: CGFloat(alpha)) - } - -} - -public protocol HPLuvInitializable: Color {} -extension HPLuvInitializable { - - /// Initializes and returns a color object using the specified opacity and - /// HPLuv color space component values. - /// - /// - parameter hue: Double - /// - parameter pastelSaturation: Double - /// - parameter lightness: Double - /// - parameter alpha: Double - public init(hue: Double, pastelSaturation: Double, lightness: Double, alpha: Double) { - let rgb = hpluvToRgb(HSLuvTuple(hue, pastelSaturation, lightness)) - self.init(red: CGFloat(rgb.R), green: CGFloat(rgb.G), blue: CGFloat(rgb.B), alpha: CGFloat(alpha)) - } - -} - -// MARK: - To HSLuv HPLuv conversions - -extension HSLuvTuple { - - init(_ color: Color) { - self = rgbToHsluv(color.getRGBTuple()) - } - -} - -extension HPLuvTuple { - - init(_ color: Color) { - self = rgbToHpluv(color.getRGBTuple()) - } - -} - -public protocol HSLuvConvertible: Color {} -extension HSLuvConvertible { - - /// Returns the HSLuv color space component values for this color. - public func getHSLuv() -> (hue: Double, saturation: Double, lightness: Double) { - HSLuvTuple(self).tuple - } - -} - -public protocol HPLuvConvertible: Color {} -extension HPLuvConvertible { - - /// Returns the HPLuv color space component values for this color. - public func getHPLuv() -> (hue: Double, pastelSaturation: Double, lightness: Double) { - HPLuvTuple(self).tuple - } - -} diff --git a/Tests/HSLuvSwiftTests/HSLuvTests.swift b/Tests/HSLuvSwiftTests/HSLuvTests.swift deleted file mode 100644 index fabbb34..0000000 --- a/Tests/HSLuvSwiftTests/HSLuvTests.swift +++ /dev/null @@ -1,83 +0,0 @@ -// -// Common.swift -// HSLuvSwift -// -// Created by Clay Smith on 6/16/15. -// Copyright © 2015 Clay Smith. All rights reserved. -// - -import Foundation -import XCTest -@testable import HSLuvSwift - -// TODO: Add HPLuv tests - -class HSLuvTests: XCTestCase { - let rgbRangeTolerance = 0.000000001 - let snapshotTolerance = 0.000000001 - - override func setUp() { - super.setUp() - // Put setup code here. This method is called before the invocation of each test method in the class. - - continueAfterFailure = false - } - - func testConversionConsistency() { - for fromHex in Snapshot.hexSamples { - let fromRgb = hexToRgb(Hex(fromHex)) - let fromXyz = rgbToXyz(fromRgb) - let fromLuv = xyzToLuv(fromXyz) - let fromLch = luvToLch(fromLuv) - let fromHsluv = lchToHsluv(fromLch) - - let toLch = hsluvToLch(fromHsluv) - let toLuv = lchToLuv(toLch) - let toXyz = luvToXyz(toLuv) - let toRgb = xyzToRgb(toXyz) - let toHex = rgbToHex(toRgb) - - XCTAssertEqual(fromLch.L, toLch.L, accuracy: rgbRangeTolerance) - XCTAssertEqual(fromLch.C, toLch.C, accuracy: rgbRangeTolerance) - XCTAssertEqual(fromLch.H, toLch.H, accuracy: rgbRangeTolerance) - - XCTAssertEqual(fromLuv.L, toLuv.L, accuracy: rgbRangeTolerance) - XCTAssertEqual(fromLuv.U, toLuv.U, accuracy: rgbRangeTolerance) - XCTAssertEqual(fromLuv.V, toLuv.V, accuracy: rgbRangeTolerance) - - XCTAssertEqual(fromXyz.X, toXyz.X, accuracy: rgbRangeTolerance) - XCTAssertEqual(fromXyz.Y, toXyz.Y, accuracy: rgbRangeTolerance) - XCTAssertEqual(fromXyz.Z, toXyz.Z, accuracy: rgbRangeTolerance) - - XCTAssertEqual(fromRgb.R, toRgb.R, accuracy: rgbRangeTolerance) - XCTAssertEqual(fromRgb.G, toRgb.G, accuracy: rgbRangeTolerance) - XCTAssertEqual(fromRgb.B, toRgb.B, accuracy: rgbRangeTolerance) - - XCTAssertEqual(fromHex, toHex.string) - } - } - - func testRgbRangeTolerance() { - for h in stride(from: 0.0, through: 360, by: 5) { - for s in stride(from: 0.0, through: 100, by: 5) { - for l in stride(from: 0.0, through: 100, by: 5) { - let tRgb = hsluvToRgb(HSLuvTuple(h, s, l)) - let rgb = [tRgb.R, tRgb.G, tRgb.B] - - for channel in rgb { - XCTAssertGreaterThan(channel, -rgbRangeTolerance, "HSLuv: \([h, s, l]) -> RGB: \(rgb)") - XCTAssertLessThanOrEqual(channel, 1 + rgbRangeTolerance, "HSLuv: \([h, s, l]) -> RGB: \(rgb)") - } - } - } - } - } - - func testSnapshot() { - Snapshot.compare(Snapshot.current) { [snapshotTolerance] hex, tag, stableTuple, currentTuple, stableChannel, currentChannel in - let diff = abs(currentChannel - stableChannel) - - XCTAssertLessThan(diff, snapshotTolerance, "Snapshots for \(hex) don't match at \(tag): (stable: \(stableTuple), current: \(currentTuple)") - } - } -} diff --git a/Tests/HSLuvSwiftTests/Info.plist b/Tests/HSLuvSwiftTests/Info.plist deleted file mode 100644 index ba72822..0000000 --- a/Tests/HSLuvSwiftTests/Info.plist +++ /dev/null @@ -1,24 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - BNDL - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - 1 - - diff --git a/Tests/HSLuvSwiftTests/MacExtensions/AppKit.swift b/Tests/HSLuvSwiftTests/MacExtensions/AppKit.swift deleted file mode 100644 index 67cca20..0000000 --- a/Tests/HSLuvSwiftTests/MacExtensions/AppKit.swift +++ /dev/null @@ -1,55 +0,0 @@ -// -// The MIT License (MIT) -// -// Copyright (c) 2015 Clay Smith -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -// - -import Foundation -import XCTest -import HSLuvSwift - -#if canImport(AppKit) - -class AppKitTests: XCTestCase { - let rgbRangeTolerance = 0.00000000001 - - func testNSColorRGBRangeTolerance() { - for h in stride(from: 0.0, through: 360, by: 5) { - for s in stride(from: 0.0, through: 100, by: 5) { - for l in stride(from: 0.0, through: 100, by: 5) { - let color = NSColor(hue: h, saturation: s, lightness: l, alpha: 1.0) - - XCTAssertNotNil(color) - - let tRgb = color.getRGB() - let rgb = [tRgb.red, tRgb.green, tRgb.blue].map { Double($0) } - - for channel in rgb { - XCTAssertGreaterThan(channel, -rgbRangeTolerance, "HSLuv: \([h, s, l]) -> RGB: \(rgb)") - XCTAssertLessThanOrEqual(channel, 1 + rgbRangeTolerance, "HSLuv: \([h, s, l]) -> RGB: \(rgb)") - } - } - } - } - } -} - -#endif diff --git a/Tests/HSLuvSwiftTests/Snapshot.swift b/Tests/HSLuvSwiftTests/Snapshot.swift deleted file mode 100644 index 1defd74..0000000 --- a/Tests/HSLuvSwiftTests/Snapshot.swift +++ /dev/null @@ -1,103 +0,0 @@ -// -// Snapshot.swift -// HSLuvSwift -// -// Created by Clay Smith on 6/17/15. -// Copyright © 2015 Clay Smith. All rights reserved. -// - -import Foundation -import XCTest -@testable import HSLuvSwift - -// TODO: Add HPLuv support - -typealias SnapshotType = [String: [String: [Double]]] - -class Snapshot { - static var hexSamples: [String] = { - let samples = "0123456789abcdef" - - var hexSamples = [String]() - samples.forEach { r in - samples.forEach { g in - samples.forEach { b in - hexSamples.append("#\(r)\(r)\(g)\(g)\(b)\(b)") - } - } - } - - return hexSamples - }() - - static var stable: SnapshotType = { - // SPM has a special way of accessing bundle resources, - // therefore we need to handle this differenlty than in the project. - #if SPM - guard let url = Bundle.module.url(forResource: "snapshot-rev4", withExtension: "json") else { - fatalError("Snapshot JSON file is missing") - } - #else - guard let url = Bundle(for: Snapshot.self).url(forResource: "snapshot-rev4", withExtension: "json") else { - fatalError("Snapshot JSON file is missing") - } - #endif - - let jsonData = try! Data(contentsOf: url, options: .mappedIfSafe) - let jsonResult = try! JSONSerialization.jsonObject(with: jsonData, options: .mutableContainers) as! SnapshotType - - return jsonResult - }() - - static var current: SnapshotType = { - var current = SnapshotType() - - for sample in Snapshot.hexSamples { - let hex = Hex(sample) - - let rgb = hexToRgb(hex) - let xyz = rgbToXyz(rgb) - let luv = xyzToLuv(xyz) - let lch = luvToLch(luv) - let hsluv = lchToHsluv(lch) - let hpluv = lchToHpluv(lch) - - current[sample] = [ - "rgb": [rgb.R, rgb.G, rgb.B], - "xyz": [xyz.X, xyz.Y, xyz.Z], - "luv": [luv.L, luv.U, luv.V], - "lch": [lch.L, lch.C, lch.H], - "hsluv": [hsluv.H, hsluv.S, hsluv.L], - "hpluv": [hpluv.H, hpluv.P, hpluv.L] - ] - } - - return current - }() - - static func compare(_ snapshot: SnapshotType, - block: (_ hex: String, _ tag: String, _ stableTuple: [Double], _ currentTuple: [Double], _ stableChannel: Double, _ currentChannel: Double) -> Void) { - - hexes: for (hex, stableSamples) in stable { - guard let currentSamples = current[hex] else { - fatalError("Current sample is missing at \(hex)") - } - - tags: for (tag, stableTuple) in stableSamples { - guard let currentTuple = currentSamples[tag] else { - fatalError("Current tuple is missing at \(hex):\(tag)") - } - - channels: for i in [0...2] { - guard let stableChannel = stableTuple[i].first, let currentChannel = currentTuple[i].first else { - fatalError("Current channel is missing at \(hex):\(tag):\(i)") - } - - block(hex, tag, stableTuple, currentTuple, stableChannel, currentChannel) - } - - } - } - - } -} diff --git a/Tests/HSLuvSwiftTests/iOSExtensions/UIKit.swift b/Tests/HSLuvSwiftTests/iOSExtensions/UIKit.swift deleted file mode 100644 index 4774e7b..0000000 --- a/Tests/HSLuvSwiftTests/iOSExtensions/UIKit.swift +++ /dev/null @@ -1,55 +0,0 @@ -// -// The MIT License (MIT) -// -// Copyright (c) 2015 Clay Smith -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -// - -import Foundation -import XCTest -import HSLuvSwift - -#if canImport(UIKit) - -class UIKitTests: XCTestCase { - let rgbRangeTolerance = 0.00000000001 - - func testUIColorRGBRangeTolerance() { - for h in stride(from: 0, through: 360, by: 5) { - for s in stride(from: 0, through: 100, by: 5) { - for l in stride(from: 0, through: 100, by: 5) { - let color = UIColor(hue: Double(h), saturation: Double(s), lightness: Double(l), alpha: 1.0) - - XCTAssertNotNil(color) - - let tRgb = color.getRGB() - let rgb = [tRgb.red, tRgb.green, tRgb.blue].map { Double($0) } - - for channel in rgb { - XCTAssertGreaterThan(channel, -rgbRangeTolerance, "HSLuv: \([h, s, l]) -> RGB: \(rgb)") - XCTAssertLessThanOrEqual(channel, 1 + rgbRangeTolerance, "HSLuv: \([h, s, l]) -> RGB: \(rgb)") - } - } - } - } - } -} - -#endif diff --git a/Tests/HSLuvTests.swift b/Tests/HSLuvTests.swift new file mode 100644 index 0000000..bd32e8e --- /dev/null +++ b/Tests/HSLuvTests.swift @@ -0,0 +1,122 @@ +// https://www.swift.org/documentation/server/guides/testing.html +import XCTest +import Foundation +@testable import HSLuv + +class HSLuvTests: XCTestCase { + + struct Snapshot: Codable { + let rgb: [Double] + let xyz: [Double] + let luv: [Double] + let lch: [Double] + let hsluv: [Double] + let hpluv: [Double] + } + + func assertClose(expected: Hsluv, actual: Hsluv, tolerance: Double = 1e-10) { + XCTAssertEqual(expected.hex, actual.hex) + XCTAssertLessThanOrEqual(abs(expected.rgb_r - actual.rgb_r), tolerance) + XCTAssertLessThanOrEqual(abs(expected.rgb_g - actual.rgb_g), tolerance) + XCTAssertLessThanOrEqual(abs(expected.rgb_b - actual.rgb_b), tolerance) + XCTAssertLessThanOrEqual(abs(expected.xyz_x - actual.xyz_x), tolerance) + XCTAssertLessThanOrEqual(abs(expected.xyz_y - actual.xyz_y), tolerance) + XCTAssertLessThanOrEqual(abs(expected.xyz_z - actual.xyz_z), tolerance) + XCTAssertLessThanOrEqual(abs(expected.luv_l - actual.luv_l), tolerance) + XCTAssertLessThanOrEqual(abs(expected.luv_u - actual.luv_u), tolerance) + XCTAssertLessThanOrEqual(abs(expected.luv_v - actual.luv_v), tolerance) + XCTAssertLessThanOrEqual(abs(expected.lch_l - actual.lch_l), tolerance) + XCTAssertLessThanOrEqual(abs(expected.lch_c - actual.lch_c), tolerance) + XCTAssertLessThanOrEqual(abs(expected.lch_h - actual.lch_h), tolerance) + XCTAssertLessThanOrEqual(abs(expected.hsluv_h - actual.hsluv_h), tolerance) + XCTAssertLessThanOrEqual(abs(expected.hsluv_s - actual.hsluv_s), tolerance) + XCTAssertLessThanOrEqual(abs(expected.hsluv_l - actual.hsluv_l), tolerance) + XCTAssertLessThanOrEqual(abs(expected.hpluv_h - actual.hpluv_h), tolerance) + XCTAssertLessThanOrEqual(abs(expected.hpluv_p - actual.hpluv_p), tolerance) + XCTAssertLessThanOrEqual(abs(expected.hpluv_l - actual.hpluv_l), tolerance) + } + + func testStandaloneFunctions() { + // Test that the functions match the class-based implementation + + // hsluvToRgb + let converter = Hsluv() + converter.hsluv_h = 120 + converter.hsluv_s = 50 + converter.hsluv_l = 70 + converter.hsluvToRgb() + + let standaloneResult = hsluvToRgb(120, 50, 70) + XCTAssertEqual(standaloneResult.red, clamp(converter.rgb_r), accuracy: 0.0001) + XCTAssertEqual(standaloneResult.green, clamp(converter.rgb_g), accuracy: 0.0001) + XCTAssertEqual(standaloneResult.blue, clamp(converter.rgb_b), accuracy: 0.0001) + + // hpluvToRgb + converter.hpluv_h = 120 + converter.hpluv_p = 50 + converter.hpluv_l = 70 + converter.hpluvToRgb() + + let standaloneResult2 = hpluvToRgb(120, 50, 70) + XCTAssertEqual(standaloneResult2.red, clamp(converter.rgb_r), accuracy: 0.0001) + XCTAssertEqual(standaloneResult2.green, clamp(converter.rgb_g), accuracy: 0.0001) + XCTAssertEqual(standaloneResult2.blue, clamp(converter.rgb_b), accuracy: 0.0001) + } + + private func clamp(_ value: Double) -> Double { + return max(0.0, min(1.0, value)) + } + + func testSnapshot() { + let url = URL(fileURLWithPath: "Tests/Resources/snapshot-rev4.json") + guard let data = try? Data(contentsOf: url), + let snapshot = try? JSONDecoder().decode([String: Snapshot].self, from: data) else { + XCTFail("Could not load snapshot-rev4.json") + return + } + + let conv = Hsluv() + + for (hex, s) in snapshot { + let sample = Hsluv() + sample.hex = hex + sample.rgb_r = s.rgb[0] + sample.rgb_g = s.rgb[1] + sample.rgb_b = s.rgb[2] + sample.xyz_x = s.xyz[0] + sample.xyz_y = s.xyz[1] + sample.xyz_z = s.xyz[2] + sample.luv_l = s.luv[0] + sample.luv_u = s.luv[1] + sample.luv_v = s.luv[2] + sample.lch_l = s.lch[0] + sample.lch_c = s.lch[1] + sample.lch_h = s.lch[2] + sample.hsluv_h = s.hsluv[0] + sample.hsluv_s = s.hsluv[1] + sample.hsluv_l = s.hsluv[2] + sample.hpluv_h = s.hpluv[0] + sample.hpluv_p = s.hpluv[1] + sample.hpluv_l = s.hpluv[2] + + // Test hex to HSLuv conversion + conv.hex = hex + conv.hexToHsluv() + assertClose(expected: sample, actual: conv) + + // Test HSLuv to hex conversion + conv.hsluv_h = sample.hsluv_h + conv.hsluv_s = sample.hsluv_s + conv.hsluv_l = sample.hsluv_l + conv.hsluvToHex() + assertClose(expected: sample, actual: conv) + + // Test HPLuv to hex conversion + conv.hpluv_h = sample.hpluv_h + conv.hpluv_p = sample.hpluv_p + conv.hpluv_l = sample.hpluv_l + conv.hpluvToHex() + assertClose(expected: sample, actual: conv) + } + } +} \ No newline at end of file diff --git a/Tests/HSLuvSwiftTests/Resources/snapshot-rev4.json b/Tests/Resources/snapshot-rev4.json similarity index 100% rename from Tests/HSLuvSwiftTests/Resources/snapshot-rev4.json rename to Tests/Resources/snapshot-rev4.json