├── .gitattributes ├── .gitignore ├── ImageColoriser.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ └── ImageColoriser.xcscheme ├── ImageColoriser ├── .gitattributes ├── Resources │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Images │ │ ├── jordan-pulmano-PoAkzayxhYE-unsplash.jpg │ │ ├── kayla-kozlowski-p6x4jGJ-oU4-unsplash.jpg │ │ ├── omar-mena-QIktihNFiYY-unsplash.jpg │ │ ├── robert-godwin-cdksyTqEXzo-unsplash.jpg │ │ ├── stephanie-bergeron-AjFVsvjuSPU-unsplash.jpg │ │ ├── taya-dianna-elMQ400zvpc-unsplash.jpg │ │ ├── taylor-kopel-WX4i1Jq_o0Y-unsplash.jpg │ │ └── tom-prejeant-bAT7YePX_zA-unsplash.jpg │ ├── Info.plist │ └── coloriser.mlmodel └── Sources │ ├── AppDelegate.swift │ ├── ImageColorizer │ ├── ImageColorizer.swift │ ├── ImagePicker.swift │ ├── ImageProvider.swift │ ├── ImageScanner.swift │ └── coremlColorizer.mlmodel │ ├── SceneDelegate.swift │ ├── Utils │ ├── DispatchQueue+Utils.swift │ ├── ImageColoriser-Bridging-Header.h │ ├── UIImage+Lab.h │ ├── UIImage+Lab.mm │ ├── UIImage+Utils.swift │ ├── UIViewController+Utils.swift │ └── littlecms │ │ ├── Makefile.am │ │ ├── Makefile.in │ │ ├── cmsalpha.c │ │ ├── cmscam02.c │ │ ├── cmscgats.c │ │ ├── cmscnvrt.c │ │ ├── cmserr.c │ │ ├── cmsgamma.c │ │ ├── cmsgmt.c │ │ ├── cmshalf.c │ │ ├── cmsintrp.c │ │ ├── cmsio0.c │ │ ├── cmsio1.c │ │ ├── cmslut.c │ │ ├── cmsmd5.c │ │ ├── cmsmtrx.c │ │ ├── cmsnamed.c │ │ ├── cmsopt.c │ │ ├── cmspack.c │ │ ├── cmspcs.c │ │ ├── cmsplugin.c │ │ ├── cmsps2.c │ │ ├── cmssamp.c │ │ ├── cmssm.c │ │ ├── cmstypes.c │ │ ├── cmsvirt.c │ │ ├── cmswtpnt.c │ │ ├── cmsxform.c │ │ ├── lcms2.def │ │ ├── lcms2.h │ │ ├── lcms2_internal.h │ │ ├── lcms2_plugin.h │ │ ├── sRGB_ICC_v4_Appearance.icc │ │ └── sRGB_v4_ICC_preference.icc │ └── ViewController.swift ├── README.md └── screenshots └── demo.gif /.gitattributes: -------------------------------------------------------------------------------- 1 | *.mlmodel filter=lfs diff=lfs merge=lfs -text 2 | ImageColoriser/Sources/Utils/littlecms/* linguist-vendored=true -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | ## Playgrounds 32 | timeline.xctimeline 33 | playground.xcworkspace 34 | 35 | # Swift Package Manager 36 | # 37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 38 | # Packages/ 39 | # Package.pins 40 | # Package.resolved 41 | .build/ 42 | 43 | # CocoaPods 44 | # 45 | # We recommend against adding the Pods directory to your .gitignore. However 46 | # you should judge for yourself, the pros and cons are mentioned at: 47 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 48 | # 49 | # Pods/ 50 | 51 | # Carthage 52 | # 53 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 54 | # Carthage/Checkouts 55 | 56 | Carthage/Build 57 | 58 | # fastlane 59 | # 60 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 61 | # screenshots whenever they are needed. 62 | # For more information about the recommended setup visit: 63 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 64 | 65 | fastlane/report.xml 66 | fastlane/Preview.html 67 | fastlane/screenshots/**/*.png 68 | fastlane/test_output 69 | -------------------------------------------------------------------------------- /ImageColoriser.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ImageColoriser.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ImageColoriser.xcodeproj/xcshareddata/xcschemes/ImageColoriser.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 54 | 60 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /ImageColoriser/.gitattributes: -------------------------------------------------------------------------------- 1 | *.mlmodel filter=lfs diff=lfs merge=lfs -text 2 | -------------------------------------------------------------------------------- /ImageColoriser/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /ImageColoriser/Resources/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /ImageColoriser/Resources/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /ImageColoriser/Resources/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /ImageColoriser/Resources/Images/jordan-pulmano-PoAkzayxhYE-unsplash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sgl0v/ImageColorizer/9d34b28e473ac2d7438cbece0979d00262c0d447/ImageColoriser/Resources/Images/jordan-pulmano-PoAkzayxhYE-unsplash.jpg -------------------------------------------------------------------------------- /ImageColoriser/Resources/Images/kayla-kozlowski-p6x4jGJ-oU4-unsplash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sgl0v/ImageColorizer/9d34b28e473ac2d7438cbece0979d00262c0d447/ImageColoriser/Resources/Images/kayla-kozlowski-p6x4jGJ-oU4-unsplash.jpg -------------------------------------------------------------------------------- /ImageColoriser/Resources/Images/omar-mena-QIktihNFiYY-unsplash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sgl0v/ImageColorizer/9d34b28e473ac2d7438cbece0979d00262c0d447/ImageColoriser/Resources/Images/omar-mena-QIktihNFiYY-unsplash.jpg -------------------------------------------------------------------------------- /ImageColoriser/Resources/Images/robert-godwin-cdksyTqEXzo-unsplash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sgl0v/ImageColorizer/9d34b28e473ac2d7438cbece0979d00262c0d447/ImageColoriser/Resources/Images/robert-godwin-cdksyTqEXzo-unsplash.jpg -------------------------------------------------------------------------------- /ImageColoriser/Resources/Images/stephanie-bergeron-AjFVsvjuSPU-unsplash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sgl0v/ImageColorizer/9d34b28e473ac2d7438cbece0979d00262c0d447/ImageColoriser/Resources/Images/stephanie-bergeron-AjFVsvjuSPU-unsplash.jpg -------------------------------------------------------------------------------- /ImageColoriser/Resources/Images/taya-dianna-elMQ400zvpc-unsplash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sgl0v/ImageColorizer/9d34b28e473ac2d7438cbece0979d00262c0d447/ImageColoriser/Resources/Images/taya-dianna-elMQ400zvpc-unsplash.jpg -------------------------------------------------------------------------------- /ImageColoriser/Resources/Images/taylor-kopel-WX4i1Jq_o0Y-unsplash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sgl0v/ImageColorizer/9d34b28e473ac2d7438cbece0979d00262c0d447/ImageColoriser/Resources/Images/taylor-kopel-WX4i1Jq_o0Y-unsplash.jpg -------------------------------------------------------------------------------- /ImageColoriser/Resources/Images/tom-prejeant-bAT7YePX_zA-unsplash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sgl0v/ImageColorizer/9d34b28e473ac2d7438cbece0979d00262c0d447/ImageColoriser/Resources/Images/tom-prejeant-bAT7YePX_zA-unsplash.jpg -------------------------------------------------------------------------------- /ImageColoriser/Resources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | NSCameraUsageDescription 24 | This app requires access to the camera 25 | NSPhotoLibraryUsageDescription 26 | This app requires access to the photo library 27 | UIApplicationSceneManifest 28 | 29 | UIApplicationSupportsMultipleScenes 30 | 31 | UISceneConfigurations 32 | 33 | UIWindowSceneSessionRoleApplication 34 | 35 | 36 | UISceneConfigurationName 37 | Default Configuration 38 | UISceneDelegateClassName 39 | $(PRODUCT_MODULE_NAME).SceneDelegate 40 | UISceneStoryboardFile 41 | Main 42 | 43 | 44 | 45 | 46 | UILaunchStoryboardName 47 | LaunchScreen 48 | UIMainStoryboardFile 49 | Main 50 | UIRequiredDeviceCapabilities 51 | 52 | armv7 53 | 54 | UISupportedInterfaceOrientations 55 | 56 | UIInterfaceOrientationPortrait 57 | 58 | UISupportedInterfaceOrientations~ipad 59 | 60 | UIInterfaceOrientationPortrait 61 | UIInterfaceOrientationPortraitUpsideDown 62 | UIInterfaceOrientationLandscapeLeft 63 | UIInterfaceOrientationLandscapeRight 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /ImageColoriser/Resources/coloriser.mlmodel: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:c23f95bc1e6cce7e237b26f89d933c364bb33db939096993bd6417cd3e55e962 3 | size 26167522 4 | -------------------------------------------------------------------------------- /ImageColoriser/Sources/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageColoriser 3 | // 4 | // Created by Maksym Shcheglov. 5 | // Copyright © 2021 Maksym Shcheglov. All rights reserved. 6 | // 7 | 8 | import UIKit 9 | 10 | @UIApplicationMain 11 | class AppDelegate: UIResponder, UIApplicationDelegate { 12 | 13 | 14 | 15 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 16 | // Override point for customization after application launch. 17 | return true 18 | } 19 | 20 | // MARK: UISceneSession Lifecycle 21 | 22 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 23 | // Called when a new scene session is being created. 24 | // Use this method to select a configuration to create the new scene with. 25 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 26 | } 27 | 28 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 29 | // Called when the user discards a scene session. 30 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 31 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 32 | } 33 | 34 | 35 | } 36 | 37 | -------------------------------------------------------------------------------- /ImageColoriser/Sources/ImageColorizer/ImageColorizer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageColoriser 3 | // 4 | // Created by Maksym Shcheglov. 5 | // Copyright © 2021 Maksym Shcheglov. All rights reserved. 6 | // 7 | 8 | import UIKit 9 | import CoreML 10 | 11 | final class ImageColorizer { 12 | 13 | func colorize(image inputImage: UIImage, completion: @escaping (Result) -> Void) { 14 | DispatchQueue.background.async { 15 | let result = self.colorize(image: inputImage) 16 | DispatchQueue.main.async { completion(result) } 17 | } 18 | } 19 | } 20 | 21 | // MARK: Private 22 | 23 | private struct LAB { 24 | let l, a, b: [NSNumber] 25 | } 26 | 27 | private enum ColorizerError: Swift.Error { 28 | case preprocessFailure 29 | case postprocessFailure 30 | } 31 | 32 | extension ImageColorizer { 33 | 34 | struct Constants { 35 | static let inputDimension = 256 36 | static let inputSize = CGSize(width: inputDimension, height: inputDimension) 37 | static let coremlInputShape = [1, 1, NSNumber(value: Constants.inputDimension), NSNumber(value: Constants.inputDimension)] 38 | } 39 | 40 | private func colorize(image inputImage: UIImage) -> Result { 41 | do { 42 | let inputImageLab = try preProcess(inputImage: inputImage) 43 | let input = try coloriserInput(from: inputImageLab) 44 | let output = try coremlColorizer(configuration: MLModelConfiguration()).prediction(input: input) 45 | let outputImageLab = imageLab(from: output, inputImageLab: inputImageLab) 46 | let resultImage = try postProcess(outputLAB: outputImageLab, inputImage: inputImage) 47 | return .success(resultImage) 48 | } catch { 49 | return .failure(error) 50 | } 51 | } 52 | 53 | private func coloriserInput(from imageLab: LAB) throws -> coremlColorizerInput { 54 | let inputArray = try MLMultiArray(shape: Constants.coremlInputShape, dataType: MLMultiArrayDataType.float32) 55 | imageLab.l.enumerated().forEach({ (idx, value) in 56 | let inputIndex = [NSNumber(value: 0), NSNumber(value: 0), NSNumber(value: idx / Constants.inputDimension), NSNumber(value: idx % Constants.inputDimension)] 57 | inputArray[inputIndex] = value 58 | }) 59 | return coremlColorizerInput(input1: inputArray) 60 | } 61 | 62 | private func imageLab(from colorizerOutput: coremlColorizerOutput, inputImageLab: LAB) -> LAB { 63 | var a = [NSNumber]() 64 | var b = [NSNumber]() 65 | for idx in 0.. LAB { 76 | guard let lab = inputImage.resizedImage(with: Constants.inputSize)?.toLab() else { 77 | throw ColorizerError.preprocessFailure 78 | } 79 | return LAB(l: lab[0], a: lab[1], b: lab[2]) 80 | } 81 | 82 | // Post-process output: resize to original & create a new one using Lightness component from the input image 83 | private func postProcess(outputLAB: LAB, inputImage: UIImage) throws -> UIImage { 84 | guard let resultImageLab = UIImage.image(from: [outputLAB.l, outputLAB.a, outputLAB.b], size: Constants.inputSize).resizedImage(with: inputImage.size)?.toLab(), 85 | let originalImageLab = inputImage.resizedImage(with: inputImage.size)?.toLab() else { 86 | throw ColorizerError.postprocessFailure 87 | } 88 | return UIImage.image(from: [originalImageLab[0], resultImageLab[1], resultImageLab[2]], size: inputImage.size) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /ImageColoriser/Sources/ImageColorizer/ImagePicker.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageColoriser 3 | // 4 | // Created by Maksym Shcheglov. 5 | // Copyright © 2021 Maksym Shcheglov. All rights reserved. 6 | // 7 | 8 | import UIKit 9 | import PhotosUI 10 | 11 | final class ImagePicker: ImageProvider { 12 | private var completion: Completion? 13 | 14 | func image(with completion: @escaping Completion) { 15 | guard self.completion == nil else { 16 | completion(.failure(ImageProviderError.startFailure)) 17 | return 18 | } 19 | self.completion = completion 20 | 21 | var configuration = PHPickerConfiguration() 22 | configuration.filter = .images 23 | configuration.selectionLimit = 1 24 | 25 | let picker = PHPickerViewController(configuration: configuration) 26 | picker.delegate = self 27 | UIViewController.topmostViewContoller.present(picker, animated: true) 28 | } 29 | } 30 | 31 | extension ImagePicker: PHPickerViewControllerDelegate { 32 | 33 | func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) { 34 | guard let itemProvider = results.first?.itemProvider else { 35 | self.completion = nil 36 | picker.dismiss(animated: true) 37 | return 38 | } 39 | image(from: itemProvider) { result in 40 | self.completion?(result) 41 | self.completion = nil 42 | picker.dismiss(animated: true) 43 | } 44 | } 45 | 46 | private func image(from itemProvider: NSItemProvider, completion: @escaping (Result) -> Void) { 47 | guard itemProvider.canLoadObject(ofClass: UIImage.self) else { 48 | completion(.failure(ImageProviderError.internalError)) 49 | return 50 | } 51 | 52 | itemProvider.loadObject(ofClass: UIImage.self) { image, error in 53 | if let image = image as? UIImage { 54 | DispatchQueue.main.async { completion(.success(image)) } 55 | } else { 56 | DispatchQueue.main.async { completion(.failure(ImageProviderError.internalError)) } 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /ImageColoriser/Sources/ImageColorizer/ImageProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageColoriser 3 | // 4 | // Created by Maksym Shcheglov. 5 | // Copyright © 2021 Maksym Shcheglov. All rights reserved. 6 | // 7 | 8 | import UIKit 9 | 10 | enum ImageProviderError: Error { 11 | case startFailure, internalError 12 | } 13 | 14 | protocol ImageProvider { 15 | typealias Completion = (Result) -> Void 16 | func image(with completion: @escaping Completion) 17 | } 18 | -------------------------------------------------------------------------------- /ImageColoriser/Sources/ImageColorizer/ImageScanner.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageColoriser 3 | // 4 | // Created by Maksym Shcheglov. 5 | // Copyright © 2021 Maksym Shcheglov. All rights reserved. 6 | // 7 | 8 | import UIKit 9 | import Vision 10 | 11 | final class ImageScanner: NSObject, ImageProvider { 12 | private var completion: Completion? 13 | 14 | func image(with completion: @escaping Completion) { 15 | guard self.completion == nil, UIImagePickerController.isSourceTypeAvailable(.camera) else { 16 | completion(.failure(ImageProviderError.startFailure)) 17 | return 18 | } 19 | self.completion = completion 20 | 21 | let pickerController = UIImagePickerController() 22 | pickerController.delegate = self 23 | pickerController.sourceType = .camera 24 | UIViewController.topmostViewContoller.present(pickerController, animated: true) 25 | } 26 | } 27 | 28 | extension ImageScanner: UIImagePickerControllerDelegate, UINavigationControllerDelegate { 29 | 30 | func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { 31 | guard let uiImage = info[UIImagePickerController.InfoKey.originalImage] as? UIImage else { 32 | dismissController(picker, with: .failure(ImageProviderError.internalError)) 33 | return 34 | } 35 | 36 | DispatchQueue.background.async { 37 | let result = self.postProcessImage(uiImage) 38 | self.dismissController(picker, with: result) 39 | } 40 | } 41 | 42 | private func postProcessImage(_ image: UIImage) -> Result { 43 | guard let ciImage = CIImage(image: image), 44 | let orientation = CGImagePropertyOrientation(rawValue: UInt32(image.imageOrientation.rawValue)) else { 45 | return .failure(ImageProviderError.internalError) 46 | } 47 | let inputImage = ciImage.oriented(forExifOrientation: Int32(orientation.rawValue)) 48 | return detectRectangle(on: ciImage, orientation: orientation).flatMap {detectedRectangle in 49 | self.cropImage(inputImage, with: detectedRectangle) 50 | } 51 | } 52 | 53 | private func detectRectangle(on image: CIImage, orientation: CGImagePropertyOrientation) -> Result { 54 | var result: Result = .failure(ImageProviderError.internalError) 55 | let semaphore = DispatchSemaphore(value: 1) 56 | let rectanglesRequest = VNDetectRectanglesRequest { request, error in 57 | guard error == nil, 58 | let observations = request.results as? [VNRectangleObservation], 59 | let detectedRectangle = observations.first else { 60 | return 61 | } 62 | result = .success(detectedRectangle) 63 | semaphore.signal() 64 | } 65 | let handler = VNImageRequestHandler(ciImage: image, orientation: orientation) 66 | if (try? handler.perform([rectanglesRequest])) != nil { semaphore.wait() } 67 | return result 68 | } 69 | 70 | private func cropImage(_ inputImage: CIImage, with detectedRectangle: VNRectangleObservation) -> Result { 71 | let imageSize = inputImage.extent.size 72 | let transform = CGAffineTransform.identity.scaledBy(x: imageSize.width, y: imageSize.height) 73 | let boundingBox = detectedRectangle.boundingBox.applying(transform) 74 | guard inputImage.extent.contains(boundingBox) else { 75 | return .failure(ImageProviderError.internalError) 76 | } 77 | 78 | let topLeft = detectedRectangle.topLeft.applying(transform) 79 | let topRight = detectedRectangle.topRight.applying(transform) 80 | let bottomLeft = detectedRectangle.bottomLeft.applying(transform) 81 | let bottomRight = detectedRectangle.bottomRight.applying(transform) 82 | let correctedImage = inputImage.cropped(to: boundingBox) 83 | .applyingFilter("CIPerspectiveCorrection", parameters: [ 84 | "inputTopLeft": CIVector(cgPoint: topLeft), 85 | "inputTopRight": CIVector(cgPoint: topRight), 86 | "inputBottomLeft": CIVector(cgPoint: bottomLeft), 87 | "inputBottomRight": CIVector(cgPoint: bottomRight) 88 | ]) 89 | .transformed(by: CGAffineTransform.identity.rotated(by: 90 / 180.0 * CGFloat.pi)) 90 | 91 | let imageRef = CIContext().createCGImage(correctedImage, from: correctedImage.extent)! 92 | return .success(UIImage(cgImage: imageRef)) 93 | } 94 | 95 | private func dismissController(_ controller: UIViewController, with result: Result) { 96 | DispatchQueue.main.async { 97 | controller.dismiss(animated: true) { [weak self] in 98 | guard let self = self else { return } 99 | self.completion?(result) 100 | self.completion = nil 101 | } 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /ImageColoriser/Sources/ImageColorizer/coremlColorizer.mlmodel: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:00b95f71aab751eb8002e8f4347b276052f311d6040d7a41e1823dc6730a6bb3 3 | size 136232839 4 | -------------------------------------------------------------------------------- /ImageColoriser/Sources/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageColoriser 3 | // 4 | // Created by Maksym Shcheglov. 5 | // Copyright © 2021 Maksym Shcheglov. All rights reserved. 6 | // 7 | 8 | import UIKit 9 | 10 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 11 | 12 | var window: UIWindow? 13 | 14 | 15 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 16 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 17 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 18 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 19 | guard let _ = (scene as? UIWindowScene) else { return } 20 | } 21 | 22 | func sceneDidDisconnect(_ scene: UIScene) { 23 | // Called as the scene is being released by the system. 24 | // This occurs shortly after the scene enters the background, or when its session is discarded. 25 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 26 | // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead). 27 | } 28 | 29 | func sceneDidBecomeActive(_ scene: UIScene) { 30 | // Called when the scene has moved from an inactive state to an active state. 31 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 32 | } 33 | 34 | func sceneWillResignActive(_ scene: UIScene) { 35 | // Called when the scene will move from an active state to an inactive state. 36 | // This may occur due to temporary interruptions (ex. an incoming phone call). 37 | } 38 | 39 | func sceneWillEnterForeground(_ scene: UIScene) { 40 | // Called as the scene transitions from the background to the foreground. 41 | // Use this method to undo the changes made on entering the background. 42 | } 43 | 44 | func sceneDidEnterBackground(_ scene: UIScene) { 45 | // Called as the scene transitions from the foreground to the background. 46 | // Use this method to save data, release shared resources, and store enough scene-specific state information 47 | // to restore the scene back to its current state. 48 | } 49 | 50 | 51 | } 52 | 53 | -------------------------------------------------------------------------------- /ImageColoriser/Sources/Utils/DispatchQueue+Utils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageColoriser 3 | // 4 | // Created by Maksym Shcheglov. 5 | // Copyright © 2021 Maksym Shcheglov. All rights reserved. 6 | // 7 | 8 | import Foundation 9 | 10 | extension DispatchQueue { 11 | 12 | static var background = DispatchQueue(label: "com.sgl0v.colorizer", qos: .userInitiated) 13 | 14 | } 15 | -------------------------------------------------------------------------------- /ImageColoriser/Sources/Utils/ImageColoriser-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | #import "UIImage+Lab.h" 6 | -------------------------------------------------------------------------------- /ImageColoriser/Sources/Utils/UIImage+Lab.h: -------------------------------------------------------------------------------- 1 | // 2 | // ImageColoriser 3 | // 4 | // Created by Maksym Shcheglov. 5 | // Copyright © 2021 Maksym Shcheglov. All rights reserved. 6 | // 7 | 8 | #ifndef UIImage_Lab_h 9 | #define UIImage_Lab_h 10 | 11 | #import 12 | 13 | NS_ASSUME_NONNULL_BEGIN 14 | 15 | @interface UIImage(Lab) 16 | 17 | - (NSArray*>*)toLab; 18 | + (UIImage*)imageFromLab:(NSArray*>*)lab size:(CGSize)size NS_SWIFT_NAME(image(from:size:)); 19 | 20 | @end 21 | NS_ASSUME_NONNULL_END 22 | 23 | #endif /* UIImage_Lab_h */ 24 | -------------------------------------------------------------------------------- /ImageColoriser/Sources/Utils/UIImage+Lab.mm: -------------------------------------------------------------------------------- 1 | // 2 | // ImageColoriser 3 | // 4 | // Created by Maksym Shcheglov. 5 | // Copyright © 2021 Maksym Shcheglov. All rights reserved. 6 | // 7 | 8 | #import 9 | #import "UIImage+Lab.h" 10 | #include "lcms2.h" 11 | 12 | struct Rgb { 13 | float r, g, b; 14 | Rgb() {} 15 | Rgb(float r, float g, float b) : r(r), g(g), b(b) {} 16 | }; 17 | 18 | struct Lab { 19 | float l, a, b; 20 | Lab() {} 21 | Lab(float l, float a, float b) : l(l), a(a), b(b) {} 22 | }; 23 | 24 | Lab &rgb2lab(cmsHTRANSFORM &xform, Rgb &rgbColor) { 25 | Lab labColor; 26 | float rgbValues[3]; 27 | float labValues[3]; 28 | rgbValues[0] = rgbColor.r / 255.0; 29 | rgbValues[1] = rgbColor.g / 255.0; 30 | rgbValues[2] = rgbColor.b / 255.0; 31 | cmsDoTransform(xform, rgbValues, labValues, 1); 32 | Lab lab = Lab(labValues[0], labValues[1], labValues[2]); 33 | return lab; 34 | } 35 | 36 | Rgb &lab2rgb(cmsHTRANSFORM &xform, Lab &labColor) { 37 | float rgbValues[3]; 38 | float labValues[3]; 39 | labValues[0] = labColor.l; 40 | labValues[1] = labColor.a; 41 | labValues[2] = labColor.b; 42 | cmsDoTransform(xform, labValues, rgbValues, 1); 43 | Rgb rgb = Rgb(rgbValues[0] * 255.0, rgbValues[1] * 255.0, rgbValues[2] * 255.0); 44 | return rgb; 45 | } 46 | 47 | @implementation UIImage (Lab) 48 | 49 | + (cmsHTRANSFORM)lab2rgbTransform { 50 | static cmsHTRANSFORM lab2rgbTransform = nil; 51 | if (lab2rgbTransform == nil) { 52 | NSString *rgbProfilePath = [[NSBundle mainBundle] pathForResource:@"sRGB_ICC_v4_Appearance.icc" ofType:nil]; 53 | cmsHPROFILE rgbProfile = cmsOpenProfileFromFile([rgbProfilePath fileSystemRepresentation], "r"); 54 | cmsHPROFILE labProfile = cmsCreateLab4Profile(NULL); 55 | lab2rgbTransform = cmsCreateTransform(labProfile, TYPE_Lab_FLT, rgbProfile, TYPE_RGB_FLT, INTENT_PERCEPTUAL, 0); 56 | cmsCloseProfile(labProfile); 57 | cmsCloseProfile(rgbProfile); 58 | } 59 | return lab2rgbTransform; 60 | } 61 | 62 | + (cmsHTRANSFORM)rgb2labTransform { 63 | static cmsHTRANSFORM rgb2labTransform = nil; 64 | if (rgb2labTransform == nil) { 65 | NSString *rgbProfilePath = [[NSBundle mainBundle] pathForResource:@"sRGB_v4_ICC_preference.icc" ofType:nil]; 66 | cmsHPROFILE rgbProfile = cmsOpenProfileFromFile([rgbProfilePath fileSystemRepresentation], "r"); 67 | cmsHPROFILE labProfile = cmsCreateLab4Profile(NULL); 68 | rgb2labTransform = cmsCreateTransform(rgbProfile, TYPE_RGB_FLT, labProfile, TYPE_Lab_FLT, INTENT_PERCEPTUAL, 0); 69 | cmsCloseProfile(labProfile); 70 | cmsCloseProfile(rgbProfile); 71 | } 72 | return rgb2labTransform; 73 | } 74 | 75 | - (NSArray*>*)toLab { 76 | cmsHTRANSFORM xform = [UIImage rgb2labTransform]; 77 | CGImageRef imageRef = self.CGImage; 78 | NSData *data = (NSData *)CFBridgingRelease(CGDataProviderCopyData(CGImageGetDataProvider(imageRef))); 79 | unsigned char *pixels = (unsigned char *)[data bytes]; 80 | NSMutableArray* resL = [NSMutableArray new]; 81 | NSMutableArray* resA = [NSMutableArray new]; 82 | NSMutableArray* resB = [NSMutableArray new]; 83 | NSUInteger step = CGImageGetBitsPerPixel(imageRef) / 8; 84 | for(NSUInteger i = 0; i < [data length]; i += step) { 85 | Rgb rgb(pixels[i], pixels[i + 1], pixels[i + 2]); 86 | Lab lab = rgb2lab(xform, rgb); 87 | [resL addObject:@(lab.l)]; 88 | [resA addObject:@(lab.a)]; 89 | [resB addObject:@(lab.b)]; 90 | } 91 | return @[resL, resA, resB]; 92 | } 93 | 94 | + (UIImage*)imageFromLab:(NSArray*>*)lab size:(CGSize)size { 95 | cmsHTRANSFORM xform = [UIImage lab2rgbTransform]; 96 | 97 | NSInteger bufferSize = lab[0].count * 4; 98 | unsigned char *pixels = (unsigned char *)malloc(bufferSize); 99 | 100 | for(NSUInteger i = 0; i < size.width * size.height; i++) { 101 | Lab labValue(lab[0][i].doubleValue, lab[1][i].doubleValue, lab[2][i].doubleValue); 102 | Rgb rgb = lab2rgb(xform, labValue); 103 | 104 | NSUInteger offset = i * 4; 105 | pixels[offset] = rgb.r; 106 | pixels[offset + 1] = rgb.g; 107 | pixels[offset + 2] = rgb.b; 108 | pixels[offset + 3] = 255.0; 109 | } 110 | 111 | CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, pixels, bufferSize, NULL); 112 | CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB(); 113 | 114 | CGImageRef rgbImageRef = CGImageCreate(size.width, size.height, 8, 32, size.width * 4, colorspace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big, provider, NULL, false, kCGRenderingIntentDefault); 115 | 116 | UIImage *newImage = [UIImage imageWithCGImage:rgbImageRef]; 117 | 118 | CGDataProviderRelease(provider); 119 | CGColorSpaceRelease(colorspace); 120 | CGImageRelease(rgbImageRef); 121 | 122 | return newImage; 123 | } 124 | 125 | @end 126 | 127 | -------------------------------------------------------------------------------- /ImageColoriser/Sources/Utils/UIImage+Utils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageColoriser 3 | // 4 | // Created by Maksym Shcheglov. 5 | // Copyright © 2021 Maksym Shcheglov. All rights reserved. 6 | // 7 | 8 | import UIKit 9 | 10 | extension UIImage { 11 | 12 | func scaled(with dimension: CGFloat) -> UIImage { 13 | let width = self.size.width 14 | let height = self.size.height 15 | let aspectWidth = dimension / width 16 | let aspectHeight = dimension / height 17 | let scaleFactor = max(aspectWidth, aspectHeight) 18 | let size = CGSize(width: CGFloat(round(width * scaleFactor)), height: CGFloat(round(height * scaleFactor))) 19 | 20 | let renderer = UIGraphicsImageRenderer(size: size) 21 | return renderer.image { _ in 22 | draw(in: CGRect(origin: .zero, size: size)) 23 | } 24 | } 25 | 26 | func resizedImage(with size: CGSize) -> UIImage? { 27 | guard let image = cgImage else { return nil } 28 | 29 | if (image.colorSpace?.model == .rgb) { 30 | let bytesPerPixel = 4; 31 | let bytesPerRow = bytesPerPixel * Int(size.width); 32 | let bitsPerComponent = 8; 33 | let context = CGContext(data: nil, 34 | width: Int(size.width), 35 | height: Int(size.height), 36 | bitsPerComponent: bitsPerComponent, 37 | bytesPerRow: bytesPerRow, 38 | space: image.colorSpace!, 39 | bitmapInfo: image.bitmapInfo.rawValue) 40 | context?.interpolationQuality = .high 41 | context?.draw(image, in: CGRect(origin: .zero, size: size)) 42 | 43 | guard let scaledImage = context?.makeImage() else { return nil } 44 | 45 | return UIImage(cgImage: scaledImage) 46 | } else if (image.colorSpace?.model == .monochrome) { 47 | let context = CGContext(data: nil, 48 | width: Int(size.width), 49 | height: Int(size.height), 50 | bitsPerComponent: image.bitsPerComponent, 51 | bytesPerRow: Int(size.width), 52 | space: image.colorSpace!, 53 | bitmapInfo: image.bitmapInfo.rawValue) 54 | context?.interpolationQuality = .high 55 | context?.draw(image, in: CGRect(origin: .zero, size: size)) 56 | 57 | guard let scaledImage = context?.makeImage() else { return nil } 58 | 59 | return UIImage(cgImage: scaledImage) 60 | } else { 61 | assertionFailure("The colorspace is not supported yet!") 62 | return nil 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /ImageColoriser/Sources/Utils/UIViewController+Utils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageColoriser 3 | // 4 | // Created by Maksym Shcheglov. 5 | // Copyright © 2021 Maksym Shcheglov. All rights reserved. 6 | // 7 | 8 | import UIKit 9 | 10 | extension UIViewController { 11 | 12 | static var topmostViewContoller: UIViewController { 13 | var topController = UIApplication.shared.windows.filter({$0.isKeyWindow}).first!.rootViewController! 14 | while let presentedViewController = topController.presentedViewController { 15 | topController = presentedViewController 16 | } 17 | return topController 18 | } 19 | } 20 | 21 | -------------------------------------------------------------------------------- /ImageColoriser/Sources/Utils/littlecms/Makefile.am: -------------------------------------------------------------------------------- 1 | # 2 | # Makefile for building lcms 2 library 3 | # 4 | 5 | # Don't require all the GNU mandated files 6 | AUTOMAKE_OPTIONS = 1.7 foreign 7 | 8 | # CFLAGS = -pedantic -Wall -std=c99 -O3 9 | 10 | includedir = ${prefix}/include 11 | 12 | # Shared libraries built in this directory 13 | lib_LTLIBRARIES = liblcms2.la 14 | 15 | LIBRARY_CURRENT = @LIBRARY_CURRENT@ 16 | LIBRARY_REVISION = @LIBRARY_REVISION@ 17 | LIBRARY_AGE = @LIBRARY_AGE@ 18 | 19 | AM_CPPFLAGS = -I$(top_builddir)/include -I$(top_srcdir)/include 20 | 21 | liblcms2_la_LDFLAGS = -no-undefined \ 22 | -version-info $(LIBRARY_CURRENT):$(LIBRARY_REVISION):$(LIBRARY_AGE) 23 | 24 | liblcms2_la_LIBADD = $(LCMS_LIB_DEPLIBS) 25 | 26 | liblcms2_la_SOURCES = \ 27 | cmscnvrt.c cmserr.c cmsgamma.c cmsgmt.c cmsintrp.c cmsio0.c cmsio1.c cmslut.c \ 28 | cmsplugin.c cmssm.c cmsmd5.c cmsmtrx.c cmspack.c cmspcs.c cmswtpnt.c cmsxform.c \ 29 | cmssamp.c cmsnamed.c cmscam02.c cmsvirt.c cmstypes.c cmscgats.c cmsps2.c cmsopt.c \ 30 | cmshalf.c cmsalpha.c lcms2_internal.h 31 | 32 | -------------------------------------------------------------------------------- /ImageColoriser/Sources/Utils/littlecms/cmsalpha.c: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------------- 2 | // 3 | // Little Color Management System 4 | // Copyright (c) 1998-2020 Marti Maria Saguer 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining 7 | // a copy of this software and associated documentation files (the "Software"), 8 | // to deal in the Software without restriction, including without limitation 9 | // the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 | // and/or sell copies of the Software, and to permit persons to whom the Software 11 | // is furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 18 | // THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | // 24 | //--------------------------------------------------------------------------------- 25 | // 26 | 27 | #include "lcms2_internal.h" 28 | 29 | // Alpha copy ------------------------------------------------------------------------------------------------------------------ 30 | 31 | // This macro return words stored as big endian 32 | #define CHANGE_ENDIAN(w) (cmsUInt16Number) ((cmsUInt16Number) ((w)<<8)|((w)>>8)) 33 | 34 | 35 | // Floor to byte, taking care of saturation 36 | cmsINLINE cmsUInt8Number _cmsQuickSaturateByte(cmsFloat64Number d) 37 | { 38 | d += 0.5; 39 | if (d <= 0) return 0; 40 | if (d >= 255.0) return 255; 41 | 42 | return (cmsUInt8Number) _cmsQuickFloorWord(d); 43 | } 44 | 45 | 46 | // Return the size in bytes of a given formatter 47 | static 48 | cmsUInt32Number trueBytesSize(cmsUInt32Number Format) 49 | { 50 | cmsUInt32Number fmt_bytes = T_BYTES(Format); 51 | 52 | // For double, the T_BYTES field returns zero 53 | if (fmt_bytes == 0) 54 | return sizeof(double); 55 | 56 | // Otherwise, it is already correct for all formats 57 | return fmt_bytes; 58 | } 59 | 60 | 61 | // Several format converters 62 | 63 | typedef void(*cmsFormatterAlphaFn)(void* dst, const void* src); 64 | 65 | 66 | // From 8 67 | 68 | static 69 | void copy8(void* dst, const void* src) 70 | { 71 | memmove(dst, src, 1); 72 | } 73 | 74 | static 75 | void from8to16(void* dst, const void* src) 76 | { 77 | cmsUInt8Number n = *(cmsUInt8Number*)src; 78 | *(cmsUInt16Number*) dst = FROM_8_TO_16(n); 79 | } 80 | 81 | static 82 | void from8to16SE(void* dst, const void* src) 83 | { 84 | cmsUInt8Number n = *(cmsUInt8Number*)src; 85 | *(cmsUInt16Number*)dst = CHANGE_ENDIAN(FROM_8_TO_16(n)); 86 | } 87 | 88 | static 89 | void from8toFLT(void* dst, const void* src) 90 | { 91 | *(cmsFloat32Number*)dst = (*(cmsUInt8Number*)src) / 255.0f; 92 | } 93 | 94 | static 95 | void from8toDBL(void* dst, const void* src) 96 | { 97 | *(cmsFloat64Number*)dst = (*(cmsUInt8Number*)src) / 255.0; 98 | } 99 | 100 | static 101 | void from8toHLF(void* dst, const void* src) 102 | { 103 | #ifndef CMS_NO_HALF_SUPPORT 104 | cmsFloat32Number n = (*(cmsUInt8Number*)src) / 255.0f; 105 | *(cmsUInt16Number*)dst = _cmsFloat2Half(n); 106 | #else 107 | cmsUNUSED_PARAMETER(dst); 108 | cmsUNUSED_PARAMETER(src); 109 | #endif 110 | } 111 | 112 | // From 16 113 | 114 | static 115 | void from16to8(void* dst, const void* src) 116 | { 117 | cmsUInt16Number n = *(cmsUInt16Number*)src; 118 | *(cmsUInt8Number*) dst = FROM_16_TO_8(n); 119 | } 120 | 121 | static 122 | void from16SEto8(void* dst, const void* src) 123 | { 124 | cmsUInt16Number n = *(cmsUInt16Number*)src; 125 | *(cmsUInt8Number*)dst = FROM_16_TO_8(CHANGE_ENDIAN(n)); 126 | } 127 | 128 | static 129 | void copy16(void* dst, const void* src) 130 | { 131 | memmove(dst, src, 2); 132 | } 133 | 134 | static 135 | void from16to16(void* dst, const void* src) 136 | { 137 | cmsUInt16Number n = *(cmsUInt16Number*)src; 138 | *(cmsUInt16Number*)dst = CHANGE_ENDIAN(n); 139 | } 140 | 141 | static 142 | void from16toFLT(void* dst, const void* src) 143 | { 144 | *(cmsFloat32Number*)dst = (*(cmsUInt16Number*)src) / 65535.0f; 145 | } 146 | 147 | static 148 | void from16SEtoFLT(void* dst, const void* src) 149 | { 150 | *(cmsFloat32Number*)dst = (CHANGE_ENDIAN(*(cmsUInt16Number*)src)) / 65535.0f; 151 | } 152 | 153 | static 154 | void from16toDBL(void* dst, const void* src) 155 | { 156 | *(cmsFloat64Number*)dst = (*(cmsUInt16Number*)src) / 65535.0f; 157 | } 158 | 159 | static 160 | void from16SEtoDBL(void* dst, const void* src) 161 | { 162 | *(cmsFloat64Number*)dst = (CHANGE_ENDIAN(*(cmsUInt16Number*)src)) / 65535.0f; 163 | } 164 | 165 | static 166 | void from16toHLF(void* dst, const void* src) 167 | { 168 | #ifndef CMS_NO_HALF_SUPPORT 169 | cmsFloat32Number n = (*(cmsUInt16Number*)src) / 65535.0f; 170 | *(cmsUInt16Number*)dst = _cmsFloat2Half(n); 171 | #else 172 | cmsUNUSED_PARAMETER(dst); 173 | cmsUNUSED_PARAMETER(src); 174 | #endif 175 | } 176 | 177 | static 178 | void from16SEtoHLF(void* dst, const void* src) 179 | { 180 | #ifndef CMS_NO_HALF_SUPPORT 181 | cmsFloat32Number n = (CHANGE_ENDIAN(*(cmsUInt16Number*)src)) / 65535.0f; 182 | *(cmsUInt16Number*)dst = _cmsFloat2Half(n); 183 | #else 184 | cmsUNUSED_PARAMETER(dst); 185 | cmsUNUSED_PARAMETER(src); 186 | #endif 187 | } 188 | // From Float 189 | 190 | static 191 | void fromFLTto8(void* dst, const void* src) 192 | { 193 | cmsFloat32Number n = *(cmsFloat32Number*)src; 194 | *(cmsUInt8Number*)dst = _cmsQuickSaturateByte(n * 255.0f); 195 | } 196 | 197 | static 198 | void fromFLTto16(void* dst, const void* src) 199 | { 200 | cmsFloat32Number n = *(cmsFloat32Number*)src; 201 | *(cmsUInt16Number*)dst = _cmsQuickSaturateWord(n * 65535.0f); 202 | } 203 | 204 | static 205 | void fromFLTto16SE(void* dst, const void* src) 206 | { 207 | cmsFloat32Number n = *(cmsFloat32Number*)src; 208 | cmsUInt16Number i = _cmsQuickSaturateWord(n * 65535.0f); 209 | 210 | *(cmsUInt16Number*)dst = CHANGE_ENDIAN(i); 211 | } 212 | 213 | static 214 | void copy32(void* dst, const void* src) 215 | { 216 | memmove(dst, src, sizeof(cmsFloat32Number)); 217 | } 218 | 219 | static 220 | void fromFLTtoDBL(void* dst, const void* src) 221 | { 222 | cmsFloat32Number n = *(cmsFloat32Number*)src; 223 | *(cmsFloat64Number*)dst = (cmsFloat64Number)n; 224 | } 225 | 226 | static 227 | void fromFLTtoHLF(void* dst, const void* src) 228 | { 229 | #ifndef CMS_NO_HALF_SUPPORT 230 | cmsFloat32Number n = *(cmsFloat32Number*)src; 231 | *(cmsUInt16Number*)dst = _cmsFloat2Half(n); 232 | #else 233 | cmsUNUSED_PARAMETER(dst); 234 | cmsUNUSED_PARAMETER(src); 235 | #endif 236 | } 237 | 238 | 239 | // From HALF 240 | 241 | static 242 | void fromHLFto8(void* dst, const void* src) 243 | { 244 | #ifndef CMS_NO_HALF_SUPPORT 245 | cmsFloat32Number n = _cmsHalf2Float(*(cmsUInt16Number*)src); 246 | *(cmsUInt8Number*)dst = _cmsQuickSaturateByte(n * 255.0f); 247 | #else 248 | cmsUNUSED_PARAMETER(dst); 249 | cmsUNUSED_PARAMETER(src); 250 | #endif 251 | 252 | } 253 | 254 | static 255 | void fromHLFto16(void* dst, const void* src) 256 | { 257 | #ifndef CMS_NO_HALF_SUPPORT 258 | cmsFloat32Number n = _cmsHalf2Float(*(cmsUInt16Number*)src); 259 | *(cmsUInt16Number*)dst = _cmsQuickSaturateWord(n * 65535.0f); 260 | #else 261 | cmsUNUSED_PARAMETER(dst); 262 | cmsUNUSED_PARAMETER(src); 263 | #endif 264 | } 265 | 266 | static 267 | void fromHLFto16SE(void* dst, const void* src) 268 | { 269 | #ifndef CMS_NO_HALF_SUPPORT 270 | cmsFloat32Number n = _cmsHalf2Float(*(cmsUInt16Number*)src); 271 | cmsUInt16Number i = _cmsQuickSaturateWord(n * 65535.0f); 272 | *(cmsUInt16Number*)dst = CHANGE_ENDIAN(i); 273 | #else 274 | cmsUNUSED_PARAMETER(dst); 275 | cmsUNUSED_PARAMETER(src); 276 | #endif 277 | } 278 | 279 | static 280 | void fromHLFtoFLT(void* dst, const void* src) 281 | { 282 | #ifndef CMS_NO_HALF_SUPPORT 283 | *(cmsFloat32Number*)dst = _cmsHalf2Float(*(cmsUInt16Number*)src); 284 | #else 285 | cmsUNUSED_PARAMETER(dst); 286 | cmsUNUSED_PARAMETER(src); 287 | #endif 288 | } 289 | 290 | static 291 | void fromHLFtoDBL(void* dst, const void* src) 292 | { 293 | #ifndef CMS_NO_HALF_SUPPORT 294 | *(cmsFloat64Number*)dst = (cmsFloat64Number)_cmsHalf2Float(*(cmsUInt16Number*)src); 295 | #else 296 | cmsUNUSED_PARAMETER(dst); 297 | cmsUNUSED_PARAMETER(src); 298 | #endif 299 | } 300 | 301 | // From double 302 | static 303 | void fromDBLto8(void* dst, const void* src) 304 | { 305 | cmsFloat64Number n = *(cmsFloat64Number*)src; 306 | *(cmsUInt8Number*)dst = _cmsQuickSaturateByte(n * 255.0); 307 | } 308 | 309 | static 310 | void fromDBLto16(void* dst, const void* src) 311 | { 312 | cmsFloat64Number n = *(cmsFloat64Number*)src; 313 | *(cmsUInt16Number*)dst = _cmsQuickSaturateWord(n * 65535.0f); 314 | } 315 | 316 | static 317 | void fromDBLto16SE(void* dst, const void* src) 318 | { 319 | cmsFloat64Number n = *(cmsFloat64Number*)src; 320 | cmsUInt16Number i = _cmsQuickSaturateWord(n * 65535.0f); 321 | *(cmsUInt16Number*)dst = CHANGE_ENDIAN(i); 322 | } 323 | 324 | static 325 | void fromDBLtoFLT(void* dst, const void* src) 326 | { 327 | cmsFloat64Number n = *(cmsFloat64Number*)src; 328 | *(cmsFloat32Number*)dst = (cmsFloat32Number) n; 329 | } 330 | 331 | static 332 | void fromDBLtoHLF(void* dst, const void* src) 333 | { 334 | #ifndef CMS_NO_HALF_SUPPORT 335 | cmsFloat32Number n = (cmsFloat32Number) *(cmsFloat64Number*)src; 336 | *(cmsUInt16Number*)dst = _cmsFloat2Half(n); 337 | #else 338 | cmsUNUSED_PARAMETER(dst); 339 | cmsUNUSED_PARAMETER(src); 340 | #endif 341 | } 342 | 343 | static 344 | void copy64(void* dst, const void* src) 345 | { 346 | memmove(dst, src, sizeof(cmsFloat64Number)); 347 | } 348 | 349 | 350 | // Returns the position (x or y) of the formatter in the table of functions 351 | static 352 | int FormatterPos(cmsUInt32Number frm) 353 | { 354 | cmsUInt32Number b = T_BYTES(frm); 355 | 356 | if (b == 0 && T_FLOAT(frm)) 357 | return 5; // DBL 358 | #ifndef CMS_NO_HALF_SUPPORT 359 | if (b == 2 && T_FLOAT(frm)) 360 | return 3; // HLF 361 | #endif 362 | if (b == 4 && T_FLOAT(frm)) 363 | return 4; // FLT 364 | if (b == 2 && !T_FLOAT(frm)) 365 | { 366 | if (T_ENDIAN16(frm)) 367 | return 2; // 16SE 368 | else 369 | return 1; // 16 370 | } 371 | if (b == 1 && !T_FLOAT(frm)) 372 | return 0; // 8 373 | return -1; // not recognized 374 | } 375 | 376 | // Obtains an alpha-to-alpha function formatter 377 | static 378 | cmsFormatterAlphaFn _cmsGetFormatterAlpha(cmsContext id, cmsUInt32Number in, cmsUInt32Number out) 379 | { 380 | static cmsFormatterAlphaFn FormattersAlpha[6][6] = { 381 | 382 | /* from 8 */ { copy8, from8to16, from8to16SE, from8toHLF, from8toFLT, from8toDBL }, 383 | /* from 16*/ { from16to8, copy16, from16to16, from16toHLF, from16toFLT, from16toDBL }, 384 | /* from 16SE*/{ from16SEto8, from16to16, copy16, from16SEtoHLF,from16SEtoFLT, from16SEtoDBL }, 385 | /* from HLF*/ { fromHLFto8, fromHLFto16, fromHLFto16SE, copy16, fromHLFtoFLT, fromHLFtoDBL }, 386 | /* from FLT*/ { fromFLTto8, fromFLTto16, fromFLTto16SE, fromFLTtoHLF, copy32, fromFLTtoDBL }, 387 | /* from DBL*/ { fromDBLto8, fromDBLto16, fromDBLto16SE, fromDBLtoHLF, fromDBLtoFLT, copy64 }}; 388 | 389 | int in_n = FormatterPos(in); 390 | int out_n = FormatterPos(out); 391 | 392 | if (in_n < 0 || out_n < 0 || in_n > 5 || out_n > 5) { 393 | 394 | cmsSignalError(id, cmsERROR_UNKNOWN_EXTENSION, "Unrecognized alpha channel width"); 395 | return NULL; 396 | } 397 | 398 | return FormattersAlpha[in_n][out_n]; 399 | } 400 | 401 | 402 | 403 | // This function computes the distance from each component to the next one in bytes. 404 | static 405 | void ComputeIncrementsForChunky(cmsUInt32Number Format, 406 | cmsUInt32Number ComponentStartingOrder[], 407 | cmsUInt32Number ComponentPointerIncrements[]) 408 | { 409 | cmsUInt32Number channels[cmsMAXCHANNELS]; 410 | cmsUInt32Number extra = T_EXTRA(Format); 411 | cmsUInt32Number nchannels = T_CHANNELS(Format); 412 | cmsUInt32Number total_chans = nchannels + extra; 413 | cmsUInt32Number i; 414 | cmsUInt32Number channelSize = trueBytesSize(Format); 415 | cmsUInt32Number pixelSize = channelSize * total_chans; 416 | 417 | // Sanity check 418 | if (total_chans <= 0 || total_chans >= cmsMAXCHANNELS) 419 | return; 420 | 421 | memset(channels, 0, sizeof(channels)); 422 | 423 | // Separation is independent of starting point and only depends on channel size 424 | for (i = 0; i < extra; i++) 425 | ComponentPointerIncrements[i] = pixelSize; 426 | 427 | // Handle do swap 428 | for (i = 0; i < total_chans; i++) 429 | { 430 | if (T_DOSWAP(Format)) { 431 | channels[i] = total_chans - i - 1; 432 | } 433 | else { 434 | channels[i] = i; 435 | } 436 | } 437 | 438 | // Handle swap first (ROL of positions), example CMYK -> KCMY | 0123 -> 3012 439 | if (T_SWAPFIRST(Format) && total_chans > 1) { 440 | 441 | cmsUInt32Number tmp = channels[0]; 442 | for (i = 0; i < total_chans-1; i++) 443 | channels[i] = channels[i + 1]; 444 | 445 | channels[total_chans - 1] = tmp; 446 | } 447 | 448 | // Handle size 449 | if (channelSize > 1) 450 | for (i = 0; i < total_chans; i++) { 451 | channels[i] *= channelSize; 452 | } 453 | 454 | for (i = 0; i < extra; i++) 455 | ComponentStartingOrder[i] = channels[i + nchannels]; 456 | } 457 | 458 | 459 | 460 | // On planar configurations, the distance is the stride added to any non-negative 461 | static 462 | void ComputeIncrementsForPlanar(cmsUInt32Number Format, 463 | cmsUInt32Number BytesPerPlane, 464 | cmsUInt32Number ComponentStartingOrder[], 465 | cmsUInt32Number ComponentPointerIncrements[]) 466 | { 467 | cmsUInt32Number channels[cmsMAXCHANNELS]; 468 | cmsUInt32Number extra = T_EXTRA(Format); 469 | cmsUInt32Number nchannels = T_CHANNELS(Format); 470 | cmsUInt32Number total_chans = nchannels + extra; 471 | cmsUInt32Number i; 472 | cmsUInt32Number channelSize = trueBytesSize(Format); 473 | 474 | // Sanity check 475 | if (total_chans <= 0 || total_chans >= cmsMAXCHANNELS) 476 | return; 477 | 478 | memset(channels, 0, sizeof(channels)); 479 | 480 | // Separation is independent of starting point and only depends on channel size 481 | for (i = 0; i < extra; i++) 482 | ComponentPointerIncrements[i] = channelSize; 483 | 484 | // Handle do swap 485 | for (i = 0; i < total_chans; i++) 486 | { 487 | if (T_DOSWAP(Format)) { 488 | channels[i] = total_chans - i - 1; 489 | } 490 | else { 491 | channels[i] = i; 492 | } 493 | } 494 | 495 | // Handle swap first (ROL of positions), example CMYK -> KCMY | 0123 -> 3012 496 | if (T_SWAPFIRST(Format) && total_chans > 0) { 497 | 498 | cmsUInt32Number tmp = channels[0]; 499 | for (i = 0; i < total_chans - 1; i++) 500 | channels[i] = channels[i + 1]; 501 | 502 | channels[total_chans - 1] = tmp; 503 | } 504 | 505 | // Handle size 506 | for (i = 0; i < total_chans; i++) { 507 | channels[i] *= BytesPerPlane; 508 | } 509 | 510 | for (i = 0; i < extra; i++) 511 | ComponentStartingOrder[i] = channels[i + nchannels]; 512 | } 513 | 514 | 515 | 516 | // Dispatcher por chunky and planar RGB 517 | static 518 | void ComputeComponentIncrements(cmsUInt32Number Format, 519 | cmsUInt32Number BytesPerPlane, 520 | cmsUInt32Number ComponentStartingOrder[], 521 | cmsUInt32Number ComponentPointerIncrements[]) 522 | { 523 | if (T_PLANAR(Format)) { 524 | 525 | ComputeIncrementsForPlanar(Format, BytesPerPlane, ComponentStartingOrder, ComponentPointerIncrements); 526 | } 527 | else { 528 | ComputeIncrementsForChunky(Format, ComponentStartingOrder, ComponentPointerIncrements); 529 | } 530 | 531 | } 532 | 533 | 534 | 535 | // Handles extra channels copying alpha if requested by the flags 536 | void _cmsHandleExtraChannels(_cmsTRANSFORM* p, const void* in, 537 | void* out, 538 | cmsUInt32Number PixelsPerLine, 539 | cmsUInt32Number LineCount, 540 | const cmsStride* Stride) 541 | { 542 | cmsUInt32Number i, j, k; 543 | cmsUInt32Number nExtra; 544 | cmsUInt32Number SourceStartingOrder[cmsMAXCHANNELS]; 545 | cmsUInt32Number SourceIncrements[cmsMAXCHANNELS]; 546 | cmsUInt32Number DestStartingOrder[cmsMAXCHANNELS]; 547 | cmsUInt32Number DestIncrements[cmsMAXCHANNELS]; 548 | 549 | cmsFormatterAlphaFn copyValueFn; 550 | 551 | // Make sure we need some copy 552 | if (!(p->dwOriginalFlags & cmsFLAGS_COPY_ALPHA)) 553 | return; 554 | 555 | // Exit early if in-place color-management is occurring - no need to copy extra channels to themselves. 556 | if (p->InputFormat == p->OutputFormat && in == out) 557 | return; 558 | 559 | // Make sure we have same number of alpha channels. If not, just return as this should be checked at transform creation time. 560 | nExtra = T_EXTRA(p->InputFormat); 561 | if (nExtra != T_EXTRA(p->OutputFormat)) 562 | return; 563 | 564 | // Anything to do? 565 | if (nExtra == 0) 566 | return; 567 | 568 | // Compute the increments 569 | ComputeComponentIncrements(p->InputFormat, Stride->BytesPerPlaneIn, SourceStartingOrder, SourceIncrements); 570 | ComputeComponentIncrements(p->OutputFormat, Stride->BytesPerPlaneOut, DestStartingOrder, DestIncrements); 571 | 572 | // Check for conversions 8, 16, half, float, dbl 573 | copyValueFn = _cmsGetFormatterAlpha(p->ContextID, p->InputFormat, p->OutputFormat); 574 | if (copyValueFn == NULL) 575 | return; 576 | 577 | if (nExtra == 1) { // Optimized routine for copying a single extra channel quickly 578 | 579 | cmsUInt8Number* SourcePtr; 580 | cmsUInt8Number* DestPtr; 581 | 582 | cmsUInt32Number SourceStrideIncrement = 0; 583 | cmsUInt32Number DestStrideIncrement = 0; 584 | 585 | // The loop itself 586 | for (i = 0; i < LineCount; i++) { 587 | 588 | // Prepare pointers for the loop 589 | SourcePtr = (cmsUInt8Number*)in + SourceStartingOrder[0] + SourceStrideIncrement; 590 | DestPtr = (cmsUInt8Number*)out + DestStartingOrder[0] + DestStrideIncrement; 591 | 592 | for (j = 0; j < PixelsPerLine; j++) { 593 | 594 | copyValueFn(DestPtr, SourcePtr); 595 | 596 | SourcePtr += SourceIncrements[0]; 597 | DestPtr += DestIncrements[0]; 598 | } 599 | 600 | SourceStrideIncrement += Stride->BytesPerLineIn; 601 | DestStrideIncrement += Stride->BytesPerLineOut; 602 | } 603 | 604 | } 605 | else { // General case with more than one extra channel 606 | 607 | cmsUInt8Number* SourcePtr[cmsMAXCHANNELS]; 608 | cmsUInt8Number* DestPtr[cmsMAXCHANNELS]; 609 | 610 | cmsUInt32Number SourceStrideIncrements[cmsMAXCHANNELS]; 611 | cmsUInt32Number DestStrideIncrements[cmsMAXCHANNELS]; 612 | 613 | memset(SourceStrideIncrements, 0, sizeof(SourceStrideIncrements)); 614 | memset(DestStrideIncrements, 0, sizeof(DestStrideIncrements)); 615 | 616 | // The loop itself 617 | for (i = 0; i < LineCount; i++) { 618 | 619 | // Prepare pointers for the loop 620 | for (j = 0; j < nExtra; j++) { 621 | 622 | SourcePtr[j] = (cmsUInt8Number*)in + SourceStartingOrder[j] + SourceStrideIncrements[j]; 623 | DestPtr[j] = (cmsUInt8Number*)out + DestStartingOrder[j] + DestStrideIncrements[j]; 624 | } 625 | 626 | for (j = 0; j < PixelsPerLine; j++) { 627 | 628 | for (k = 0; k < nExtra; k++) { 629 | 630 | copyValueFn(DestPtr[k], SourcePtr[k]); 631 | 632 | SourcePtr[k] += SourceIncrements[k]; 633 | DestPtr[k] += DestIncrements[k]; 634 | } 635 | } 636 | 637 | for (j = 0; j < nExtra; j++) { 638 | 639 | SourceStrideIncrements[j] += Stride->BytesPerLineIn; 640 | DestStrideIncrements[j] += Stride->BytesPerLineOut; 641 | } 642 | } 643 | } 644 | } 645 | 646 | 647 | -------------------------------------------------------------------------------- /ImageColoriser/Sources/Utils/littlecms/cmscam02.c: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------------- 2 | // 3 | // Little Color Management System 4 | // Copyright (c) 1998-2020 Marti Maria Saguer 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining 7 | // a copy of this software and associated documentation files (the "Software"), 8 | // to deal in the Software without restriction, including without limitation 9 | // the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 | // and/or sell copies of the Software, and to permit persons to whom the Software 11 | // is furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 18 | // THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | // 24 | //--------------------------------------------------------------------------------- 25 | // 26 | 27 | #include "lcms2_internal.h" 28 | 29 | // CIECAM 02 appearance model. Many thanks to Jordi Vilar for the debugging. 30 | 31 | // ---------- Implementation -------------------------------------------- 32 | 33 | typedef struct { 34 | 35 | cmsFloat64Number XYZ[3]; 36 | cmsFloat64Number RGB[3]; 37 | cmsFloat64Number RGBc[3]; 38 | cmsFloat64Number RGBp[3]; 39 | cmsFloat64Number RGBpa[3]; 40 | cmsFloat64Number a, b, h, e, H, A, J, Q, s, t, C, M; 41 | cmsFloat64Number abC[2]; 42 | cmsFloat64Number abs[2]; 43 | cmsFloat64Number abM[2]; 44 | 45 | } CAM02COLOR; 46 | 47 | typedef struct { 48 | 49 | CAM02COLOR adoptedWhite; 50 | cmsFloat64Number LA, Yb; 51 | cmsFloat64Number F, c, Nc; 52 | cmsUInt32Number surround; 53 | cmsFloat64Number n, Nbb, Ncb, z, FL, D; 54 | 55 | cmsContext ContextID; 56 | 57 | } cmsCIECAM02; 58 | 59 | 60 | static 61 | cmsFloat64Number compute_n(cmsCIECAM02* pMod) 62 | { 63 | return (pMod -> Yb / pMod -> adoptedWhite.XYZ[1]); 64 | } 65 | 66 | static 67 | cmsFloat64Number compute_z(cmsCIECAM02* pMod) 68 | { 69 | return (1.48 + pow(pMod -> n, 0.5)); 70 | } 71 | 72 | static 73 | cmsFloat64Number computeNbb(cmsCIECAM02* pMod) 74 | { 75 | return (0.725 * pow((1.0 / pMod -> n), 0.2)); 76 | } 77 | 78 | static 79 | cmsFloat64Number computeFL(cmsCIECAM02* pMod) 80 | { 81 | cmsFloat64Number k, FL; 82 | 83 | k = 1.0 / ((5.0 * pMod->LA) + 1.0); 84 | FL = 0.2 * pow(k, 4.0) * (5.0 * pMod->LA) + 0.1 * 85 | (pow((1.0 - pow(k, 4.0)), 2.0)) * 86 | (pow((5.0 * pMod->LA), (1.0 / 3.0))); 87 | 88 | return FL; 89 | } 90 | 91 | static 92 | cmsFloat64Number computeD(cmsCIECAM02* pMod) 93 | { 94 | cmsFloat64Number D; 95 | 96 | D = pMod->F - (1.0/3.6)*(exp(((-pMod ->LA-42) / 92.0))); 97 | 98 | return D; 99 | } 100 | 101 | 102 | static 103 | CAM02COLOR XYZtoCAT02(CAM02COLOR clr) 104 | { 105 | clr.RGB[0] = (clr.XYZ[0] * 0.7328) + (clr.XYZ[1] * 0.4296) + (clr.XYZ[2] * -0.1624); 106 | clr.RGB[1] = (clr.XYZ[0] * -0.7036) + (clr.XYZ[1] * 1.6975) + (clr.XYZ[2] * 0.0061); 107 | clr.RGB[2] = (clr.XYZ[0] * 0.0030) + (clr.XYZ[1] * 0.0136) + (clr.XYZ[2] * 0.9834); 108 | 109 | return clr; 110 | } 111 | 112 | static 113 | CAM02COLOR ChromaticAdaptation(CAM02COLOR clr, cmsCIECAM02* pMod) 114 | { 115 | cmsUInt32Number i; 116 | 117 | for (i = 0; i < 3; i++) { 118 | clr.RGBc[i] = ((pMod -> adoptedWhite.XYZ[1] * 119 | (pMod->D / pMod -> adoptedWhite.RGB[i])) + 120 | (1.0 - pMod->D)) * clr.RGB[i]; 121 | } 122 | 123 | return clr; 124 | } 125 | 126 | 127 | static 128 | CAM02COLOR CAT02toHPE(CAM02COLOR clr) 129 | { 130 | cmsFloat64Number M[9]; 131 | 132 | M[0] =(( 0.38971 * 1.096124) + (0.68898 * 0.454369) + (-0.07868 * -0.009628)); 133 | M[1] =(( 0.38971 * -0.278869) + (0.68898 * 0.473533) + (-0.07868 * -0.005698)); 134 | M[2] =(( 0.38971 * 0.182745) + (0.68898 * 0.072098) + (-0.07868 * 1.015326)); 135 | M[3] =((-0.22981 * 1.096124) + (1.18340 * 0.454369) + ( 0.04641 * -0.009628)); 136 | M[4] =((-0.22981 * -0.278869) + (1.18340 * 0.473533) + ( 0.04641 * -0.005698)); 137 | M[5] =((-0.22981 * 0.182745) + (1.18340 * 0.072098) + ( 0.04641 * 1.015326)); 138 | M[6] =(-0.009628); 139 | M[7] =(-0.005698); 140 | M[8] =( 1.015326); 141 | 142 | clr.RGBp[0] = (clr.RGBc[0] * M[0]) + (clr.RGBc[1] * M[1]) + (clr.RGBc[2] * M[2]); 143 | clr.RGBp[1] = (clr.RGBc[0] * M[3]) + (clr.RGBc[1] * M[4]) + (clr.RGBc[2] * M[5]); 144 | clr.RGBp[2] = (clr.RGBc[0] * M[6]) + (clr.RGBc[1] * M[7]) + (clr.RGBc[2] * M[8]); 145 | 146 | return clr; 147 | } 148 | 149 | static 150 | CAM02COLOR NonlinearCompression(CAM02COLOR clr, cmsCIECAM02* pMod) 151 | { 152 | cmsUInt32Number i; 153 | cmsFloat64Number temp; 154 | 155 | for (i = 0; i < 3; i++) { 156 | if (clr.RGBp[i] < 0) { 157 | 158 | temp = pow((-1.0 * pMod->FL * clr.RGBp[i] / 100.0), 0.42); 159 | clr.RGBpa[i] = (-1.0 * 400.0 * temp) / (temp + 27.13) + 0.1; 160 | } 161 | else { 162 | temp = pow((pMod->FL * clr.RGBp[i] / 100.0), 0.42); 163 | clr.RGBpa[i] = (400.0 * temp) / (temp + 27.13) + 0.1; 164 | } 165 | } 166 | 167 | clr.A = (((2.0 * clr.RGBpa[0]) + clr.RGBpa[1] + 168 | (clr.RGBpa[2] / 20.0)) - 0.305) * pMod->Nbb; 169 | 170 | return clr; 171 | } 172 | 173 | static 174 | CAM02COLOR ComputeCorrelates(CAM02COLOR clr, cmsCIECAM02* pMod) 175 | { 176 | cmsFloat64Number a, b, temp, e, t, r2d, d2r; 177 | 178 | a = clr.RGBpa[0] - (12.0 * clr.RGBpa[1] / 11.0) + (clr.RGBpa[2] / 11.0); 179 | b = (clr.RGBpa[0] + clr.RGBpa[1] - (2.0 * clr.RGBpa[2])) / 9.0; 180 | 181 | r2d = (180.0 / 3.141592654); 182 | if (a == 0) { 183 | if (b == 0) clr.h = 0; 184 | else if (b > 0) clr.h = 90; 185 | else clr.h = 270; 186 | } 187 | else if (a > 0) { 188 | temp = b / a; 189 | if (b > 0) clr.h = (r2d * atan(temp)); 190 | else if (b == 0) clr.h = 0; 191 | else clr.h = (r2d * atan(temp)) + 360; 192 | } 193 | else { 194 | temp = b / a; 195 | clr.h = (r2d * atan(temp)) + 180; 196 | } 197 | 198 | d2r = (3.141592654 / 180.0); 199 | e = ((12500.0 / 13.0) * pMod->Nc * pMod->Ncb) * 200 | (cos((clr.h * d2r + 2.0)) + 3.8); 201 | 202 | if (clr.h < 20.14) { 203 | temp = ((clr.h + 122.47)/1.2) + ((20.14 - clr.h)/0.8); 204 | clr.H = 300 + (100*((clr.h + 122.47)/1.2)) / temp; 205 | } 206 | else if (clr.h < 90.0) { 207 | temp = ((clr.h - 20.14)/0.8) + ((90.00 - clr.h)/0.7); 208 | clr.H = (100*((clr.h - 20.14)/0.8)) / temp; 209 | } 210 | else if (clr.h < 164.25) { 211 | temp = ((clr.h - 90.00)/0.7) + ((164.25 - clr.h)/1.0); 212 | clr.H = 100 + ((100*((clr.h - 90.00)/0.7)) / temp); 213 | } 214 | else if (clr.h < 237.53) { 215 | temp = ((clr.h - 164.25)/1.0) + ((237.53 - clr.h)/1.2); 216 | clr.H = 200 + ((100*((clr.h - 164.25)/1.0)) / temp); 217 | } 218 | else { 219 | temp = ((clr.h - 237.53)/1.2) + ((360 - clr.h + 20.14)/0.8); 220 | clr.H = 300 + ((100*((clr.h - 237.53)/1.2)) / temp); 221 | } 222 | 223 | clr.J = 100.0 * pow((clr.A / pMod->adoptedWhite.A), 224 | (pMod->c * pMod->z)); 225 | 226 | clr.Q = (4.0 / pMod->c) * pow((clr.J / 100.0), 0.5) * 227 | (pMod->adoptedWhite.A + 4.0) * pow(pMod->FL, 0.25); 228 | 229 | t = (e * pow(((a * a) + (b * b)), 0.5)) / 230 | (clr.RGBpa[0] + clr.RGBpa[1] + 231 | ((21.0 / 20.0) * clr.RGBpa[2])); 232 | 233 | clr.C = pow(t, 0.9) * pow((clr.J / 100.0), 0.5) * 234 | pow((1.64 - pow(0.29, pMod->n)), 0.73); 235 | 236 | clr.M = clr.C * pow(pMod->FL, 0.25); 237 | clr.s = 100.0 * pow((clr.M / clr.Q), 0.5); 238 | 239 | return clr; 240 | } 241 | 242 | 243 | static 244 | CAM02COLOR InverseCorrelates(CAM02COLOR clr, cmsCIECAM02* pMod) 245 | { 246 | 247 | cmsFloat64Number t, e, p1, p2, p3, p4, p5, hr, d2r; 248 | d2r = 3.141592654 / 180.0; 249 | 250 | t = pow( (clr.C / (pow((clr.J / 100.0), 0.5) * 251 | (pow((1.64 - pow(0.29, pMod->n)), 0.73)))), 252 | (1.0 / 0.9) ); 253 | e = ((12500.0 / 13.0) * pMod->Nc * pMod->Ncb) * 254 | (cos((clr.h * d2r + 2.0)) + 3.8); 255 | 256 | clr.A = pMod->adoptedWhite.A * pow( 257 | (clr.J / 100.0), 258 | (1.0 / (pMod->c * pMod->z))); 259 | 260 | p1 = e / t; 261 | p2 = (clr.A / pMod->Nbb) + 0.305; 262 | p3 = 21.0 / 20.0; 263 | 264 | hr = clr.h * d2r; 265 | 266 | if (fabs(sin(hr)) >= fabs(cos(hr))) { 267 | p4 = p1 / sin(hr); 268 | clr.b = (p2 * (2.0 + p3) * (460.0 / 1403.0)) / 269 | (p4 + (2.0 + p3) * (220.0 / 1403.0) * 270 | (cos(hr) / sin(hr)) - (27.0 / 1403.0) + 271 | p3 * (6300.0 / 1403.0)); 272 | clr.a = clr.b * (cos(hr) / sin(hr)); 273 | } 274 | else { 275 | p5 = p1 / cos(hr); 276 | clr.a = (p2 * (2.0 + p3) * (460.0 / 1403.0)) / 277 | (p5 + (2.0 + p3) * (220.0 / 1403.0) - 278 | ((27.0 / 1403.0) - p3 * (6300.0 / 1403.0)) * 279 | (sin(hr) / cos(hr))); 280 | clr.b = clr.a * (sin(hr) / cos(hr)); 281 | } 282 | 283 | clr.RGBpa[0] = ((460.0 / 1403.0) * p2) + 284 | ((451.0 / 1403.0) * clr.a) + 285 | ((288.0 / 1403.0) * clr.b); 286 | clr.RGBpa[1] = ((460.0 / 1403.0) * p2) - 287 | ((891.0 / 1403.0) * clr.a) - 288 | ((261.0 / 1403.0) * clr.b); 289 | clr.RGBpa[2] = ((460.0 / 1403.0) * p2) - 290 | ((220.0 / 1403.0) * clr.a) - 291 | ((6300.0 / 1403.0) * clr.b); 292 | 293 | return clr; 294 | } 295 | 296 | static 297 | CAM02COLOR InverseNonlinearity(CAM02COLOR clr, cmsCIECAM02* pMod) 298 | { 299 | cmsUInt32Number i; 300 | cmsFloat64Number c1; 301 | 302 | for (i = 0; i < 3; i++) { 303 | if ((clr.RGBpa[i] - 0.1) < 0) c1 = -1; 304 | else c1 = 1; 305 | clr.RGBp[i] = c1 * (100.0 / pMod->FL) * 306 | pow(((27.13 * fabs(clr.RGBpa[i] - 0.1)) / 307 | (400.0 - fabs(clr.RGBpa[i] - 0.1))), 308 | (1.0 / 0.42)); 309 | } 310 | 311 | return clr; 312 | } 313 | 314 | static 315 | CAM02COLOR HPEtoCAT02(CAM02COLOR clr) 316 | { 317 | cmsFloat64Number M[9]; 318 | 319 | M[0] = (( 0.7328 * 1.910197) + (0.4296 * 0.370950)); 320 | M[1] = (( 0.7328 * -1.112124) + (0.4296 * 0.629054)); 321 | M[2] = (( 0.7328 * 0.201908) + (0.4296 * 0.000008) - 0.1624); 322 | M[3] = ((-0.7036 * 1.910197) + (1.6975 * 0.370950)); 323 | M[4] = ((-0.7036 * -1.112124) + (1.6975 * 0.629054)); 324 | M[5] = ((-0.7036 * 0.201908) + (1.6975 * 0.000008) + 0.0061); 325 | M[6] = (( 0.0030 * 1.910197) + (0.0136 * 0.370950)); 326 | M[7] = (( 0.0030 * -1.112124) + (0.0136 * 0.629054)); 327 | M[8] = (( 0.0030 * 0.201908) + (0.0136 * 0.000008) + 0.9834);; 328 | 329 | clr.RGBc[0] = (clr.RGBp[0] * M[0]) + (clr.RGBp[1] * M[1]) + (clr.RGBp[2] * M[2]); 330 | clr.RGBc[1] = (clr.RGBp[0] * M[3]) + (clr.RGBp[1] * M[4]) + (clr.RGBp[2] * M[5]); 331 | clr.RGBc[2] = (clr.RGBp[0] * M[6]) + (clr.RGBp[1] * M[7]) + (clr.RGBp[2] * M[8]); 332 | return clr; 333 | } 334 | 335 | 336 | static 337 | CAM02COLOR InverseChromaticAdaptation(CAM02COLOR clr, cmsCIECAM02* pMod) 338 | { 339 | cmsUInt32Number i; 340 | for (i = 0; i < 3; i++) { 341 | clr.RGB[i] = clr.RGBc[i] / 342 | ((pMod->adoptedWhite.XYZ[1] * pMod->D / pMod->adoptedWhite.RGB[i]) + 1.0 - pMod->D); 343 | } 344 | return clr; 345 | } 346 | 347 | 348 | static 349 | CAM02COLOR CAT02toXYZ(CAM02COLOR clr) 350 | { 351 | clr.XYZ[0] = (clr.RGB[0] * 1.096124) + (clr.RGB[1] * -0.278869) + (clr.RGB[2] * 0.182745); 352 | clr.XYZ[1] = (clr.RGB[0] * 0.454369) + (clr.RGB[1] * 0.473533) + (clr.RGB[2] * 0.072098); 353 | clr.XYZ[2] = (clr.RGB[0] * -0.009628) + (clr.RGB[1] * -0.005698) + (clr.RGB[2] * 1.015326); 354 | 355 | return clr; 356 | } 357 | 358 | 359 | cmsHANDLE CMSEXPORT cmsCIECAM02Init(cmsContext ContextID, const cmsViewingConditions* pVC) 360 | { 361 | cmsCIECAM02* lpMod; 362 | 363 | _cmsAssert(pVC != NULL); 364 | 365 | if((lpMod = (cmsCIECAM02*) _cmsMallocZero(ContextID, sizeof(cmsCIECAM02))) == NULL) { 366 | return NULL; 367 | } 368 | 369 | lpMod ->ContextID = ContextID; 370 | 371 | lpMod ->adoptedWhite.XYZ[0] = pVC ->whitePoint.X; 372 | lpMod ->adoptedWhite.XYZ[1] = pVC ->whitePoint.Y; 373 | lpMod ->adoptedWhite.XYZ[2] = pVC ->whitePoint.Z; 374 | 375 | lpMod -> LA = pVC ->La; 376 | lpMod -> Yb = pVC ->Yb; 377 | lpMod -> D = pVC ->D_value; 378 | lpMod -> surround = pVC ->surround; 379 | 380 | switch (lpMod -> surround) { 381 | 382 | 383 | case CUTSHEET_SURROUND: 384 | lpMod->F = 0.8; 385 | lpMod->c = 0.41; 386 | lpMod->Nc = 0.8; 387 | break; 388 | 389 | case DARK_SURROUND: 390 | lpMod -> F = 0.8; 391 | lpMod -> c = 0.525; 392 | lpMod -> Nc = 0.8; 393 | break; 394 | 395 | case DIM_SURROUND: 396 | lpMod -> F = 0.9; 397 | lpMod -> c = 0.59; 398 | lpMod -> Nc = 0.95; 399 | break; 400 | 401 | default: 402 | // Average surround 403 | lpMod -> F = 1.0; 404 | lpMod -> c = 0.69; 405 | lpMod -> Nc = 1.0; 406 | } 407 | 408 | lpMod -> n = compute_n(lpMod); 409 | lpMod -> z = compute_z(lpMod); 410 | lpMod -> Nbb = computeNbb(lpMod); 411 | lpMod -> FL = computeFL(lpMod); 412 | 413 | if (lpMod -> D == D_CALCULATE) { 414 | lpMod -> D = computeD(lpMod); 415 | } 416 | 417 | lpMod -> Ncb = lpMod -> Nbb; 418 | 419 | lpMod -> adoptedWhite = XYZtoCAT02(lpMod -> adoptedWhite); 420 | lpMod -> adoptedWhite = ChromaticAdaptation(lpMod -> adoptedWhite, lpMod); 421 | lpMod -> adoptedWhite = CAT02toHPE(lpMod -> adoptedWhite); 422 | lpMod -> adoptedWhite = NonlinearCompression(lpMod -> adoptedWhite, lpMod); 423 | 424 | return (cmsHANDLE) lpMod; 425 | 426 | } 427 | 428 | void CMSEXPORT cmsCIECAM02Done(cmsHANDLE hModel) 429 | { 430 | cmsCIECAM02* lpMod = (cmsCIECAM02*) hModel; 431 | 432 | if (lpMod) _cmsFree(lpMod ->ContextID, lpMod); 433 | } 434 | 435 | 436 | void CMSEXPORT cmsCIECAM02Forward(cmsHANDLE hModel, const cmsCIEXYZ* pIn, cmsJCh* pOut) 437 | { 438 | CAM02COLOR clr; 439 | cmsCIECAM02* lpMod = (cmsCIECAM02*) hModel; 440 | 441 | _cmsAssert(lpMod != NULL); 442 | _cmsAssert(pIn != NULL); 443 | _cmsAssert(pOut != NULL); 444 | 445 | memset(&clr, 0, sizeof(clr)); 446 | 447 | clr.XYZ[0] = pIn ->X; 448 | clr.XYZ[1] = pIn ->Y; 449 | clr.XYZ[2] = pIn ->Z; 450 | 451 | clr = XYZtoCAT02(clr); 452 | clr = ChromaticAdaptation(clr, lpMod); 453 | clr = CAT02toHPE(clr); 454 | clr = NonlinearCompression(clr, lpMod); 455 | clr = ComputeCorrelates(clr, lpMod); 456 | 457 | pOut ->J = clr.J; 458 | pOut ->C = clr.C; 459 | pOut ->h = clr.h; 460 | } 461 | 462 | void CMSEXPORT cmsCIECAM02Reverse(cmsHANDLE hModel, const cmsJCh* pIn, cmsCIEXYZ* pOut) 463 | { 464 | CAM02COLOR clr; 465 | cmsCIECAM02* lpMod = (cmsCIECAM02*) hModel; 466 | 467 | _cmsAssert(lpMod != NULL); 468 | _cmsAssert(pIn != NULL); 469 | _cmsAssert(pOut != NULL); 470 | 471 | memset(&clr, 0, sizeof(clr)); 472 | 473 | clr.J = pIn -> J; 474 | clr.C = pIn -> C; 475 | clr.h = pIn -> h; 476 | 477 | clr = InverseCorrelates(clr, lpMod); 478 | clr = InverseNonlinearity(clr, lpMod); 479 | clr = HPEtoCAT02(clr); 480 | clr = InverseChromaticAdaptation(clr, lpMod); 481 | clr = CAT02toXYZ(clr); 482 | 483 | pOut ->X = clr.XYZ[0]; 484 | pOut ->Y = clr.XYZ[1]; 485 | pOut ->Z = clr.XYZ[2]; 486 | } 487 | -------------------------------------------------------------------------------- /ImageColoriser/Sources/Utils/littlecms/cmserr.c: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------------- 2 | // 3 | // Little Color Management System 4 | // Copyright (c) 1998-2020 Marti Maria Saguer 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining 7 | // a copy of this software and associated documentation files (the "Software"), 8 | // to deal in the Software without restriction, including without limitation 9 | // the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 | // and/or sell copies of the Software, and to permit persons to whom the Software 11 | // is furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 18 | // THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | // 24 | //--------------------------------------------------------------------------------- 25 | 26 | #include "lcms2_internal.h" 27 | 28 | 29 | // This function is here to help applications to prevent mixing lcms versions on header and shared objects. 30 | int CMSEXPORT cmsGetEncodedCMMversion(void) 31 | { 32 | return LCMS_VERSION; 33 | } 34 | 35 | // I am so tired about incompatibilities on those functions that here are some replacements 36 | // that hopefully would be fully portable. 37 | 38 | // compare two strings ignoring case 39 | int CMSEXPORT cmsstrcasecmp(const char* s1, const char* s2) 40 | { 41 | CMSREGISTER const unsigned char *us1 = (const unsigned char *)s1, 42 | *us2 = (const unsigned char *)s2; 43 | 44 | while (toupper(*us1) == toupper(*us2++)) 45 | if (*us1++ == '\0') 46 | return 0; 47 | 48 | return (toupper(*us1) - toupper(*--us2)); 49 | } 50 | 51 | // long int because C99 specifies ftell in such way (7.19.9.2) 52 | long int CMSEXPORT cmsfilelength(FILE* f) 53 | { 54 | long int p , n; 55 | 56 | p = ftell(f); // register current file position 57 | if (p == -1L) 58 | return -1L; 59 | 60 | if (fseek(f, 0, SEEK_END) != 0) { 61 | return -1L; 62 | } 63 | 64 | n = ftell(f); 65 | fseek(f, p, SEEK_SET); // file position restored 66 | 67 | return n; 68 | } 69 | 70 | 71 | // Memory handling ------------------------------------------------------------------ 72 | // 73 | // This is the interface to low-level memory management routines. By default a simple 74 | // wrapping to malloc/free/realloc is provided, although there is a limit on the max 75 | // amount of memoy that can be reclaimed. This is mostly as a safety feature to prevent 76 | // bogus or evil code to allocate huge blocks that otherwise lcms would never need. 77 | 78 | #define MAX_MEMORY_FOR_ALLOC ((cmsUInt32Number)(1024U*1024U*512U)) 79 | 80 | // User may override this behaviour by using a memory plug-in, which basically replaces 81 | // the default memory management functions. In this case, no check is performed and it 82 | // is up to the plug-in writter to keep in the safe side. There are only three functions 83 | // required to be implemented: malloc, realloc and free, although the user may want to 84 | // replace the optional mallocZero, calloc and dup as well. 85 | 86 | cmsBool _cmsRegisterMemHandlerPlugin(cmsContext ContextID, cmsPluginBase* Plugin); 87 | 88 | // ********************************************************************************* 89 | 90 | // This is the default memory allocation function. It does a very coarse 91 | // check of amount of memory, just to prevent exploits 92 | static 93 | void* _cmsMallocDefaultFn(cmsContext ContextID, cmsUInt32Number size) 94 | { 95 | if (size > MAX_MEMORY_FOR_ALLOC) return NULL; // Never allow over maximum 96 | 97 | return (void*) malloc(size); 98 | 99 | cmsUNUSED_PARAMETER(ContextID); 100 | } 101 | 102 | // Generic allocate & zero 103 | static 104 | void* _cmsMallocZeroDefaultFn(cmsContext ContextID, cmsUInt32Number size) 105 | { 106 | void *pt = _cmsMalloc(ContextID, size); 107 | if (pt == NULL) return NULL; 108 | 109 | memset(pt, 0, size); 110 | return pt; 111 | } 112 | 113 | 114 | // The default free function. The only check proformed is against NULL pointers 115 | static 116 | void _cmsFreeDefaultFn(cmsContext ContextID, void *Ptr) 117 | { 118 | // free(NULL) is defined a no-op by C99, therefore it is safe to 119 | // avoid the check, but it is here just in case... 120 | 121 | if (Ptr) free(Ptr); 122 | 123 | cmsUNUSED_PARAMETER(ContextID); 124 | } 125 | 126 | // The default realloc function. Again it checks for exploits. If Ptr is NULL, 127 | // realloc behaves the same way as malloc and allocates a new block of size bytes. 128 | static 129 | void* _cmsReallocDefaultFn(cmsContext ContextID, void* Ptr, cmsUInt32Number size) 130 | { 131 | 132 | if (size > MAX_MEMORY_FOR_ALLOC) return NULL; // Never realloc over 512Mb 133 | 134 | return realloc(Ptr, size); 135 | 136 | cmsUNUSED_PARAMETER(ContextID); 137 | } 138 | 139 | 140 | // The default calloc function. Allocates an array of num elements, each one of size bytes 141 | // all memory is initialized to zero. 142 | static 143 | void* _cmsCallocDefaultFn(cmsContext ContextID, cmsUInt32Number num, cmsUInt32Number size) 144 | { 145 | cmsUInt32Number Total = num * size; 146 | 147 | // Preserve calloc behaviour 148 | if (Total == 0) return NULL; 149 | 150 | // Safe check for overflow. 151 | if (num >= UINT_MAX / size) return NULL; 152 | 153 | // Check for overflow 154 | if (Total < num || Total < size) { 155 | return NULL; 156 | } 157 | 158 | if (Total > MAX_MEMORY_FOR_ALLOC) return NULL; // Never alloc over 512Mb 159 | 160 | return _cmsMallocZero(ContextID, Total); 161 | } 162 | 163 | // Generic block duplication 164 | static 165 | void* _cmsDupDefaultFn(cmsContext ContextID, const void* Org, cmsUInt32Number size) 166 | { 167 | void* mem; 168 | 169 | if (size > MAX_MEMORY_FOR_ALLOC) return NULL; // Never dup over 512Mb 170 | 171 | mem = _cmsMalloc(ContextID, size); 172 | 173 | if (mem != NULL && Org != NULL) 174 | memmove(mem, Org, size); 175 | 176 | return mem; 177 | } 178 | 179 | 180 | // Pointers to memory manager functions in Context0 181 | _cmsMemPluginChunkType _cmsMemPluginChunk = { _cmsMallocDefaultFn, _cmsMallocZeroDefaultFn, _cmsFreeDefaultFn, 182 | _cmsReallocDefaultFn, _cmsCallocDefaultFn, _cmsDupDefaultFn 183 | }; 184 | 185 | 186 | // Reset and duplicate memory manager 187 | void _cmsAllocMemPluginChunk(struct _cmsContext_struct* ctx, const struct _cmsContext_struct* src) 188 | { 189 | _cmsAssert(ctx != NULL); 190 | 191 | if (src != NULL) { 192 | 193 | // Duplicate 194 | ctx ->chunks[MemPlugin] = _cmsSubAllocDup(ctx ->MemPool, src ->chunks[MemPlugin], sizeof(_cmsMemPluginChunkType)); 195 | } 196 | else { 197 | 198 | // To reset it, we use the default allocators, which cannot be overridden 199 | ctx ->chunks[MemPlugin] = &ctx ->DefaultMemoryManager; 200 | } 201 | } 202 | 203 | // Auxiliary to fill memory management functions from plugin (or context 0 defaults) 204 | void _cmsInstallAllocFunctions(cmsPluginMemHandler* Plugin, _cmsMemPluginChunkType* ptr) 205 | { 206 | if (Plugin == NULL) { 207 | 208 | memcpy(ptr, &_cmsMemPluginChunk, sizeof(_cmsMemPluginChunk)); 209 | } 210 | else { 211 | 212 | ptr ->MallocPtr = Plugin -> MallocPtr; 213 | ptr ->FreePtr = Plugin -> FreePtr; 214 | ptr ->ReallocPtr = Plugin -> ReallocPtr; 215 | 216 | // Make sure we revert to defaults 217 | ptr ->MallocZeroPtr= _cmsMallocZeroDefaultFn; 218 | ptr ->CallocPtr = _cmsCallocDefaultFn; 219 | ptr ->DupPtr = _cmsDupDefaultFn; 220 | 221 | if (Plugin ->MallocZeroPtr != NULL) ptr ->MallocZeroPtr = Plugin -> MallocZeroPtr; 222 | if (Plugin ->CallocPtr != NULL) ptr ->CallocPtr = Plugin -> CallocPtr; 223 | if (Plugin ->DupPtr != NULL) ptr ->DupPtr = Plugin -> DupPtr; 224 | 225 | } 226 | } 227 | 228 | 229 | // Plug-in replacement entry 230 | cmsBool _cmsRegisterMemHandlerPlugin(cmsContext ContextID, cmsPluginBase *Data) 231 | { 232 | cmsPluginMemHandler* Plugin = (cmsPluginMemHandler*) Data; 233 | _cmsMemPluginChunkType* ptr; 234 | 235 | // NULL forces to reset to defaults. In this special case, the defaults are stored in the context structure. 236 | // Remaining plug-ins does NOT have any copy in the context structure, but this is somehow special as the 237 | // context internal data should be malloce'd by using those functions. 238 | if (Data == NULL) { 239 | 240 | struct _cmsContext_struct* ctx = ( struct _cmsContext_struct*) ContextID; 241 | 242 | // Return to the default allocators 243 | if (ContextID != NULL) { 244 | ctx->chunks[MemPlugin] = (void*) &ctx->DefaultMemoryManager; 245 | } 246 | return TRUE; 247 | } 248 | 249 | // Check for required callbacks 250 | if (Plugin -> MallocPtr == NULL || 251 | Plugin -> FreePtr == NULL || 252 | Plugin -> ReallocPtr == NULL) return FALSE; 253 | 254 | // Set replacement functions 255 | ptr = (_cmsMemPluginChunkType*) _cmsContextGetClientChunk(ContextID, MemPlugin); 256 | if (ptr == NULL) 257 | return FALSE; 258 | 259 | _cmsInstallAllocFunctions(Plugin, ptr); 260 | return TRUE; 261 | } 262 | 263 | // Generic allocate 264 | void* CMSEXPORT _cmsMalloc(cmsContext ContextID, cmsUInt32Number size) 265 | { 266 | _cmsMemPluginChunkType* ptr = (_cmsMemPluginChunkType*) _cmsContextGetClientChunk(ContextID, MemPlugin); 267 | return ptr ->MallocPtr(ContextID, size); 268 | } 269 | 270 | // Generic allocate & zero 271 | void* CMSEXPORT _cmsMallocZero(cmsContext ContextID, cmsUInt32Number size) 272 | { 273 | _cmsMemPluginChunkType* ptr = (_cmsMemPluginChunkType*) _cmsContextGetClientChunk(ContextID, MemPlugin); 274 | return ptr->MallocZeroPtr(ContextID, size); 275 | } 276 | 277 | // Generic calloc 278 | void* CMSEXPORT _cmsCalloc(cmsContext ContextID, cmsUInt32Number num, cmsUInt32Number size) 279 | { 280 | _cmsMemPluginChunkType* ptr = (_cmsMemPluginChunkType*) _cmsContextGetClientChunk(ContextID, MemPlugin); 281 | return ptr->CallocPtr(ContextID, num, size); 282 | } 283 | 284 | // Generic reallocate 285 | void* CMSEXPORT _cmsRealloc(cmsContext ContextID, void* Ptr, cmsUInt32Number size) 286 | { 287 | _cmsMemPluginChunkType* ptr = (_cmsMemPluginChunkType*) _cmsContextGetClientChunk(ContextID, MemPlugin); 288 | return ptr->ReallocPtr(ContextID, Ptr, size); 289 | } 290 | 291 | // Generic free memory 292 | void CMSEXPORT _cmsFree(cmsContext ContextID, void* Ptr) 293 | { 294 | if (Ptr != NULL) { 295 | _cmsMemPluginChunkType* ptr = (_cmsMemPluginChunkType*) _cmsContextGetClientChunk(ContextID, MemPlugin); 296 | ptr ->FreePtr(ContextID, Ptr); 297 | } 298 | } 299 | 300 | // Generic block duplication 301 | void* CMSEXPORT _cmsDupMem(cmsContext ContextID, const void* Org, cmsUInt32Number size) 302 | { 303 | _cmsMemPluginChunkType* ptr = (_cmsMemPluginChunkType*) _cmsContextGetClientChunk(ContextID, MemPlugin); 304 | return ptr ->DupPtr(ContextID, Org, size); 305 | } 306 | 307 | // ******************************************************************************************** 308 | 309 | // Sub allocation takes care of many pointers of small size. The memory allocated in 310 | // this way have be freed at once. Next function allocates a single chunk for linked list 311 | // I prefer this method over realloc due to the big inpact on xput realloc may have if 312 | // memory is being swapped to disk. This approach is safer (although that may not be true on all platforms) 313 | static 314 | _cmsSubAllocator_chunk* _cmsCreateSubAllocChunk(cmsContext ContextID, cmsUInt32Number Initial) 315 | { 316 | _cmsSubAllocator_chunk* chunk; 317 | 318 | // 20K by default 319 | if (Initial == 0) 320 | Initial = 20*1024; 321 | 322 | // Create the container 323 | chunk = (_cmsSubAllocator_chunk*) _cmsMallocZero(ContextID, sizeof(_cmsSubAllocator_chunk)); 324 | if (chunk == NULL) return NULL; 325 | 326 | // Initialize values 327 | chunk ->Block = (cmsUInt8Number*) _cmsMalloc(ContextID, Initial); 328 | if (chunk ->Block == NULL) { 329 | 330 | // Something went wrong 331 | _cmsFree(ContextID, chunk); 332 | return NULL; 333 | } 334 | 335 | chunk ->BlockSize = Initial; 336 | chunk ->Used = 0; 337 | chunk ->next = NULL; 338 | 339 | return chunk; 340 | } 341 | 342 | // The suballocated is nothing but a pointer to the first element in the list. We also keep 343 | // the thread ID in this structure. 344 | _cmsSubAllocator* _cmsCreateSubAlloc(cmsContext ContextID, cmsUInt32Number Initial) 345 | { 346 | _cmsSubAllocator* sub; 347 | 348 | // Create the container 349 | sub = (_cmsSubAllocator*) _cmsMallocZero(ContextID, sizeof(_cmsSubAllocator)); 350 | if (sub == NULL) return NULL; 351 | 352 | sub ->ContextID = ContextID; 353 | 354 | sub ->h = _cmsCreateSubAllocChunk(ContextID, Initial); 355 | if (sub ->h == NULL) { 356 | _cmsFree(ContextID, sub); 357 | return NULL; 358 | } 359 | 360 | return sub; 361 | } 362 | 363 | 364 | // Get rid of whole linked list 365 | void _cmsSubAllocDestroy(_cmsSubAllocator* sub) 366 | { 367 | _cmsSubAllocator_chunk *chunk, *n; 368 | 369 | for (chunk = sub ->h; chunk != NULL; chunk = n) { 370 | 371 | n = chunk->next; 372 | if (chunk->Block != NULL) _cmsFree(sub ->ContextID, chunk->Block); 373 | _cmsFree(sub ->ContextID, chunk); 374 | } 375 | 376 | // Free the header 377 | _cmsFree(sub ->ContextID, sub); 378 | } 379 | 380 | 381 | // Get a pointer to small memory block. 382 | void* _cmsSubAlloc(_cmsSubAllocator* sub, cmsUInt32Number size) 383 | { 384 | cmsUInt32Number Free = sub -> h ->BlockSize - sub -> h -> Used; 385 | cmsUInt8Number* ptr; 386 | 387 | size = _cmsALIGNMEM(size); 388 | 389 | // Check for memory. If there is no room, allocate a new chunk of double memory size. 390 | if (size > Free) { 391 | 392 | _cmsSubAllocator_chunk* chunk; 393 | cmsUInt32Number newSize; 394 | 395 | newSize = sub -> h ->BlockSize * 2; 396 | if (newSize < size) newSize = size; 397 | 398 | chunk = _cmsCreateSubAllocChunk(sub -> ContextID, newSize); 399 | if (chunk == NULL) return NULL; 400 | 401 | // Link list 402 | chunk ->next = sub ->h; 403 | sub ->h = chunk; 404 | 405 | } 406 | 407 | ptr = sub -> h ->Block + sub -> h ->Used; 408 | sub -> h -> Used += size; 409 | 410 | return (void*) ptr; 411 | } 412 | 413 | // Duplicate in pool 414 | void* _cmsSubAllocDup(_cmsSubAllocator* s, const void *ptr, cmsUInt32Number size) 415 | { 416 | void *NewPtr; 417 | 418 | // Dup of null pointer is also NULL 419 | if (ptr == NULL) 420 | return NULL; 421 | 422 | NewPtr = _cmsSubAlloc(s, size); 423 | 424 | if (ptr != NULL && NewPtr != NULL) { 425 | memcpy(NewPtr, ptr, size); 426 | } 427 | 428 | return NewPtr; 429 | } 430 | 431 | 432 | 433 | // Error logging ****************************************************************** 434 | 435 | // There is no error handling at all. When a function fails, it returns proper value. 436 | // For example, all create functions does return NULL on failure. Other return FALSE 437 | // It may be interesting, for the developer, to know why the function is failing. 438 | // for that reason, lcms2 does offer a logging function. This function does receive 439 | // a ENGLISH string with some clues on what is going wrong. You can show this 440 | // info to the end user, or just create some sort of log. 441 | // The logging function should NOT terminate the program, as this obviously can leave 442 | // resources. It is the programmer's responsibility to check each function return code 443 | // to make sure it didn't fail. 444 | 445 | // Error messages are limited to MAX_ERROR_MESSAGE_LEN 446 | 447 | #define MAX_ERROR_MESSAGE_LEN 1024 448 | 449 | // --------------------------------------------------------------------------------------------------------- 450 | 451 | // This is our default log error 452 | static void DefaultLogErrorHandlerFunction(cmsContext ContextID, cmsUInt32Number ErrorCode, const char *Text); 453 | 454 | // Context0 storage, which is global 455 | _cmsLogErrorChunkType _cmsLogErrorChunk = { DefaultLogErrorHandlerFunction }; 456 | 457 | // Allocates and inits error logger container for a given context. If src is NULL, only initializes the value 458 | // to the default. Otherwise, it duplicates the value. The interface is standard across all context clients 459 | void _cmsAllocLogErrorChunk(struct _cmsContext_struct* ctx, 460 | const struct _cmsContext_struct* src) 461 | { 462 | static _cmsLogErrorChunkType LogErrorChunk = { DefaultLogErrorHandlerFunction }; 463 | void* from; 464 | 465 | if (src != NULL) { 466 | from = src ->chunks[Logger]; 467 | } 468 | else { 469 | from = &LogErrorChunk; 470 | } 471 | 472 | ctx ->chunks[Logger] = _cmsSubAllocDup(ctx ->MemPool, from, sizeof(_cmsLogErrorChunkType)); 473 | } 474 | 475 | // The default error logger does nothing. 476 | static 477 | void DefaultLogErrorHandlerFunction(cmsContext ContextID, cmsUInt32Number ErrorCode, const char *Text) 478 | { 479 | // fprintf(stderr, "[lcms]: %s\n", Text); 480 | // fflush(stderr); 481 | 482 | cmsUNUSED_PARAMETER(ContextID); 483 | cmsUNUSED_PARAMETER(ErrorCode); 484 | cmsUNUSED_PARAMETER(Text); 485 | } 486 | 487 | // Change log error, context based 488 | void CMSEXPORT cmsSetLogErrorHandlerTHR(cmsContext ContextID, cmsLogErrorHandlerFunction Fn) 489 | { 490 | _cmsLogErrorChunkType* lhg = (_cmsLogErrorChunkType*) _cmsContextGetClientChunk(ContextID, Logger); 491 | 492 | if (lhg != NULL) { 493 | 494 | if (Fn == NULL) 495 | lhg -> LogErrorHandler = DefaultLogErrorHandlerFunction; 496 | else 497 | lhg -> LogErrorHandler = Fn; 498 | } 499 | } 500 | 501 | // Change log error, legacy 502 | void CMSEXPORT cmsSetLogErrorHandler(cmsLogErrorHandlerFunction Fn) 503 | { 504 | cmsSetLogErrorHandlerTHR(NULL, Fn); 505 | } 506 | 507 | // Log an error 508 | // ErrorText is a text holding an english description of error. 509 | void CMSEXPORT cmsSignalError(cmsContext ContextID, cmsUInt32Number ErrorCode, const char *ErrorText, ...) 510 | { 511 | va_list args; 512 | char Buffer[MAX_ERROR_MESSAGE_LEN]; 513 | _cmsLogErrorChunkType* lhg; 514 | 515 | 516 | va_start(args, ErrorText); 517 | vsnprintf(Buffer, MAX_ERROR_MESSAGE_LEN-1, ErrorText, args); 518 | va_end(args); 519 | 520 | // Check for the context, if specified go there. If not, go for the global 521 | lhg = (_cmsLogErrorChunkType*) _cmsContextGetClientChunk(ContextID, Logger); 522 | if (lhg ->LogErrorHandler) { 523 | lhg ->LogErrorHandler(ContextID, ErrorCode, Buffer); 524 | } 525 | } 526 | 527 | // Utility function to print signatures 528 | void _cmsTagSignature2String(char String[5], cmsTagSignature sig) 529 | { 530 | cmsUInt32Number be; 531 | 532 | // Convert to big endian 533 | be = _cmsAdjustEndianess32((cmsUInt32Number) sig); 534 | 535 | // Move chars 536 | memmove(String, &be, 4); 537 | 538 | // Make sure of terminator 539 | String[4] = 0; 540 | } 541 | 542 | //-------------------------------------------------------------------------------------------------- 543 | 544 | 545 | static 546 | void* defMtxCreate(cmsContext id) 547 | { 548 | _cmsMutex* ptr_mutex = (_cmsMutex*) _cmsMalloc(id, sizeof(_cmsMutex)); 549 | _cmsInitMutexPrimitive(ptr_mutex); 550 | return (void*) ptr_mutex; 551 | } 552 | 553 | static 554 | void defMtxDestroy(cmsContext id, void* mtx) 555 | { 556 | _cmsDestroyMutexPrimitive((_cmsMutex *) mtx); 557 | _cmsFree(id, mtx); 558 | } 559 | 560 | static 561 | cmsBool defMtxLock(cmsContext id, void* mtx) 562 | { 563 | cmsUNUSED_PARAMETER(id); 564 | return _cmsLockPrimitive((_cmsMutex *) mtx) == 0; 565 | } 566 | 567 | static 568 | void defMtxUnlock(cmsContext id, void* mtx) 569 | { 570 | cmsUNUSED_PARAMETER(id); 571 | _cmsUnlockPrimitive((_cmsMutex *) mtx); 572 | } 573 | 574 | 575 | 576 | // Pointers to memory manager functions in Context0 577 | _cmsMutexPluginChunkType _cmsMutexPluginChunk = { defMtxCreate, defMtxDestroy, defMtxLock, defMtxUnlock }; 578 | 579 | // Allocate and init mutex container. 580 | void _cmsAllocMutexPluginChunk(struct _cmsContext_struct* ctx, 581 | const struct _cmsContext_struct* src) 582 | { 583 | static _cmsMutexPluginChunkType MutexChunk = {defMtxCreate, defMtxDestroy, defMtxLock, defMtxUnlock }; 584 | void* from; 585 | 586 | if (src != NULL) { 587 | from = src ->chunks[MutexPlugin]; 588 | } 589 | else { 590 | from = &MutexChunk; 591 | } 592 | 593 | ctx ->chunks[MutexPlugin] = _cmsSubAllocDup(ctx ->MemPool, from, sizeof(_cmsMutexPluginChunkType)); 594 | } 595 | 596 | // Register new ways to transform 597 | cmsBool _cmsRegisterMutexPlugin(cmsContext ContextID, cmsPluginBase* Data) 598 | { 599 | cmsPluginMutex* Plugin = (cmsPluginMutex*) Data; 600 | _cmsMutexPluginChunkType* ctx = ( _cmsMutexPluginChunkType*) _cmsContextGetClientChunk(ContextID, MutexPlugin); 601 | 602 | if (Data == NULL) { 603 | 604 | // No lock routines 605 | ctx->CreateMutexPtr = NULL; 606 | ctx->DestroyMutexPtr = NULL; 607 | ctx->LockMutexPtr = NULL; 608 | ctx ->UnlockMutexPtr = NULL; 609 | return TRUE; 610 | } 611 | 612 | // Factory callback is required 613 | if (Plugin ->CreateMutexPtr == NULL || Plugin ->DestroyMutexPtr == NULL || 614 | Plugin ->LockMutexPtr == NULL || Plugin ->UnlockMutexPtr == NULL) return FALSE; 615 | 616 | 617 | ctx->CreateMutexPtr = Plugin->CreateMutexPtr; 618 | ctx->DestroyMutexPtr = Plugin ->DestroyMutexPtr; 619 | ctx ->LockMutexPtr = Plugin ->LockMutexPtr; 620 | ctx ->UnlockMutexPtr = Plugin ->UnlockMutexPtr; 621 | 622 | // All is ok 623 | return TRUE; 624 | } 625 | 626 | // Generic Mutex fns 627 | void* CMSEXPORT _cmsCreateMutex(cmsContext ContextID) 628 | { 629 | _cmsMutexPluginChunkType* ptr = (_cmsMutexPluginChunkType*) _cmsContextGetClientChunk(ContextID, MutexPlugin); 630 | 631 | if (ptr ->CreateMutexPtr == NULL) return NULL; 632 | 633 | return ptr ->CreateMutexPtr(ContextID); 634 | } 635 | 636 | void CMSEXPORT _cmsDestroyMutex(cmsContext ContextID, void* mtx) 637 | { 638 | _cmsMutexPluginChunkType* ptr = (_cmsMutexPluginChunkType*) _cmsContextGetClientChunk(ContextID, MutexPlugin); 639 | 640 | if (ptr ->DestroyMutexPtr != NULL) { 641 | 642 | ptr ->DestroyMutexPtr(ContextID, mtx); 643 | } 644 | } 645 | 646 | cmsBool CMSEXPORT _cmsLockMutex(cmsContext ContextID, void* mtx) 647 | { 648 | _cmsMutexPluginChunkType* ptr = (_cmsMutexPluginChunkType*) _cmsContextGetClientChunk(ContextID, MutexPlugin); 649 | 650 | if (ptr ->LockMutexPtr == NULL) return TRUE; 651 | 652 | return ptr ->LockMutexPtr(ContextID, mtx); 653 | } 654 | 655 | void CMSEXPORT _cmsUnlockMutex(cmsContext ContextID, void* mtx) 656 | { 657 | _cmsMutexPluginChunkType* ptr = (_cmsMutexPluginChunkType*) _cmsContextGetClientChunk(ContextID, MutexPlugin); 658 | 659 | if (ptr ->UnlockMutexPtr != NULL) { 660 | 661 | ptr ->UnlockMutexPtr(ContextID, mtx); 662 | } 663 | } 664 | -------------------------------------------------------------------------------- /ImageColoriser/Sources/Utils/littlecms/cmsgmt.c: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------------- 2 | // 3 | // Little Color Management System 4 | // Copyright (c) 1998-2020 Marti Maria Saguer 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining 7 | // a copy of this software and associated documentation files (the "Software"), 8 | // to deal in the Software without restriction, including without limitation 9 | // the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 | // and/or sell copies of the Software, and to permit persons to whom the Software 11 | // is furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 18 | // THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | // 24 | //--------------------------------------------------------------------------------- 25 | // 26 | 27 | #include "lcms2_internal.h" 28 | 29 | 30 | // Auxiliary: append a Lab identity after the given sequence of profiles 31 | // and return the transform. Lab profile is closed, rest of profiles are kept open. 32 | cmsHTRANSFORM _cmsChain2Lab(cmsContext ContextID, 33 | cmsUInt32Number nProfiles, 34 | cmsUInt32Number InputFormat, 35 | cmsUInt32Number OutputFormat, 36 | const cmsUInt32Number Intents[], 37 | const cmsHPROFILE hProfiles[], 38 | const cmsBool BPC[], 39 | const cmsFloat64Number AdaptationStates[], 40 | cmsUInt32Number dwFlags) 41 | { 42 | cmsHTRANSFORM xform; 43 | cmsHPROFILE hLab; 44 | cmsHPROFILE ProfileList[256]; 45 | cmsBool BPCList[256]; 46 | cmsFloat64Number AdaptationList[256]; 47 | cmsUInt32Number IntentList[256]; 48 | cmsUInt32Number i; 49 | 50 | // This is a rather big number and there is no need of dynamic memory 51 | // since we are adding a profile, 254 + 1 = 255 and this is the limit 52 | if (nProfiles > 254) return NULL; 53 | 54 | // The output space 55 | hLab = cmsCreateLab4ProfileTHR(ContextID, NULL); 56 | if (hLab == NULL) return NULL; 57 | 58 | // Create a copy of parameters 59 | for (i=0; i < nProfiles; i++) { 60 | 61 | ProfileList[i] = hProfiles[i]; 62 | BPCList[i] = BPC[i]; 63 | AdaptationList[i] = AdaptationStates[i]; 64 | IntentList[i] = Intents[i]; 65 | } 66 | 67 | // Place Lab identity at chain's end. 68 | ProfileList[nProfiles] = hLab; 69 | BPCList[nProfiles] = 0; 70 | AdaptationList[nProfiles] = 1.0; 71 | IntentList[nProfiles] = INTENT_RELATIVE_COLORIMETRIC; 72 | 73 | // Create the transform 74 | xform = cmsCreateExtendedTransform(ContextID, nProfiles + 1, ProfileList, 75 | BPCList, 76 | IntentList, 77 | AdaptationList, 78 | NULL, 0, 79 | InputFormat, 80 | OutputFormat, 81 | dwFlags); 82 | 83 | cmsCloseProfile(hLab); 84 | 85 | return xform; 86 | } 87 | 88 | 89 | // Compute K -> L* relationship. Flags may include black point compensation. In this case, 90 | // the relationship is assumed from the profile with BPC to a black point zero. 91 | static 92 | cmsToneCurve* ComputeKToLstar(cmsContext ContextID, 93 | cmsUInt32Number nPoints, 94 | cmsUInt32Number nProfiles, 95 | const cmsUInt32Number Intents[], 96 | const cmsHPROFILE hProfiles[], 97 | const cmsBool BPC[], 98 | const cmsFloat64Number AdaptationStates[], 99 | cmsUInt32Number dwFlags) 100 | { 101 | cmsToneCurve* out = NULL; 102 | cmsUInt32Number i; 103 | cmsHTRANSFORM xform; 104 | cmsCIELab Lab; 105 | cmsFloat32Number cmyk[4]; 106 | cmsFloat32Number* SampledPoints; 107 | 108 | xform = _cmsChain2Lab(ContextID, nProfiles, TYPE_CMYK_FLT, TYPE_Lab_DBL, Intents, hProfiles, BPC, AdaptationStates, dwFlags); 109 | if (xform == NULL) return NULL; 110 | 111 | SampledPoints = (cmsFloat32Number*) _cmsCalloc(ContextID, nPoints, sizeof(cmsFloat32Number)); 112 | if (SampledPoints == NULL) goto Error; 113 | 114 | for (i=0; i < nPoints; i++) { 115 | 116 | cmyk[0] = 0; 117 | cmyk[1] = 0; 118 | cmyk[2] = 0; 119 | cmyk[3] = (cmsFloat32Number) ((i * 100.0) / (nPoints-1)); 120 | 121 | cmsDoTransform(xform, cmyk, &Lab, 1); 122 | SampledPoints[i]= (cmsFloat32Number) (1.0 - Lab.L / 100.0); // Negate K for easier operation 123 | } 124 | 125 | out = cmsBuildTabulatedToneCurveFloat(ContextID, nPoints, SampledPoints); 126 | 127 | Error: 128 | 129 | cmsDeleteTransform(xform); 130 | if (SampledPoints) _cmsFree(ContextID, SampledPoints); 131 | 132 | return out; 133 | } 134 | 135 | 136 | // Compute Black tone curve on a CMYK -> CMYK transform. This is done by 137 | // using the proof direction on both profiles to find K->L* relationship 138 | // then joining both curves. dwFlags may include black point compensation. 139 | cmsToneCurve* _cmsBuildKToneCurve(cmsContext ContextID, 140 | cmsUInt32Number nPoints, 141 | cmsUInt32Number nProfiles, 142 | const cmsUInt32Number Intents[], 143 | const cmsHPROFILE hProfiles[], 144 | const cmsBool BPC[], 145 | const cmsFloat64Number AdaptationStates[], 146 | cmsUInt32Number dwFlags) 147 | { 148 | cmsToneCurve *in, *out, *KTone; 149 | 150 | // Make sure CMYK -> CMYK 151 | if (cmsGetColorSpace(hProfiles[0]) != cmsSigCmykData || 152 | cmsGetColorSpace(hProfiles[nProfiles-1])!= cmsSigCmykData) return NULL; 153 | 154 | 155 | // Make sure last is an output profile 156 | if (cmsGetDeviceClass(hProfiles[nProfiles - 1]) != cmsSigOutputClass) return NULL; 157 | 158 | // Create individual curves. BPC works also as each K to L* is 159 | // computed as a BPC to zero black point in case of L* 160 | in = ComputeKToLstar(ContextID, nPoints, nProfiles - 1, Intents, hProfiles, BPC, AdaptationStates, dwFlags); 161 | if (in == NULL) return NULL; 162 | 163 | out = ComputeKToLstar(ContextID, nPoints, 1, 164 | Intents + (nProfiles - 1), 165 | &hProfiles [nProfiles - 1], 166 | BPC + (nProfiles - 1), 167 | AdaptationStates + (nProfiles - 1), 168 | dwFlags); 169 | if (out == NULL) { 170 | cmsFreeToneCurve(in); 171 | return NULL; 172 | } 173 | 174 | // Build the relationship. This effectively limits the maximum accuracy to 16 bits, but 175 | // since this is used on black-preserving LUTs, we are not losing accuracy in any case 176 | KTone = cmsJoinToneCurve(ContextID, in, out, nPoints); 177 | 178 | // Get rid of components 179 | cmsFreeToneCurve(in); cmsFreeToneCurve(out); 180 | 181 | // Something went wrong... 182 | if (KTone == NULL) return NULL; 183 | 184 | // Make sure it is monotonic 185 | if (!cmsIsToneCurveMonotonic(KTone)) { 186 | cmsFreeToneCurve(KTone); 187 | return NULL; 188 | } 189 | 190 | return KTone; 191 | } 192 | 193 | 194 | // Gamut LUT Creation ----------------------------------------------------------------------------------------- 195 | 196 | // Used by gamut & softproofing 197 | 198 | typedef struct { 199 | 200 | cmsHTRANSFORM hInput; // From whatever input color space. 16 bits to DBL 201 | cmsHTRANSFORM hForward, hReverse; // Transforms going from Lab to colorant and back 202 | cmsFloat64Number Thereshold; // The thereshold after which is considered out of gamut 203 | 204 | } GAMUTCHAIN; 205 | 206 | // This sampler does compute gamut boundaries by comparing original 207 | // values with a transform going back and forth. Values above ERR_THERESHOLD 208 | // of maximum are considered out of gamut. 209 | 210 | #define ERR_THERESHOLD 5 211 | 212 | 213 | static 214 | int GamutSampler(CMSREGISTER const cmsUInt16Number In[], CMSREGISTER cmsUInt16Number Out[], CMSREGISTER void* Cargo) 215 | { 216 | GAMUTCHAIN* t = (GAMUTCHAIN* ) Cargo; 217 | cmsCIELab LabIn1, LabOut1; 218 | cmsCIELab LabIn2, LabOut2; 219 | cmsUInt16Number Proof[cmsMAXCHANNELS], Proof2[cmsMAXCHANNELS]; 220 | cmsFloat64Number dE1, dE2, ErrorRatio; 221 | 222 | // Assume in-gamut by default. 223 | ErrorRatio = 1.0; 224 | 225 | // Convert input to Lab 226 | cmsDoTransform(t -> hInput, In, &LabIn1, 1); 227 | 228 | // converts from PCS to colorant. This always 229 | // does return in-gamut values, 230 | cmsDoTransform(t -> hForward, &LabIn1, Proof, 1); 231 | 232 | // Now, do the inverse, from colorant to PCS. 233 | cmsDoTransform(t -> hReverse, Proof, &LabOut1, 1); 234 | 235 | memmove(&LabIn2, &LabOut1, sizeof(cmsCIELab)); 236 | 237 | // Try again, but this time taking Check as input 238 | cmsDoTransform(t -> hForward, &LabOut1, Proof2, 1); 239 | cmsDoTransform(t -> hReverse, Proof2, &LabOut2, 1); 240 | 241 | // Take difference of direct value 242 | dE1 = cmsDeltaE(&LabIn1, &LabOut1); 243 | 244 | // Take difference of converted value 245 | dE2 = cmsDeltaE(&LabIn2, &LabOut2); 246 | 247 | 248 | // if dE1 is small and dE2 is small, value is likely to be in gamut 249 | if (dE1 < t->Thereshold && dE2 < t->Thereshold) 250 | Out[0] = 0; 251 | else { 252 | 253 | // if dE1 is small and dE2 is big, undefined. Assume in gamut 254 | if (dE1 < t->Thereshold && dE2 > t->Thereshold) 255 | Out[0] = 0; 256 | else 257 | // dE1 is big and dE2 is small, clearly out of gamut 258 | if (dE1 > t->Thereshold && dE2 < t->Thereshold) 259 | Out[0] = (cmsUInt16Number) _cmsQuickFloor((dE1 - t->Thereshold) + .5); 260 | else { 261 | 262 | // dE1 is big and dE2 is also big, could be due to perceptual mapping 263 | // so take error ratio 264 | if (dE2 == 0.0) 265 | ErrorRatio = dE1; 266 | else 267 | ErrorRatio = dE1 / dE2; 268 | 269 | if (ErrorRatio > t->Thereshold) 270 | Out[0] = (cmsUInt16Number) _cmsQuickFloor((ErrorRatio - t->Thereshold) + .5); 271 | else 272 | Out[0] = 0; 273 | } 274 | } 275 | 276 | 277 | return TRUE; 278 | } 279 | 280 | // Does compute a gamut LUT going back and forth across pcs -> relativ. colorimetric intent -> pcs 281 | // the dE obtained is then annotated on the LUT. Values truly out of gamut are clipped to dE = 0xFFFE 282 | // and values changed are supposed to be handled by any gamut remapping, so, are out of gamut as well. 283 | // 284 | // **WARNING: This algorithm does assume that gamut remapping algorithms does NOT move in-gamut colors, 285 | // of course, many perceptual and saturation intents does not work in such way, but relativ. ones should. 286 | 287 | cmsPipeline* _cmsCreateGamutCheckPipeline(cmsContext ContextID, 288 | cmsHPROFILE hProfiles[], 289 | cmsBool BPC[], 290 | cmsUInt32Number Intents[], 291 | cmsFloat64Number AdaptationStates[], 292 | cmsUInt32Number nGamutPCSposition, 293 | cmsHPROFILE hGamut) 294 | { 295 | cmsHPROFILE hLab; 296 | cmsPipeline* Gamut; 297 | cmsStage* CLUT; 298 | cmsUInt32Number dwFormat; 299 | GAMUTCHAIN Chain; 300 | cmsUInt32Number nChannels, nGridpoints; 301 | cmsColorSpaceSignature ColorSpace; 302 | cmsUInt32Number i; 303 | cmsHPROFILE ProfileList[256]; 304 | cmsBool BPCList[256]; 305 | cmsFloat64Number AdaptationList[256]; 306 | cmsUInt32Number IntentList[256]; 307 | 308 | memset(&Chain, 0, sizeof(GAMUTCHAIN)); 309 | 310 | 311 | if (nGamutPCSposition <= 0 || nGamutPCSposition > 255) { 312 | cmsSignalError(ContextID, cmsERROR_RANGE, "Wrong position of PCS. 1..255 expected, %d found.", nGamutPCSposition); 313 | return NULL; 314 | } 315 | 316 | hLab = cmsCreateLab4ProfileTHR(ContextID, NULL); 317 | if (hLab == NULL) return NULL; 318 | 319 | 320 | // The figure of merit. On matrix-shaper profiles, should be almost zero as 321 | // the conversion is pretty exact. On LUT based profiles, different resolutions 322 | // of input and output CLUT may result in differences. 323 | 324 | if (cmsIsMatrixShaper(hGamut)) { 325 | 326 | Chain.Thereshold = 1.0; 327 | } 328 | else { 329 | Chain.Thereshold = ERR_THERESHOLD; 330 | } 331 | 332 | 333 | // Create a copy of parameters 334 | for (i=0; i < nGamutPCSposition; i++) { 335 | ProfileList[i] = hProfiles[i]; 336 | BPCList[i] = BPC[i]; 337 | AdaptationList[i] = AdaptationStates[i]; 338 | IntentList[i] = Intents[i]; 339 | } 340 | 341 | // Fill Lab identity 342 | ProfileList[nGamutPCSposition] = hLab; 343 | BPCList[nGamutPCSposition] = 0; 344 | AdaptationList[nGamutPCSposition] = 1.0; 345 | IntentList[nGamutPCSposition] = INTENT_RELATIVE_COLORIMETRIC; 346 | 347 | 348 | ColorSpace = cmsGetColorSpace(hGamut); 349 | 350 | nChannels = cmsChannelsOf(ColorSpace); 351 | nGridpoints = _cmsReasonableGridpointsByColorspace(ColorSpace, cmsFLAGS_HIGHRESPRECALC); 352 | dwFormat = (CHANNELS_SH(nChannels)|BYTES_SH(2)); 353 | 354 | // 16 bits to Lab double 355 | Chain.hInput = cmsCreateExtendedTransform(ContextID, 356 | nGamutPCSposition + 1, 357 | ProfileList, 358 | BPCList, 359 | IntentList, 360 | AdaptationList, 361 | NULL, 0, 362 | dwFormat, TYPE_Lab_DBL, 363 | cmsFLAGS_NOCACHE); 364 | 365 | 366 | // Does create the forward step. Lab double to device 367 | dwFormat = (CHANNELS_SH(nChannels)|BYTES_SH(2)); 368 | Chain.hForward = cmsCreateTransformTHR(ContextID, 369 | hLab, TYPE_Lab_DBL, 370 | hGamut, dwFormat, 371 | INTENT_RELATIVE_COLORIMETRIC, 372 | cmsFLAGS_NOCACHE); 373 | 374 | // Does create the backwards step 375 | Chain.hReverse = cmsCreateTransformTHR(ContextID, hGamut, dwFormat, 376 | hLab, TYPE_Lab_DBL, 377 | INTENT_RELATIVE_COLORIMETRIC, 378 | cmsFLAGS_NOCACHE); 379 | 380 | 381 | // All ok? 382 | if (Chain.hInput && Chain.hForward && Chain.hReverse) { 383 | 384 | // Go on, try to compute gamut LUT from PCS. This consist on a single channel containing 385 | // dE when doing a transform back and forth on the colorimetric intent. 386 | 387 | Gamut = cmsPipelineAlloc(ContextID, 3, 1); 388 | if (Gamut != NULL) { 389 | 390 | CLUT = cmsStageAllocCLut16bit(ContextID, nGridpoints, nChannels, 1, NULL); 391 | if (!cmsPipelineInsertStage(Gamut, cmsAT_BEGIN, CLUT)) { 392 | cmsPipelineFree(Gamut); 393 | Gamut = NULL; 394 | } 395 | else { 396 | cmsStageSampleCLut16bit(CLUT, GamutSampler, (void*) &Chain, 0); 397 | } 398 | } 399 | } 400 | else 401 | Gamut = NULL; // Didn't work... 402 | 403 | // Free all needed stuff. 404 | if (Chain.hInput) cmsDeleteTransform(Chain.hInput); 405 | if (Chain.hForward) cmsDeleteTransform(Chain.hForward); 406 | if (Chain.hReverse) cmsDeleteTransform(Chain.hReverse); 407 | if (hLab) cmsCloseProfile(hLab); 408 | 409 | // And return computed hull 410 | return Gamut; 411 | } 412 | 413 | // Total Area Coverage estimation ---------------------------------------------------------------- 414 | 415 | typedef struct { 416 | cmsUInt32Number nOutputChans; 417 | cmsHTRANSFORM hRoundTrip; 418 | cmsFloat32Number MaxTAC; 419 | cmsFloat32Number MaxInput[cmsMAXCHANNELS]; 420 | 421 | } cmsTACestimator; 422 | 423 | 424 | // This callback just accounts the maximum ink dropped in the given node. It does not populate any 425 | // memory, as the destination table is NULL. Its only purpose it to know the global maximum. 426 | static 427 | int EstimateTAC(CMSREGISTER const cmsUInt16Number In[], CMSREGISTER cmsUInt16Number Out[], CMSREGISTER void * Cargo) 428 | { 429 | cmsTACestimator* bp = (cmsTACestimator*) Cargo; 430 | cmsFloat32Number RoundTrip[cmsMAXCHANNELS]; 431 | cmsUInt32Number i; 432 | cmsFloat32Number Sum; 433 | 434 | 435 | // Evaluate the xform 436 | cmsDoTransform(bp->hRoundTrip, In, RoundTrip, 1); 437 | 438 | // All all amounts of ink 439 | for (Sum=0, i=0; i < bp ->nOutputChans; i++) 440 | Sum += RoundTrip[i]; 441 | 442 | // If above maximum, keep track of input values 443 | if (Sum > bp ->MaxTAC) { 444 | 445 | bp ->MaxTAC = Sum; 446 | 447 | for (i=0; i < bp ->nOutputChans; i++) { 448 | bp ->MaxInput[i] = In[i]; 449 | } 450 | } 451 | 452 | return TRUE; 453 | 454 | cmsUNUSED_PARAMETER(Out); 455 | } 456 | 457 | 458 | // Detect Total area coverage of the profile 459 | cmsFloat64Number CMSEXPORT cmsDetectTAC(cmsHPROFILE hProfile) 460 | { 461 | cmsTACestimator bp; 462 | cmsUInt32Number dwFormatter; 463 | cmsUInt32Number GridPoints[MAX_INPUT_DIMENSIONS]; 464 | cmsHPROFILE hLab; 465 | cmsContext ContextID = cmsGetProfileContextID(hProfile); 466 | 467 | // TAC only works on output profiles 468 | if (cmsGetDeviceClass(hProfile) != cmsSigOutputClass) { 469 | return 0; 470 | } 471 | 472 | // Create a fake formatter for result 473 | dwFormatter = cmsFormatterForColorspaceOfProfile(hProfile, 4, TRUE); 474 | 475 | bp.nOutputChans = T_CHANNELS(dwFormatter); 476 | bp.MaxTAC = 0; // Initial TAC is 0 477 | 478 | // for safety 479 | if (bp.nOutputChans >= cmsMAXCHANNELS) return 0; 480 | 481 | hLab = cmsCreateLab4ProfileTHR(ContextID, NULL); 482 | if (hLab == NULL) return 0; 483 | // Setup a roundtrip on perceptual intent in output profile for TAC estimation 484 | bp.hRoundTrip = cmsCreateTransformTHR(ContextID, hLab, TYPE_Lab_16, 485 | hProfile, dwFormatter, INTENT_PERCEPTUAL, cmsFLAGS_NOOPTIMIZE|cmsFLAGS_NOCACHE); 486 | 487 | cmsCloseProfile(hLab); 488 | if (bp.hRoundTrip == NULL) return 0; 489 | 490 | // For L* we only need black and white. For C* we need many points 491 | GridPoints[0] = 6; 492 | GridPoints[1] = 74; 493 | GridPoints[2] = 74; 494 | 495 | 496 | if (!cmsSliceSpace16(3, GridPoints, EstimateTAC, &bp)) { 497 | bp.MaxTAC = 0; 498 | } 499 | 500 | cmsDeleteTransform(bp.hRoundTrip); 501 | 502 | // Results in % 503 | return bp.MaxTAC; 504 | } 505 | 506 | 507 | // Carefully, clamp on CIELab space. 508 | 509 | cmsBool CMSEXPORT cmsDesaturateLab(cmsCIELab* Lab, 510 | double amax, double amin, 511 | double bmax, double bmin) 512 | { 513 | 514 | // Whole Luma surface to zero 515 | 516 | if (Lab -> L < 0) { 517 | 518 | Lab-> L = Lab->a = Lab-> b = 0.0; 519 | return FALSE; 520 | } 521 | 522 | // Clamp white, DISCARD HIGHLIGHTS. This is done 523 | // in such way because icc spec doesn't allow the 524 | // use of L>100 as a highlight means. 525 | 526 | if (Lab->L > 100) 527 | Lab -> L = 100; 528 | 529 | // Check out gamut prism, on a, b faces 530 | 531 | if (Lab -> a < amin || Lab->a > amax|| 532 | Lab -> b < bmin || Lab->b > bmax) { 533 | 534 | cmsCIELCh LCh; 535 | double h, slope; 536 | 537 | // Falls outside a, b limits. Transports to LCh space, 538 | // and then do the clipping 539 | 540 | 541 | if (Lab -> a == 0.0) { // Is hue exactly 90? 542 | 543 | // atan will not work, so clamp here 544 | Lab -> b = Lab->b < 0 ? bmin : bmax; 545 | return TRUE; 546 | } 547 | 548 | cmsLab2LCh(&LCh, Lab); 549 | 550 | slope = Lab -> b / Lab -> a; 551 | h = LCh.h; 552 | 553 | // There are 4 zones 554 | 555 | if ((h >= 0. && h < 45.) || 556 | (h >= 315 && h <= 360.)) { 557 | 558 | // clip by amax 559 | Lab -> a = amax; 560 | Lab -> b = amax * slope; 561 | } 562 | else 563 | if (h >= 45. && h < 135.) 564 | { 565 | // clip by bmax 566 | Lab -> b = bmax; 567 | Lab -> a = bmax / slope; 568 | } 569 | else 570 | if (h >= 135. && h < 225.) { 571 | // clip by amin 572 | Lab -> a = amin; 573 | Lab -> b = amin * slope; 574 | 575 | } 576 | else 577 | if (h >= 225. && h < 315.) { 578 | // clip by bmin 579 | Lab -> b = bmin; 580 | Lab -> a = bmin / slope; 581 | } 582 | else { 583 | cmsSignalError(0, cmsERROR_RANGE, "Invalid angle"); 584 | return FALSE; 585 | } 586 | 587 | } 588 | 589 | return TRUE; 590 | } 591 | -------------------------------------------------------------------------------- /ImageColoriser/Sources/Utils/littlecms/cmsmd5.c: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------------- 2 | // 3 | // Little Color Management System 4 | // Copyright (c) 1998-2020 Marti Maria Saguer 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining 7 | // a copy of this software and associated documentation files (the "Software"), 8 | // to deal in the Software without restriction, including without limitation 9 | // the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 | // and/or sell copies of the Software, and to permit persons to whom the Software 11 | // is furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 18 | // THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | // 24 | //--------------------------------------------------------------------------------- 25 | 26 | 27 | #include "lcms2_internal.h" 28 | 29 | #ifdef CMS_USE_BIG_ENDIAN 30 | 31 | static 32 | void byteReverse(cmsUInt8Number * buf, cmsUInt32Number longs) 33 | { 34 | do { 35 | 36 | cmsUInt32Number t = _cmsAdjustEndianess32(*(cmsUInt32Number *) buf); 37 | *(cmsUInt32Number *) buf = t; 38 | buf += sizeof(cmsUInt32Number); 39 | 40 | } while (--longs); 41 | 42 | } 43 | 44 | #else 45 | #define byteReverse(buf, len) 46 | #endif 47 | 48 | 49 | typedef struct { 50 | 51 | cmsUInt32Number buf[4]; 52 | cmsUInt32Number bits[2]; 53 | cmsUInt8Number in[64]; 54 | cmsContext ContextID; 55 | 56 | } _cmsMD5; 57 | 58 | #define F1(x, y, z) (z ^ (x & (y ^ z))) 59 | #define F2(x, y, z) F1(z, x, y) 60 | #define F3(x, y, z) (x ^ y ^ z) 61 | #define F4(x, y, z) (y ^ (x | ~z)) 62 | 63 | #define STEP(f, w, x, y, z, data, s) \ 64 | ( w += f(x, y, z) + data, w = w<>(32-s), w += x ) 65 | 66 | 67 | static 68 | void cmsMD5_Transform(cmsUInt32Number buf[4], cmsUInt32Number in[16]) 69 | { 70 | CMSREGISTER cmsUInt32Number a, b, c, d; 71 | 72 | a = buf[0]; 73 | b = buf[1]; 74 | c = buf[2]; 75 | d = buf[3]; 76 | 77 | STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7); 78 | STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12); 79 | STEP(F1, c, d, a, b, in[2] + 0x242070db, 17); 80 | STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22); 81 | STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7); 82 | STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12); 83 | STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17); 84 | STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22); 85 | STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7); 86 | STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12); 87 | STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17); 88 | STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22); 89 | STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7); 90 | STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12); 91 | STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17); 92 | STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22); 93 | 94 | STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5); 95 | STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9); 96 | STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14); 97 | STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20); 98 | STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5); 99 | STEP(F2, d, a, b, c, in[10] + 0x02441453, 9); 100 | STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14); 101 | STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20); 102 | STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5); 103 | STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9); 104 | STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14); 105 | STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20); 106 | STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5); 107 | STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9); 108 | STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14); 109 | STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20); 110 | 111 | STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4); 112 | STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11); 113 | STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16); 114 | STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23); 115 | STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4); 116 | STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11); 117 | STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16); 118 | STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23); 119 | STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4); 120 | STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11); 121 | STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16); 122 | STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23); 123 | STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4); 124 | STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11); 125 | STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16); 126 | STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23); 127 | 128 | STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6); 129 | STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10); 130 | STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15); 131 | STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21); 132 | STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6); 133 | STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10); 134 | STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15); 135 | STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21); 136 | STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6); 137 | STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10); 138 | STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15); 139 | STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21); 140 | STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6); 141 | STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10); 142 | STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15); 143 | STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21); 144 | 145 | buf[0] += a; 146 | buf[1] += b; 147 | buf[2] += c; 148 | buf[3] += d; 149 | } 150 | 151 | 152 | // Create a MD5 object 153 | 154 | cmsHANDLE CMSEXPORT cmsMD5alloc(cmsContext ContextID) 155 | { 156 | _cmsMD5* ctx = (_cmsMD5*) _cmsMallocZero(ContextID, sizeof(_cmsMD5)); 157 | if (ctx == NULL) return NULL; 158 | 159 | ctx ->ContextID = ContextID; 160 | 161 | ctx->buf[0] = 0x67452301; 162 | ctx->buf[1] = 0xefcdab89; 163 | ctx->buf[2] = 0x98badcfe; 164 | ctx->buf[3] = 0x10325476; 165 | 166 | ctx->bits[0] = 0; 167 | ctx->bits[1] = 0; 168 | 169 | return (cmsHANDLE) ctx; 170 | } 171 | 172 | void CMSEXPORT cmsMD5add(cmsHANDLE Handle, const cmsUInt8Number* buf, cmsUInt32Number len) 173 | { 174 | _cmsMD5* ctx = (_cmsMD5*) Handle; 175 | cmsUInt32Number t; 176 | 177 | t = ctx->bits[0]; 178 | if ((ctx->bits[0] = t + (len << 3)) < t) 179 | ctx->bits[1]++; 180 | 181 | ctx->bits[1] += len >> 29; 182 | 183 | t = (t >> 3) & 0x3f; 184 | 185 | if (t) { 186 | 187 | cmsUInt8Number *p = (cmsUInt8Number *) ctx->in + t; 188 | 189 | t = 64 - t; 190 | if (len < t) { 191 | memmove(p, buf, len); 192 | return; 193 | } 194 | 195 | memmove(p, buf, t); 196 | byteReverse(ctx->in, 16); 197 | 198 | cmsMD5_Transform(ctx->buf, (cmsUInt32Number *) ctx->in); 199 | buf += t; 200 | len -= t; 201 | } 202 | 203 | while (len >= 64) { 204 | memmove(ctx->in, buf, 64); 205 | byteReverse(ctx->in, 16); 206 | cmsMD5_Transform(ctx->buf, (cmsUInt32Number *) ctx->in); 207 | buf += 64; 208 | len -= 64; 209 | } 210 | 211 | memmove(ctx->in, buf, len); 212 | } 213 | 214 | // Destroy the object and return the checksum 215 | void CMSEXPORT cmsMD5finish(cmsProfileID* ProfileID, cmsHANDLE Handle) 216 | { 217 | _cmsMD5* ctx = (_cmsMD5*) Handle; 218 | cmsUInt32Number count; 219 | cmsUInt8Number *p; 220 | 221 | count = (ctx->bits[0] >> 3) & 0x3F; 222 | 223 | p = ctx->in + count; 224 | *p++ = 0x80; 225 | 226 | count = 64 - 1 - count; 227 | 228 | if (count < 8) { 229 | 230 | memset(p, 0, count); 231 | byteReverse(ctx->in, 16); 232 | cmsMD5_Transform(ctx->buf, (cmsUInt32Number *) ctx->in); 233 | 234 | memset(ctx->in, 0, 56); 235 | } else { 236 | memset(p, 0, count - 8); 237 | } 238 | byteReverse(ctx->in, 14); 239 | 240 | ((cmsUInt32Number *) ctx->in)[14] = ctx->bits[0]; 241 | ((cmsUInt32Number *) ctx->in)[15] = ctx->bits[1]; 242 | 243 | cmsMD5_Transform(ctx->buf, (cmsUInt32Number *) ctx->in); 244 | 245 | byteReverse((cmsUInt8Number *) ctx->buf, 4); 246 | memmove(ProfileID ->ID8, ctx->buf, 16); 247 | 248 | _cmsFree(ctx ->ContextID, ctx); 249 | } 250 | 251 | 252 | 253 | // Assuming io points to an ICC profile, compute and store MD5 checksum 254 | // In the header, rendering intentent, attributes and ID should be set to zero 255 | // before computing MD5 checksum (per 6.1.13 in ICC spec) 256 | 257 | cmsBool CMSEXPORT cmsMD5computeID(cmsHPROFILE hProfile) 258 | { 259 | cmsContext ContextID; 260 | cmsUInt32Number BytesNeeded; 261 | cmsUInt8Number* Mem = NULL; 262 | cmsHANDLE MD5 = NULL; 263 | _cmsICCPROFILE* Icc = (_cmsICCPROFILE*) hProfile; 264 | _cmsICCPROFILE Keep; 265 | 266 | _cmsAssert(hProfile != NULL); 267 | 268 | ContextID = cmsGetProfileContextID(hProfile); 269 | 270 | // Save a copy of the profile header 271 | memmove(&Keep, Icc, sizeof(_cmsICCPROFILE)); 272 | 273 | // Set RI, attributes and ID 274 | memset(&Icc ->attributes, 0, sizeof(Icc ->attributes)); 275 | Icc ->RenderingIntent = 0; 276 | memset(&Icc ->ProfileID, 0, sizeof(Icc ->ProfileID)); 277 | 278 | // Compute needed storage 279 | if (!cmsSaveProfileToMem(hProfile, NULL, &BytesNeeded)) goto Error; 280 | 281 | // Allocate memory 282 | Mem = (cmsUInt8Number*) _cmsMalloc(ContextID, BytesNeeded); 283 | if (Mem == NULL) goto Error; 284 | 285 | // Save to temporary storage 286 | if (!cmsSaveProfileToMem(hProfile, Mem, &BytesNeeded)) goto Error; 287 | 288 | // Create MD5 object 289 | MD5 = cmsMD5alloc(ContextID); 290 | if (MD5 == NULL) goto Error; 291 | 292 | // Add all bytes 293 | cmsMD5add(MD5, Mem, BytesNeeded); 294 | 295 | // Temp storage is no longer needed 296 | _cmsFree(ContextID, Mem); 297 | 298 | // Restore header 299 | memmove(Icc, &Keep, sizeof(_cmsICCPROFILE)); 300 | 301 | // And store the ID 302 | cmsMD5finish(&Icc ->ProfileID, MD5); 303 | return TRUE; 304 | 305 | Error: 306 | 307 | // Free resources as something went wrong 308 | // "MD5" cannot be other than NULL here, so no need to free it 309 | if (Mem != NULL) _cmsFree(ContextID, Mem); 310 | memmove(Icc, &Keep, sizeof(_cmsICCPROFILE)); 311 | return FALSE; 312 | } 313 | 314 | -------------------------------------------------------------------------------- /ImageColoriser/Sources/Utils/littlecms/cmsmtrx.c: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------------- 2 | // 3 | // Little Color Management System 4 | // Copyright (c) 1998-2020 Marti Maria Saguer 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining 7 | // a copy of this software and associated documentation files (the "Software"), 8 | // to deal in the Software without restriction, including without limitation 9 | // the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 | // and/or sell copies of the Software, and to permit persons to whom the Software 11 | // is furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 18 | // THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | // 24 | //--------------------------------------------------------------------------------- 25 | // 26 | 27 | #include "lcms2_internal.h" 28 | 29 | 30 | #define DSWAP(x, y) {cmsFloat64Number tmp = (x); (x)=(y); (y)=tmp;} 31 | 32 | 33 | // Initiate a vector 34 | void CMSEXPORT _cmsVEC3init(cmsVEC3* r, cmsFloat64Number x, cmsFloat64Number y, cmsFloat64Number z) 35 | { 36 | r -> n[VX] = x; 37 | r -> n[VY] = y; 38 | r -> n[VZ] = z; 39 | } 40 | 41 | // Vector subtraction 42 | void CMSEXPORT _cmsVEC3minus(cmsVEC3* r, const cmsVEC3* a, const cmsVEC3* b) 43 | { 44 | r -> n[VX] = a -> n[VX] - b -> n[VX]; 45 | r -> n[VY] = a -> n[VY] - b -> n[VY]; 46 | r -> n[VZ] = a -> n[VZ] - b -> n[VZ]; 47 | } 48 | 49 | // Vector cross product 50 | void CMSEXPORT _cmsVEC3cross(cmsVEC3* r, const cmsVEC3* u, const cmsVEC3* v) 51 | { 52 | r ->n[VX] = u->n[VY] * v->n[VZ] - v->n[VY] * u->n[VZ]; 53 | r ->n[VY] = u->n[VZ] * v->n[VX] - v->n[VZ] * u->n[VX]; 54 | r ->n[VZ] = u->n[VX] * v->n[VY] - v->n[VX] * u->n[VY]; 55 | } 56 | 57 | // Vector dot product 58 | cmsFloat64Number CMSEXPORT _cmsVEC3dot(const cmsVEC3* u, const cmsVEC3* v) 59 | { 60 | return u->n[VX] * v->n[VX] + u->n[VY] * v->n[VY] + u->n[VZ] * v->n[VZ]; 61 | } 62 | 63 | // Euclidean length 64 | cmsFloat64Number CMSEXPORT _cmsVEC3length(const cmsVEC3* a) 65 | { 66 | return sqrt(a ->n[VX] * a ->n[VX] + 67 | a ->n[VY] * a ->n[VY] + 68 | a ->n[VZ] * a ->n[VZ]); 69 | } 70 | 71 | // Euclidean distance 72 | cmsFloat64Number CMSEXPORT _cmsVEC3distance(const cmsVEC3* a, const cmsVEC3* b) 73 | { 74 | cmsFloat64Number d1 = a ->n[VX] - b ->n[VX]; 75 | cmsFloat64Number d2 = a ->n[VY] - b ->n[VY]; 76 | cmsFloat64Number d3 = a ->n[VZ] - b ->n[VZ]; 77 | 78 | return sqrt(d1*d1 + d2*d2 + d3*d3); 79 | } 80 | 81 | 82 | 83 | // 3x3 Identity 84 | void CMSEXPORT _cmsMAT3identity(cmsMAT3* a) 85 | { 86 | _cmsVEC3init(&a-> v[0], 1.0, 0.0, 0.0); 87 | _cmsVEC3init(&a-> v[1], 0.0, 1.0, 0.0); 88 | _cmsVEC3init(&a-> v[2], 0.0, 0.0, 1.0); 89 | } 90 | 91 | static 92 | cmsBool CloseEnough(cmsFloat64Number a, cmsFloat64Number b) 93 | { 94 | return fabs(b - a) < (1.0 / 65535.0); 95 | } 96 | 97 | 98 | cmsBool CMSEXPORT _cmsMAT3isIdentity(const cmsMAT3* a) 99 | { 100 | cmsMAT3 Identity; 101 | int i, j; 102 | 103 | _cmsMAT3identity(&Identity); 104 | 105 | for (i=0; i < 3; i++) 106 | for (j=0; j < 3; j++) 107 | if (!CloseEnough(a ->v[i].n[j], Identity.v[i].n[j])) return FALSE; 108 | 109 | return TRUE; 110 | } 111 | 112 | 113 | // Multiply two matrices 114 | void CMSEXPORT _cmsMAT3per(cmsMAT3* r, const cmsMAT3* a, const cmsMAT3* b) 115 | { 116 | #define ROWCOL(i, j) \ 117 | a->v[i].n[0]*b->v[0].n[j] + a->v[i].n[1]*b->v[1].n[j] + a->v[i].n[2]*b->v[2].n[j] 118 | 119 | _cmsVEC3init(&r-> v[0], ROWCOL(0,0), ROWCOL(0,1), ROWCOL(0,2)); 120 | _cmsVEC3init(&r-> v[1], ROWCOL(1,0), ROWCOL(1,1), ROWCOL(1,2)); 121 | _cmsVEC3init(&r-> v[2], ROWCOL(2,0), ROWCOL(2,1), ROWCOL(2,2)); 122 | 123 | #undef ROWCOL //(i, j) 124 | } 125 | 126 | 127 | 128 | // Inverse of a matrix b = a^(-1) 129 | cmsBool CMSEXPORT _cmsMAT3inverse(const cmsMAT3* a, cmsMAT3* b) 130 | { 131 | cmsFloat64Number det, c0, c1, c2; 132 | 133 | c0 = a -> v[1].n[1]*a -> v[2].n[2] - a -> v[1].n[2]*a -> v[2].n[1]; 134 | c1 = -a -> v[1].n[0]*a -> v[2].n[2] + a -> v[1].n[2]*a -> v[2].n[0]; 135 | c2 = a -> v[1].n[0]*a -> v[2].n[1] - a -> v[1].n[1]*a -> v[2].n[0]; 136 | 137 | det = a -> v[0].n[0]*c0 + a -> v[0].n[1]*c1 + a -> v[0].n[2]*c2; 138 | 139 | if (fabs(det) < MATRIX_DET_TOLERANCE) return FALSE; // singular matrix; can't invert 140 | 141 | b -> v[0].n[0] = c0/det; 142 | b -> v[0].n[1] = (a -> v[0].n[2]*a -> v[2].n[1] - a -> v[0].n[1]*a -> v[2].n[2])/det; 143 | b -> v[0].n[2] = (a -> v[0].n[1]*a -> v[1].n[2] - a -> v[0].n[2]*a -> v[1].n[1])/det; 144 | b -> v[1].n[0] = c1/det; 145 | b -> v[1].n[1] = (a -> v[0].n[0]*a -> v[2].n[2] - a -> v[0].n[2]*a -> v[2].n[0])/det; 146 | b -> v[1].n[2] = (a -> v[0].n[2]*a -> v[1].n[0] - a -> v[0].n[0]*a -> v[1].n[2])/det; 147 | b -> v[2].n[0] = c2/det; 148 | b -> v[2].n[1] = (a -> v[0].n[1]*a -> v[2].n[0] - a -> v[0].n[0]*a -> v[2].n[1])/det; 149 | b -> v[2].n[2] = (a -> v[0].n[0]*a -> v[1].n[1] - a -> v[0].n[1]*a -> v[1].n[0])/det; 150 | 151 | return TRUE; 152 | } 153 | 154 | 155 | // Solve a system in the form Ax = b 156 | cmsBool CMSEXPORT _cmsMAT3solve(cmsVEC3* x, cmsMAT3* a, cmsVEC3* b) 157 | { 158 | cmsMAT3 m, a_1; 159 | 160 | memmove(&m, a, sizeof(cmsMAT3)); 161 | 162 | if (!_cmsMAT3inverse(&m, &a_1)) return FALSE; // Singular matrix 163 | 164 | _cmsMAT3eval(x, &a_1, b); 165 | return TRUE; 166 | } 167 | 168 | // Evaluate a vector across a matrix 169 | void CMSEXPORT _cmsMAT3eval(cmsVEC3* r, const cmsMAT3* a, const cmsVEC3* v) 170 | { 171 | r->n[VX] = a->v[0].n[VX]*v->n[VX] + a->v[0].n[VY]*v->n[VY] + a->v[0].n[VZ]*v->n[VZ]; 172 | r->n[VY] = a->v[1].n[VX]*v->n[VX] + a->v[1].n[VY]*v->n[VY] + a->v[1].n[VZ]*v->n[VZ]; 173 | r->n[VZ] = a->v[2].n[VX]*v->n[VX] + a->v[2].n[VY]*v->n[VY] + a->v[2].n[VZ]*v->n[VZ]; 174 | } 175 | 176 | 177 | -------------------------------------------------------------------------------- /ImageColoriser/Sources/Utils/littlecms/cmssamp.c: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------------- 2 | // 3 | // Little Color Management System 4 | // Copyright (c) 1998-2020 Marti Maria Saguer 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining 7 | // a copy of this software and associated documentation files (the "Software"), 8 | // to deal in the Software without restriction, including without limitation 9 | // the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 | // and/or sell copies of the Software, and to permit persons to whom the Software 11 | // is furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 18 | // THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | // 24 | //--------------------------------------------------------------------------------- 25 | // 26 | 27 | #include "lcms2_internal.h" 28 | 29 | 30 | #define cmsmin(a, b) (((a) < (b)) ? (a) : (b)) 31 | #define cmsmax(a, b) (((a) > (b)) ? (a) : (b)) 32 | 33 | // This file contains routines for resampling and LUT optimization, black point detection 34 | // and black preservation. 35 | 36 | // Black point detection ------------------------------------------------------------------------- 37 | 38 | 39 | // PCS -> PCS round trip transform, always uses relative intent on the device -> pcs 40 | static 41 | cmsHTRANSFORM CreateRoundtripXForm(cmsHPROFILE hProfile, cmsUInt32Number nIntent) 42 | { 43 | cmsContext ContextID = cmsGetProfileContextID(hProfile); 44 | cmsHPROFILE hLab = cmsCreateLab4ProfileTHR(ContextID, NULL); 45 | cmsHTRANSFORM xform; 46 | cmsBool BPC[4] = { FALSE, FALSE, FALSE, FALSE }; 47 | cmsFloat64Number States[4] = { 1.0, 1.0, 1.0, 1.0 }; 48 | cmsHPROFILE hProfiles[4]; 49 | cmsUInt32Number Intents[4]; 50 | 51 | hProfiles[0] = hLab; hProfiles[1] = hProfile; hProfiles[2] = hProfile; hProfiles[3] = hLab; 52 | Intents[0] = INTENT_RELATIVE_COLORIMETRIC; Intents[1] = nIntent; Intents[2] = INTENT_RELATIVE_COLORIMETRIC; Intents[3] = INTENT_RELATIVE_COLORIMETRIC; 53 | 54 | xform = cmsCreateExtendedTransform(ContextID, 4, hProfiles, BPC, Intents, 55 | States, NULL, 0, TYPE_Lab_DBL, TYPE_Lab_DBL, cmsFLAGS_NOCACHE|cmsFLAGS_NOOPTIMIZE); 56 | 57 | cmsCloseProfile(hLab); 58 | return xform; 59 | } 60 | 61 | // Use darker colorants to obtain black point. This works in the relative colorimetric intent and 62 | // assumes more ink results in darker colors. No ink limit is assumed. 63 | static 64 | cmsBool BlackPointAsDarkerColorant(cmsHPROFILE hInput, 65 | cmsUInt32Number Intent, 66 | cmsCIEXYZ* BlackPoint, 67 | cmsUInt32Number dwFlags) 68 | { 69 | cmsUInt16Number *Black; 70 | cmsHTRANSFORM xform; 71 | cmsColorSpaceSignature Space; 72 | cmsUInt32Number nChannels; 73 | cmsUInt32Number dwFormat; 74 | cmsHPROFILE hLab; 75 | cmsCIELab Lab; 76 | cmsCIEXYZ BlackXYZ; 77 | cmsContext ContextID = cmsGetProfileContextID(hInput); 78 | 79 | // If the profile does not support input direction, assume Black point 0 80 | if (!cmsIsIntentSupported(hInput, Intent, LCMS_USED_AS_INPUT)) { 81 | 82 | BlackPoint -> X = BlackPoint ->Y = BlackPoint -> Z = 0.0; 83 | return FALSE; 84 | } 85 | 86 | // Create a formatter which has n channels and floating point 87 | dwFormat = cmsFormatterForColorspaceOfProfile(hInput, 2, FALSE); 88 | 89 | // Try to get black by using black colorant 90 | Space = cmsGetColorSpace(hInput); 91 | 92 | // This function returns darker colorant in 16 bits for several spaces 93 | if (!_cmsEndPointsBySpace(Space, NULL, &Black, &nChannels)) { 94 | 95 | BlackPoint -> X = BlackPoint ->Y = BlackPoint -> Z = 0.0; 96 | return FALSE; 97 | } 98 | 99 | if (nChannels != T_CHANNELS(dwFormat)) { 100 | BlackPoint -> X = BlackPoint ->Y = BlackPoint -> Z = 0.0; 101 | return FALSE; 102 | } 103 | 104 | // Lab will be used as the output space, but lab2 will avoid recursion 105 | hLab = cmsCreateLab2ProfileTHR(ContextID, NULL); 106 | if (hLab == NULL) { 107 | BlackPoint -> X = BlackPoint ->Y = BlackPoint -> Z = 0.0; 108 | return FALSE; 109 | } 110 | 111 | // Create the transform 112 | xform = cmsCreateTransformTHR(ContextID, hInput, dwFormat, 113 | hLab, TYPE_Lab_DBL, Intent, cmsFLAGS_NOOPTIMIZE|cmsFLAGS_NOCACHE); 114 | cmsCloseProfile(hLab); 115 | 116 | if (xform == NULL) { 117 | 118 | // Something went wrong. Get rid of open resources and return zero as black 119 | BlackPoint -> X = BlackPoint ->Y = BlackPoint -> Z = 0.0; 120 | return FALSE; 121 | } 122 | 123 | // Convert black to Lab 124 | cmsDoTransform(xform, Black, &Lab, 1); 125 | 126 | // Force it to be neutral, clip to max. L* of 50 127 | Lab.a = Lab.b = 0; 128 | if (Lab.L > 50) Lab.L = 50; 129 | 130 | // Free the resources 131 | cmsDeleteTransform(xform); 132 | 133 | // Convert from Lab (which is now clipped) to XYZ. 134 | cmsLab2XYZ(NULL, &BlackXYZ, &Lab); 135 | 136 | if (BlackPoint != NULL) 137 | *BlackPoint = BlackXYZ; 138 | 139 | return TRUE; 140 | 141 | cmsUNUSED_PARAMETER(dwFlags); 142 | } 143 | 144 | // Get a black point of output CMYK profile, discounting any ink-limiting embedded 145 | // in the profile. For doing that, we use perceptual intent in input direction: 146 | // Lab (0, 0, 0) -> [Perceptual] Profile -> CMYK -> [Rel. colorimetric] Profile -> Lab 147 | static 148 | cmsBool BlackPointUsingPerceptualBlack(cmsCIEXYZ* BlackPoint, cmsHPROFILE hProfile) 149 | { 150 | cmsHTRANSFORM hRoundTrip; 151 | cmsCIELab LabIn, LabOut; 152 | cmsCIEXYZ BlackXYZ; 153 | 154 | // Is the intent supported by the profile? 155 | if (!cmsIsIntentSupported(hProfile, INTENT_PERCEPTUAL, LCMS_USED_AS_INPUT)) { 156 | 157 | BlackPoint -> X = BlackPoint ->Y = BlackPoint -> Z = 0.0; 158 | return TRUE; 159 | } 160 | 161 | hRoundTrip = CreateRoundtripXForm(hProfile, INTENT_PERCEPTUAL); 162 | if (hRoundTrip == NULL) { 163 | BlackPoint -> X = BlackPoint ->Y = BlackPoint -> Z = 0.0; 164 | return FALSE; 165 | } 166 | 167 | LabIn.L = LabIn.a = LabIn.b = 0; 168 | cmsDoTransform(hRoundTrip, &LabIn, &LabOut, 1); 169 | 170 | // Clip Lab to reasonable limits 171 | if (LabOut.L > 50) LabOut.L = 50; 172 | LabOut.a = LabOut.b = 0; 173 | 174 | cmsDeleteTransform(hRoundTrip); 175 | 176 | // Convert it to XYZ 177 | cmsLab2XYZ(NULL, &BlackXYZ, &LabOut); 178 | 179 | if (BlackPoint != NULL) 180 | *BlackPoint = BlackXYZ; 181 | 182 | return TRUE; 183 | } 184 | 185 | // This function shouldn't exist at all -- there is such quantity of broken 186 | // profiles on black point tag, that we must somehow fix chromaticity to 187 | // avoid huge tint when doing Black point compensation. This function does 188 | // just that. There is a special flag for using black point tag, but turned 189 | // off by default because it is bogus on most profiles. The detection algorithm 190 | // involves to turn BP to neutral and to use only L component. 191 | cmsBool CMSEXPORT cmsDetectBlackPoint(cmsCIEXYZ* BlackPoint, cmsHPROFILE hProfile, cmsUInt32Number Intent, cmsUInt32Number dwFlags) 192 | { 193 | cmsProfileClassSignature devClass; 194 | 195 | // Make sure the device class is adequate 196 | devClass = cmsGetDeviceClass(hProfile); 197 | if (devClass == cmsSigLinkClass || 198 | devClass == cmsSigAbstractClass || 199 | devClass == cmsSigNamedColorClass) { 200 | BlackPoint -> X = BlackPoint ->Y = BlackPoint -> Z = 0.0; 201 | return FALSE; 202 | } 203 | 204 | // Make sure intent is adequate 205 | if (Intent != INTENT_PERCEPTUAL && 206 | Intent != INTENT_RELATIVE_COLORIMETRIC && 207 | Intent != INTENT_SATURATION) { 208 | BlackPoint -> X = BlackPoint ->Y = BlackPoint -> Z = 0.0; 209 | return FALSE; 210 | } 211 | 212 | // v4 + perceptual & saturation intents does have its own black point, and it is 213 | // well specified enough to use it. Black point tag is deprecated in V4. 214 | if ((cmsGetEncodedICCversion(hProfile) >= 0x4000000) && 215 | (Intent == INTENT_PERCEPTUAL || Intent == INTENT_SATURATION)) { 216 | 217 | // Matrix shaper share MRC & perceptual intents 218 | if (cmsIsMatrixShaper(hProfile)) 219 | return BlackPointAsDarkerColorant(hProfile, INTENT_RELATIVE_COLORIMETRIC, BlackPoint, 0); 220 | 221 | // Get Perceptual black out of v4 profiles. That is fixed for perceptual & saturation intents 222 | BlackPoint -> X = cmsPERCEPTUAL_BLACK_X; 223 | BlackPoint -> Y = cmsPERCEPTUAL_BLACK_Y; 224 | BlackPoint -> Z = cmsPERCEPTUAL_BLACK_Z; 225 | 226 | return TRUE; 227 | } 228 | 229 | 230 | #ifdef CMS_USE_PROFILE_BLACK_POINT_TAG 231 | 232 | // v2, v4 rel/abs colorimetric 233 | if (cmsIsTag(hProfile, cmsSigMediaBlackPointTag) && 234 | Intent == INTENT_RELATIVE_COLORIMETRIC) { 235 | 236 | cmsCIEXYZ *BlackPtr, BlackXYZ, UntrustedBlackPoint, TrustedBlackPoint, MediaWhite; 237 | cmsCIELab Lab; 238 | 239 | // If black point is specified, then use it, 240 | 241 | BlackPtr = cmsReadTag(hProfile, cmsSigMediaBlackPointTag); 242 | if (BlackPtr != NULL) { 243 | 244 | BlackXYZ = *BlackPtr; 245 | _cmsReadMediaWhitePoint(&MediaWhite, hProfile); 246 | 247 | // Black point is absolute XYZ, so adapt to D50 to get PCS value 248 | cmsAdaptToIlluminant(&UntrustedBlackPoint, &MediaWhite, cmsD50_XYZ(), &BlackXYZ); 249 | 250 | // Force a=b=0 to get rid of any chroma 251 | cmsXYZ2Lab(NULL, &Lab, &UntrustedBlackPoint); 252 | Lab.a = Lab.b = 0; 253 | if (Lab.L > 50) Lab.L = 50; // Clip to L* <= 50 254 | cmsLab2XYZ(NULL, &TrustedBlackPoint, &Lab); 255 | 256 | if (BlackPoint != NULL) 257 | *BlackPoint = TrustedBlackPoint; 258 | 259 | return TRUE; 260 | } 261 | } 262 | #endif 263 | 264 | // That is about v2 profiles. 265 | 266 | // If output profile, discount ink-limiting and that's all 267 | if (Intent == INTENT_RELATIVE_COLORIMETRIC && 268 | (cmsGetDeviceClass(hProfile) == cmsSigOutputClass) && 269 | (cmsGetColorSpace(hProfile) == cmsSigCmykData)) 270 | return BlackPointUsingPerceptualBlack(BlackPoint, hProfile); 271 | 272 | // Nope, compute BP using current intent. 273 | return BlackPointAsDarkerColorant(hProfile, Intent, BlackPoint, dwFlags); 274 | } 275 | 276 | 277 | 278 | // --------------------------------------------------------------------------------------------------------- 279 | 280 | // Least Squares Fit of a Quadratic Curve to Data 281 | // http://www.personal.psu.edu/jhm/f90/lectures/lsq2.html 282 | 283 | static 284 | cmsFloat64Number RootOfLeastSquaresFitQuadraticCurve(int n, cmsFloat64Number x[], cmsFloat64Number y[]) 285 | { 286 | double sum_x = 0, sum_x2 = 0, sum_x3 = 0, sum_x4 = 0; 287 | double sum_y = 0, sum_yx = 0, sum_yx2 = 0; 288 | double d, a, b, c; 289 | int i; 290 | cmsMAT3 m; 291 | cmsVEC3 v, res; 292 | 293 | if (n < 4) return 0; 294 | 295 | for (i=0; i < n; i++) { 296 | 297 | double xn = x[i]; 298 | double yn = y[i]; 299 | 300 | sum_x += xn; 301 | sum_x2 += xn*xn; 302 | sum_x3 += xn*xn*xn; 303 | sum_x4 += xn*xn*xn*xn; 304 | 305 | sum_y += yn; 306 | sum_yx += yn*xn; 307 | sum_yx2 += yn*xn*xn; 308 | } 309 | 310 | _cmsVEC3init(&m.v[0], n, sum_x, sum_x2); 311 | _cmsVEC3init(&m.v[1], sum_x, sum_x2, sum_x3); 312 | _cmsVEC3init(&m.v[2], sum_x2, sum_x3, sum_x4); 313 | 314 | _cmsVEC3init(&v, sum_y, sum_yx, sum_yx2); 315 | 316 | if (!_cmsMAT3solve(&res, &m, &v)) return 0; 317 | 318 | 319 | a = res.n[2]; 320 | b = res.n[1]; 321 | c = res.n[0]; 322 | 323 | if (fabs(a) < 1.0E-10) { 324 | 325 | return cmsmin(0, cmsmax(50, -c/b )); 326 | } 327 | else { 328 | 329 | d = b*b - 4.0 * a * c; 330 | if (d <= 0) { 331 | return 0; 332 | } 333 | else { 334 | 335 | double rt = (-b + sqrt(d)) / (2.0 * a); 336 | 337 | return cmsmax(0, cmsmin(50, rt)); 338 | } 339 | } 340 | 341 | } 342 | 343 | 344 | 345 | // Calculates the black point of a destination profile. 346 | // This algorithm comes from the Adobe paper disclosing its black point compensation method. 347 | cmsBool CMSEXPORT cmsDetectDestinationBlackPoint(cmsCIEXYZ* BlackPoint, cmsHPROFILE hProfile, cmsUInt32Number Intent, cmsUInt32Number dwFlags) 348 | { 349 | cmsColorSpaceSignature ColorSpace; 350 | cmsHTRANSFORM hRoundTrip = NULL; 351 | cmsCIELab InitialLab, destLab, Lab; 352 | cmsFloat64Number inRamp[256], outRamp[256]; 353 | cmsFloat64Number MinL, MaxL; 354 | cmsBool NearlyStraightMidrange = TRUE; 355 | cmsFloat64Number yRamp[256]; 356 | cmsFloat64Number x[256], y[256]; 357 | cmsFloat64Number lo, hi; 358 | int n, l; 359 | cmsProfileClassSignature devClass; 360 | 361 | // Make sure the device class is adequate 362 | devClass = cmsGetDeviceClass(hProfile); 363 | if (devClass == cmsSigLinkClass || 364 | devClass == cmsSigAbstractClass || 365 | devClass == cmsSigNamedColorClass) { 366 | BlackPoint -> X = BlackPoint ->Y = BlackPoint -> Z = 0.0; 367 | return FALSE; 368 | } 369 | 370 | // Make sure intent is adequate 371 | if (Intent != INTENT_PERCEPTUAL && 372 | Intent != INTENT_RELATIVE_COLORIMETRIC && 373 | Intent != INTENT_SATURATION) { 374 | BlackPoint -> X = BlackPoint ->Y = BlackPoint -> Z = 0.0; 375 | return FALSE; 376 | } 377 | 378 | 379 | // v4 + perceptual & saturation intents does have its own black point, and it is 380 | // well specified enough to use it. Black point tag is deprecated in V4. 381 | if ((cmsGetEncodedICCversion(hProfile) >= 0x4000000) && 382 | (Intent == INTENT_PERCEPTUAL || Intent == INTENT_SATURATION)) { 383 | 384 | // Matrix shaper share MRC & perceptual intents 385 | if (cmsIsMatrixShaper(hProfile)) 386 | return BlackPointAsDarkerColorant(hProfile, INTENT_RELATIVE_COLORIMETRIC, BlackPoint, 0); 387 | 388 | // Get Perceptual black out of v4 profiles. That is fixed for perceptual & saturation intents 389 | BlackPoint -> X = cmsPERCEPTUAL_BLACK_X; 390 | BlackPoint -> Y = cmsPERCEPTUAL_BLACK_Y; 391 | BlackPoint -> Z = cmsPERCEPTUAL_BLACK_Z; 392 | return TRUE; 393 | } 394 | 395 | 396 | // Check if the profile is lut based and gray, rgb or cmyk (7.2 in Adobe's document) 397 | ColorSpace = cmsGetColorSpace(hProfile); 398 | if (!cmsIsCLUT(hProfile, Intent, LCMS_USED_AS_OUTPUT ) || 399 | (ColorSpace != cmsSigGrayData && 400 | ColorSpace != cmsSigRgbData && 401 | ColorSpace != cmsSigCmykData)) { 402 | 403 | // In this case, handle as input case 404 | return cmsDetectBlackPoint(BlackPoint, hProfile, Intent, dwFlags); 405 | } 406 | 407 | // It is one of the valid cases!, use Adobe algorithm 408 | 409 | 410 | // Set a first guess, that should work on good profiles. 411 | if (Intent == INTENT_RELATIVE_COLORIMETRIC) { 412 | 413 | cmsCIEXYZ IniXYZ; 414 | 415 | // calculate initial Lab as source black point 416 | if (!cmsDetectBlackPoint(&IniXYZ, hProfile, Intent, dwFlags)) { 417 | return FALSE; 418 | } 419 | 420 | // convert the XYZ to lab 421 | cmsXYZ2Lab(NULL, &InitialLab, &IniXYZ); 422 | 423 | } else { 424 | 425 | // set the initial Lab to zero, that should be the black point for perceptual and saturation 426 | InitialLab.L = 0; 427 | InitialLab.a = 0; 428 | InitialLab.b = 0; 429 | } 430 | 431 | 432 | // Step 2 433 | // ====== 434 | 435 | // Create a roundtrip. Define a Transform BT for all x in L*a*b* 436 | hRoundTrip = CreateRoundtripXForm(hProfile, Intent); 437 | if (hRoundTrip == NULL) return FALSE; 438 | 439 | // Compute ramps 440 | 441 | for (l=0; l < 256; l++) { 442 | 443 | Lab.L = (cmsFloat64Number) (l * 100.0) / 255.0; 444 | Lab.a = cmsmin(50, cmsmax(-50, InitialLab.a)); 445 | Lab.b = cmsmin(50, cmsmax(-50, InitialLab.b)); 446 | 447 | cmsDoTransform(hRoundTrip, &Lab, &destLab, 1); 448 | 449 | inRamp[l] = Lab.L; 450 | outRamp[l] = destLab.L; 451 | } 452 | 453 | // Make monotonic 454 | for (l = 254; l > 0; --l) { 455 | outRamp[l] = cmsmin(outRamp[l], outRamp[l+1]); 456 | } 457 | 458 | // Check 459 | if (! (outRamp[0] < outRamp[255])) { 460 | 461 | cmsDeleteTransform(hRoundTrip); 462 | BlackPoint -> X = BlackPoint ->Y = BlackPoint -> Z = 0.0; 463 | return FALSE; 464 | } 465 | 466 | 467 | // Test for mid range straight (only on relative colorimetric) 468 | NearlyStraightMidrange = TRUE; 469 | MinL = outRamp[0]; MaxL = outRamp[255]; 470 | if (Intent == INTENT_RELATIVE_COLORIMETRIC) { 471 | 472 | for (l=0; l < 256; l++) { 473 | 474 | if (! ((inRamp[l] <= MinL + 0.2 * (MaxL - MinL) ) || 475 | (fabs(inRamp[l] - outRamp[l]) < 4.0 ))) 476 | NearlyStraightMidrange = FALSE; 477 | } 478 | 479 | // If the mid range is straight (as determined above) then the 480 | // DestinationBlackPoint shall be the same as initialLab. 481 | // Otherwise, the DestinationBlackPoint shall be determined 482 | // using curve fitting. 483 | if (NearlyStraightMidrange) { 484 | 485 | cmsLab2XYZ(NULL, BlackPoint, &InitialLab); 486 | cmsDeleteTransform(hRoundTrip); 487 | return TRUE; 488 | } 489 | } 490 | 491 | 492 | // curve fitting: The round-trip curve normally looks like a nearly constant section at the black point, 493 | // with a corner and a nearly straight line to the white point. 494 | for (l=0; l < 256; l++) { 495 | 496 | yRamp[l] = (outRamp[l] - MinL) / (MaxL - MinL); 497 | } 498 | 499 | // find the black point using the least squares error quadratic curve fitting 500 | if (Intent == INTENT_RELATIVE_COLORIMETRIC) { 501 | lo = 0.1; 502 | hi = 0.5; 503 | } 504 | else { 505 | 506 | // Perceptual and saturation 507 | lo = 0.03; 508 | hi = 0.25; 509 | } 510 | 511 | // Capture shadow points for the fitting. 512 | n = 0; 513 | for (l=0; l < 256; l++) { 514 | 515 | cmsFloat64Number ff = yRamp[l]; 516 | 517 | if (ff >= lo && ff < hi) { 518 | x[n] = inRamp[l]; 519 | y[n] = yRamp[l]; 520 | n++; 521 | } 522 | } 523 | 524 | 525 | // No suitable points 526 | if (n < 3 ) { 527 | cmsDeleteTransform(hRoundTrip); 528 | BlackPoint -> X = BlackPoint ->Y = BlackPoint -> Z = 0.0; 529 | return FALSE; 530 | } 531 | 532 | 533 | // fit and get the vertex of quadratic curve 534 | Lab.L = RootOfLeastSquaresFitQuadraticCurve(n, x, y); 535 | 536 | if (Lab.L < 0.0) { // clip to zero L* if the vertex is negative 537 | Lab.L = 0; 538 | } 539 | 540 | Lab.a = InitialLab.a; 541 | Lab.b = InitialLab.b; 542 | 543 | cmsLab2XYZ(NULL, BlackPoint, &Lab); 544 | 545 | cmsDeleteTransform(hRoundTrip); 546 | return TRUE; 547 | } 548 | -------------------------------------------------------------------------------- /ImageColoriser/Sources/Utils/littlecms/cmssm.c: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------------- 2 | // 3 | // Little Color Management System 4 | // Copyright (c) 1998-2020 Marti Maria Saguer 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining 7 | // a copy of this software and associated documentation files (the "Software"), 8 | // to deal in the Software without restriction, including without limitation 9 | // the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 | // and/or sell copies of the Software, and to permit persons to whom the Software 11 | // is furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 18 | // THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | // 24 | //--------------------------------------------------------------------------------- 25 | // 26 | 27 | #include "lcms2_internal.h" 28 | 29 | 30 | // ------------------------------------------------------------------------ 31 | 32 | // Gamut boundary description by using Jan Morovic's Segment maxima method 33 | // Many thanks to Jan for allowing me to use his algorithm. 34 | 35 | // r = C* 36 | // alpha = Hab 37 | // theta = L* 38 | 39 | #define SECTORS 16 // number of divisions in alpha and theta 40 | 41 | // Spherical coordinates 42 | typedef struct { 43 | 44 | cmsFloat64Number r; 45 | cmsFloat64Number alpha; 46 | cmsFloat64Number theta; 47 | 48 | } cmsSpherical; 49 | 50 | typedef enum { 51 | GP_EMPTY, 52 | GP_SPECIFIED, 53 | GP_MODELED 54 | 55 | } GDBPointType; 56 | 57 | 58 | typedef struct { 59 | 60 | GDBPointType Type; 61 | cmsSpherical p; // Keep also alpha & theta of maximum 62 | 63 | } cmsGDBPoint; 64 | 65 | 66 | typedef struct { 67 | 68 | cmsContext ContextID; 69 | cmsGDBPoint Gamut[SECTORS][SECTORS]; 70 | 71 | } cmsGDB; 72 | 73 | 74 | // A line using the parametric form 75 | // P = a + t*u 76 | typedef struct { 77 | 78 | cmsVEC3 a; 79 | cmsVEC3 u; 80 | 81 | } cmsLine; 82 | 83 | 84 | // A plane using the parametric form 85 | // Q = b + r*v + s*w 86 | typedef struct { 87 | 88 | cmsVEC3 b; 89 | cmsVEC3 v; 90 | cmsVEC3 w; 91 | 92 | } cmsPlane; 93 | 94 | 95 | 96 | // -------------------------------------------------------------------------------------------- 97 | 98 | // ATAN2() which always returns degree positive numbers 99 | 100 | static 101 | cmsFloat64Number _cmsAtan2(cmsFloat64Number y, cmsFloat64Number x) 102 | { 103 | cmsFloat64Number a; 104 | 105 | // Deal with undefined case 106 | if (x == 0.0 && y == 0.0) return 0; 107 | 108 | a = (atan2(y, x) * 180.0) / M_PI; 109 | 110 | while (a < 0) { 111 | a += 360; 112 | } 113 | 114 | return a; 115 | } 116 | 117 | // Convert to spherical coordinates 118 | static 119 | void ToSpherical(cmsSpherical* sp, const cmsVEC3* v) 120 | { 121 | 122 | cmsFloat64Number L, a, b; 123 | 124 | L = v ->n[VX]; 125 | a = v ->n[VY]; 126 | b = v ->n[VZ]; 127 | 128 | sp ->r = sqrt( L*L + a*a + b*b ); 129 | 130 | if (sp ->r == 0) { 131 | sp ->alpha = sp ->theta = 0; 132 | return; 133 | } 134 | 135 | sp ->alpha = _cmsAtan2(a, b); 136 | sp ->theta = _cmsAtan2(sqrt(a*a + b*b), L); 137 | } 138 | 139 | 140 | // Convert to cartesian from spherical 141 | static 142 | void ToCartesian(cmsVEC3* v, const cmsSpherical* sp) 143 | { 144 | cmsFloat64Number sin_alpha; 145 | cmsFloat64Number cos_alpha; 146 | cmsFloat64Number sin_theta; 147 | cmsFloat64Number cos_theta; 148 | cmsFloat64Number L, a, b; 149 | 150 | sin_alpha = sin((M_PI * sp ->alpha) / 180.0); 151 | cos_alpha = cos((M_PI * sp ->alpha) / 180.0); 152 | sin_theta = sin((M_PI * sp ->theta) / 180.0); 153 | cos_theta = cos((M_PI * sp ->theta) / 180.0); 154 | 155 | a = sp ->r * sin_theta * sin_alpha; 156 | b = sp ->r * sin_theta * cos_alpha; 157 | L = sp ->r * cos_theta; 158 | 159 | v ->n[VX] = L; 160 | v ->n[VY] = a; 161 | v ->n[VZ] = b; 162 | } 163 | 164 | 165 | // Quantize sector of a spherical coordinate. Saturate 360, 180 to last sector 166 | // The limits are the centers of each sector, so 167 | static 168 | void QuantizeToSector(const cmsSpherical* sp, int* alpha, int* theta) 169 | { 170 | *alpha = (int) floor(((sp->alpha * (SECTORS)) / 360.0) ); 171 | *theta = (int) floor(((sp->theta * (SECTORS)) / 180.0) ); 172 | 173 | if (*alpha >= SECTORS) 174 | *alpha = SECTORS-1; 175 | if (*theta >= SECTORS) 176 | *theta = SECTORS-1; 177 | } 178 | 179 | 180 | // Line determined by 2 points 181 | static 182 | void LineOf2Points(cmsLine* line, cmsVEC3* a, cmsVEC3* b) 183 | { 184 | 185 | _cmsVEC3init(&line ->a, a ->n[VX], a ->n[VY], a ->n[VZ]); 186 | _cmsVEC3init(&line ->u, b ->n[VX] - a ->n[VX], 187 | b ->n[VY] - a ->n[VY], 188 | b ->n[VZ] - a ->n[VZ]); 189 | } 190 | 191 | 192 | // Evaluate parametric line 193 | static 194 | void GetPointOfLine(cmsVEC3* p, const cmsLine* line, cmsFloat64Number t) 195 | { 196 | p ->n[VX] = line ->a.n[VX] + t * line->u.n[VX]; 197 | p ->n[VY] = line ->a.n[VY] + t * line->u.n[VY]; 198 | p ->n[VZ] = line ->a.n[VZ] + t * line->u.n[VZ]; 199 | } 200 | 201 | 202 | 203 | /* 204 | Closest point in sector line1 to sector line2 (both are defined as 0 <=t <= 1) 205 | http://softsurfer.com/Archive/algorithm_0106/algorithm_0106.htm 206 | 207 | Copyright 2001, softSurfer (www.softsurfer.com) 208 | This code may be freely used and modified for any purpose 209 | providing that this copyright notice is included with it. 210 | SoftSurfer makes no warranty for this code, and cannot be held 211 | liable for any real or imagined damage resulting from its use. 212 | Users of this code must verify correctness for their application. 213 | 214 | */ 215 | 216 | static 217 | cmsBool ClosestLineToLine(cmsVEC3* r, const cmsLine* line1, const cmsLine* line2) 218 | { 219 | cmsFloat64Number a, b, c, d, e, D; 220 | cmsFloat64Number sc, sN, sD; 221 | //cmsFloat64Number tc; // left for future use 222 | cmsFloat64Number tN, tD; 223 | cmsVEC3 w0; 224 | 225 | _cmsVEC3minus(&w0, &line1 ->a, &line2 ->a); 226 | 227 | a = _cmsVEC3dot(&line1 ->u, &line1 ->u); 228 | b = _cmsVEC3dot(&line1 ->u, &line2 ->u); 229 | c = _cmsVEC3dot(&line2 ->u, &line2 ->u); 230 | d = _cmsVEC3dot(&line1 ->u, &w0); 231 | e = _cmsVEC3dot(&line2 ->u, &w0); 232 | 233 | D = a*c - b * b; // Denominator 234 | sD = tD = D; // default sD = D >= 0 235 | 236 | if (D < MATRIX_DET_TOLERANCE) { // the lines are almost parallel 237 | 238 | sN = 0.0; // force using point P0 on segment S1 239 | sD = 1.0; // to prevent possible division by 0.0 later 240 | tN = e; 241 | tD = c; 242 | } 243 | else { // get the closest points on the infinite lines 244 | 245 | sN = (b*e - c*d); 246 | tN = (a*e - b*d); 247 | 248 | if (sN < 0.0) { // sc < 0 => the s=0 edge is visible 249 | 250 | sN = 0.0; 251 | tN = e; 252 | tD = c; 253 | } 254 | else if (sN > sD) { // sc > 1 => the s=1 edge is visible 255 | sN = sD; 256 | tN = e + b; 257 | tD = c; 258 | } 259 | } 260 | 261 | if (tN < 0.0) { // tc < 0 => the t=0 edge is visible 262 | 263 | tN = 0.0; 264 | // recompute sc for this edge 265 | if (-d < 0.0) 266 | sN = 0.0; 267 | else if (-d > a) 268 | sN = sD; 269 | else { 270 | sN = -d; 271 | sD = a; 272 | } 273 | } 274 | else if (tN > tD) { // tc > 1 => the t=1 edge is visible 275 | 276 | tN = tD; 277 | 278 | // recompute sc for this edge 279 | if ((-d + b) < 0.0) 280 | sN = 0; 281 | else if ((-d + b) > a) 282 | sN = sD; 283 | else { 284 | sN = (-d + b); 285 | sD = a; 286 | } 287 | } 288 | // finally do the division to get sc and tc 289 | sc = (fabs(sN) < MATRIX_DET_TOLERANCE ? 0.0 : sN / sD); 290 | //tc = (fabs(tN) < MATRIX_DET_TOLERANCE ? 0.0 : tN / tD); // left for future use. 291 | 292 | GetPointOfLine(r, line1, sc); 293 | return TRUE; 294 | } 295 | 296 | 297 | 298 | // ------------------------------------------------------------------ Wrapper 299 | 300 | 301 | // Allocate & free structure 302 | cmsHANDLE CMSEXPORT cmsGBDAlloc(cmsContext ContextID) 303 | { 304 | cmsGDB* gbd = (cmsGDB*) _cmsMallocZero(ContextID, sizeof(cmsGDB)); 305 | if (gbd == NULL) return NULL; 306 | 307 | gbd -> ContextID = ContextID; 308 | 309 | return (cmsHANDLE) gbd; 310 | } 311 | 312 | 313 | void CMSEXPORT cmsGBDFree(cmsHANDLE hGBD) 314 | { 315 | cmsGDB* gbd = (cmsGDB*) hGBD; 316 | if (hGBD != NULL) 317 | _cmsFree(gbd->ContextID, (void*) gbd); 318 | } 319 | 320 | 321 | // Auxiliary to retrieve a pointer to the segmentr containing the Lab value 322 | static 323 | cmsGDBPoint* GetPoint(cmsGDB* gbd, const cmsCIELab* Lab, cmsSpherical* sp) 324 | { 325 | cmsVEC3 v; 326 | int alpha, theta; 327 | 328 | // Housekeeping 329 | _cmsAssert(gbd != NULL); 330 | _cmsAssert(Lab != NULL); 331 | _cmsAssert(sp != NULL); 332 | 333 | // Center L* by subtracting half of its domain, that's 50 334 | _cmsVEC3init(&v, Lab ->L - 50.0, Lab ->a, Lab ->b); 335 | 336 | // Convert to spherical coordinates 337 | ToSpherical(sp, &v); 338 | 339 | if (sp ->r < 0 || sp ->alpha < 0 || sp->theta < 0) { 340 | cmsSignalError(gbd ->ContextID, cmsERROR_RANGE, "spherical value out of range"); 341 | return NULL; 342 | } 343 | 344 | // On which sector it falls? 345 | QuantizeToSector(sp, &alpha, &theta); 346 | 347 | if (alpha < 0 || theta < 0 || alpha >= SECTORS || theta >= SECTORS) { 348 | cmsSignalError(gbd ->ContextID, cmsERROR_RANGE, " quadrant out of range"); 349 | return NULL; 350 | } 351 | 352 | // Get pointer to the sector 353 | return &gbd ->Gamut[theta][alpha]; 354 | } 355 | 356 | // Add a point to gamut descriptor. Point to add is in Lab color space. 357 | // GBD is centered on a=b=0 and L*=50 358 | cmsBool CMSEXPORT cmsGDBAddPoint(cmsHANDLE hGBD, const cmsCIELab* Lab) 359 | { 360 | cmsGDB* gbd = (cmsGDB*) hGBD; 361 | cmsGDBPoint* ptr; 362 | cmsSpherical sp; 363 | 364 | 365 | // Get pointer to the sector 366 | ptr = GetPoint(gbd, Lab, &sp); 367 | if (ptr == NULL) return FALSE; 368 | 369 | // If no samples at this sector, add it 370 | if (ptr ->Type == GP_EMPTY) { 371 | 372 | ptr -> Type = GP_SPECIFIED; 373 | ptr -> p = sp; 374 | } 375 | else { 376 | 377 | 378 | // Substitute only if radius is greater 379 | if (sp.r > ptr -> p.r) { 380 | 381 | ptr -> Type = GP_SPECIFIED; 382 | ptr -> p = sp; 383 | } 384 | } 385 | 386 | return TRUE; 387 | } 388 | 389 | // Check if a given point falls inside gamut 390 | cmsBool CMSEXPORT cmsGDBCheckPoint(cmsHANDLE hGBD, const cmsCIELab* Lab) 391 | { 392 | cmsGDB* gbd = (cmsGDB*) hGBD; 393 | cmsGDBPoint* ptr; 394 | cmsSpherical sp; 395 | 396 | // Get pointer to the sector 397 | ptr = GetPoint(gbd, Lab, &sp); 398 | if (ptr == NULL) return FALSE; 399 | 400 | // If no samples at this sector, return no data 401 | if (ptr ->Type == GP_EMPTY) return FALSE; 402 | 403 | // In gamut only if radius is greater 404 | 405 | return (sp.r <= ptr -> p.r); 406 | } 407 | 408 | // ----------------------------------------------------------------------------------------------------------------------- 409 | 410 | // Find near sectors. The list of sectors found is returned on Close[]. 411 | // The function returns the number of sectors as well. 412 | 413 | // 24 9 10 11 12 414 | // 23 8 1 2 13 415 | // 22 7 * 3 14 416 | // 21 6 5 4 15 417 | // 20 19 18 17 16 418 | // 419 | // Those are the relative movements 420 | // {-2,-2}, {-1, -2}, {0, -2}, {+1, -2}, {+2, -2}, 421 | // {-2,-1}, {-1, -1}, {0, -1}, {+1, -1}, {+2, -1}, 422 | // {-2, 0}, {-1, 0}, {0, 0}, {+1, 0}, {+2, 0}, 423 | // {-2,+1}, {-1, +1}, {0, +1}, {+1, +1}, {+2, +1}, 424 | // {-2,+2}, {-1, +2}, {0, +2}, {+1, +2}, {+2, +2}}; 425 | 426 | 427 | static 428 | const struct _spiral { 429 | 430 | int AdvX, AdvY; 431 | 432 | } Spiral[] = { {0, -1}, {+1, -1}, {+1, 0}, {+1, +1}, {0, +1}, {-1, +1}, 433 | {-1, 0}, {-1, -1}, {-1, -2}, {0, -2}, {+1, -2}, {+2, -2}, 434 | {+2, -1}, {+2, 0}, {+2, +1}, {+2, +2}, {+1, +2}, {0, +2}, 435 | {-1, +2}, {-2, +2}, {-2, +1}, {-2, 0}, {-2, -1}, {-2, -2} }; 436 | 437 | #define NSTEPS (sizeof(Spiral) / sizeof(struct _spiral)) 438 | 439 | static 440 | int FindNearSectors(cmsGDB* gbd, int alpha, int theta, cmsGDBPoint* Close[]) 441 | { 442 | int nSectors = 0; 443 | int a, t; 444 | cmsUInt32Number i; 445 | cmsGDBPoint* pt; 446 | 447 | for (i=0; i < NSTEPS; i++) { 448 | 449 | a = alpha + Spiral[i].AdvX; 450 | t = theta + Spiral[i].AdvY; 451 | 452 | // Cycle at the end 453 | a %= SECTORS; 454 | t %= SECTORS; 455 | 456 | // Cycle at the begin 457 | if (a < 0) a = SECTORS + a; 458 | if (t < 0) t = SECTORS + t; 459 | 460 | pt = &gbd ->Gamut[t][a]; 461 | 462 | if (pt -> Type != GP_EMPTY) { 463 | 464 | Close[nSectors++] = pt; 465 | } 466 | } 467 | 468 | return nSectors; 469 | } 470 | 471 | 472 | // Interpolate a missing sector. Method identifies whatever this is top, bottom or mid 473 | static 474 | cmsBool InterpolateMissingSector(cmsGDB* gbd, int alpha, int theta) 475 | { 476 | cmsSpherical sp; 477 | cmsVEC3 Lab; 478 | cmsVEC3 Centre; 479 | cmsLine ray; 480 | int nCloseSectors; 481 | cmsGDBPoint* Close[NSTEPS + 1]; 482 | cmsSpherical closel, templ; 483 | cmsLine edge; 484 | int k, m; 485 | 486 | // Is that point already specified? 487 | if (gbd ->Gamut[theta][alpha].Type != GP_EMPTY) return TRUE; 488 | 489 | // Fill close points 490 | nCloseSectors = FindNearSectors(gbd, alpha, theta, Close); 491 | 492 | 493 | // Find a central point on the sector 494 | sp.alpha = (cmsFloat64Number) ((alpha + 0.5) * 360.0) / (SECTORS); 495 | sp.theta = (cmsFloat64Number) ((theta + 0.5) * 180.0) / (SECTORS); 496 | sp.r = 50.0; 497 | 498 | // Convert to Cartesian 499 | ToCartesian(&Lab, &sp); 500 | 501 | // Create a ray line from centre to this point 502 | _cmsVEC3init(&Centre, 50.0, 0, 0); 503 | LineOf2Points(&ray, &Lab, &Centre); 504 | 505 | // For all close sectors 506 | closel.r = 0.0; 507 | closel.alpha = 0; 508 | closel.theta = 0; 509 | 510 | for (k=0; k < nCloseSectors; k++) { 511 | 512 | for(m = k+1; m < nCloseSectors; m++) { 513 | 514 | cmsVEC3 temp, a1, a2; 515 | 516 | // A line from sector to sector 517 | ToCartesian(&a1, &Close[k]->p); 518 | ToCartesian(&a2, &Close[m]->p); 519 | 520 | LineOf2Points(&edge, &a1, &a2); 521 | 522 | // Find a line 523 | ClosestLineToLine(&temp, &ray, &edge); 524 | 525 | // Convert to spherical 526 | ToSpherical(&templ, &temp); 527 | 528 | 529 | if ( templ.r > closel.r && 530 | templ.theta >= (theta*180.0/SECTORS) && 531 | templ.theta <= ((theta+1)*180.0/SECTORS) && 532 | templ.alpha >= (alpha*360.0/SECTORS) && 533 | templ.alpha <= ((alpha+1)*360.0/SECTORS)) { 534 | 535 | closel = templ; 536 | } 537 | } 538 | } 539 | 540 | gbd ->Gamut[theta][alpha].p = closel; 541 | gbd ->Gamut[theta][alpha].Type = GP_MODELED; 542 | 543 | return TRUE; 544 | 545 | } 546 | 547 | 548 | // Interpolate missing parts. The algorithm fist computes slices at 549 | // theta=0 and theta=Max. 550 | cmsBool CMSEXPORT cmsGDBCompute(cmsHANDLE hGBD, cmsUInt32Number dwFlags) 551 | { 552 | int alpha, theta; 553 | cmsGDB* gbd = (cmsGDB*) hGBD; 554 | 555 | _cmsAssert(hGBD != NULL); 556 | 557 | // Interpolate black 558 | for (alpha = 0; alpha < SECTORS; alpha++) { 559 | 560 | if (!InterpolateMissingSector(gbd, alpha, 0)) return FALSE; 561 | } 562 | 563 | // Interpolate white 564 | for (alpha = 0; alpha < SECTORS; alpha++) { 565 | 566 | if (!InterpolateMissingSector(gbd, alpha, SECTORS-1)) return FALSE; 567 | } 568 | 569 | 570 | // Interpolate Mid 571 | for (theta = 1; theta < SECTORS; theta++) { 572 | for (alpha = 0; alpha < SECTORS; alpha++) { 573 | 574 | if (!InterpolateMissingSector(gbd, alpha, theta)) return FALSE; 575 | } 576 | } 577 | 578 | // Done 579 | return TRUE; 580 | 581 | cmsUNUSED_PARAMETER(dwFlags); 582 | } 583 | 584 | 585 | 586 | 587 | // -------------------------------------------------------------------------------------------------------- 588 | 589 | // Great for debug, but not suitable for real use 590 | 591 | #if 0 592 | cmsBool cmsGBDdumpVRML(cmsHANDLE hGBD, const char* fname) 593 | { 594 | FILE* fp; 595 | int i, j; 596 | cmsGDB* gbd = (cmsGDB*) hGBD; 597 | cmsGDBPoint* pt; 598 | 599 | fp = fopen (fname, "wt"); 600 | if (fp == NULL) 601 | return FALSE; 602 | 603 | fprintf (fp, "#VRML V2.0 utf8\n"); 604 | 605 | // set the viewing orientation and distance 606 | fprintf (fp, "DEF CamTest Group {\n"); 607 | fprintf (fp, "\tchildren [\n"); 608 | fprintf (fp, "\t\tDEF Cameras Group {\n"); 609 | fprintf (fp, "\t\t\tchildren [\n"); 610 | fprintf (fp, "\t\t\t\tDEF DefaultView Viewpoint {\n"); 611 | fprintf (fp, "\t\t\t\t\tposition 0 0 340\n"); 612 | fprintf (fp, "\t\t\t\t\torientation 0 0 1 0\n"); 613 | fprintf (fp, "\t\t\t\t\tdescription \"default view\"\n"); 614 | fprintf (fp, "\t\t\t\t}\n"); 615 | fprintf (fp, "\t\t\t]\n"); 616 | fprintf (fp, "\t\t},\n"); 617 | fprintf (fp, "\t]\n"); 618 | fprintf (fp, "}\n"); 619 | 620 | // Output the background stuff 621 | fprintf (fp, "Background {\n"); 622 | fprintf (fp, "\tskyColor [\n"); 623 | fprintf (fp, "\t\t.5 .5 .5\n"); 624 | fprintf (fp, "\t]\n"); 625 | fprintf (fp, "}\n"); 626 | 627 | // Output the shape stuff 628 | fprintf (fp, "Transform {\n"); 629 | fprintf (fp, "\tscale .3 .3 .3\n"); 630 | fprintf (fp, "\tchildren [\n"); 631 | 632 | // Draw the axes as a shape: 633 | fprintf (fp, "\t\tShape {\n"); 634 | fprintf (fp, "\t\t\tappearance Appearance {\n"); 635 | fprintf (fp, "\t\t\t\tmaterial Material {\n"); 636 | fprintf (fp, "\t\t\t\t\tdiffuseColor 0 0.8 0\n"); 637 | fprintf (fp, "\t\t\t\t\temissiveColor 1.0 1.0 1.0\n"); 638 | fprintf (fp, "\t\t\t\t\tshininess 0.8\n"); 639 | fprintf (fp, "\t\t\t\t}\n"); 640 | fprintf (fp, "\t\t\t}\n"); 641 | fprintf (fp, "\t\t\tgeometry IndexedLineSet {\n"); 642 | fprintf (fp, "\t\t\t\tcoord Coordinate {\n"); 643 | fprintf (fp, "\t\t\t\t\tpoint [\n"); 644 | fprintf (fp, "\t\t\t\t\t0.0 0.0 0.0,\n"); 645 | fprintf (fp, "\t\t\t\t\t%f 0.0 0.0,\n", 255.0); 646 | fprintf (fp, "\t\t\t\t\t0.0 %f 0.0,\n", 255.0); 647 | fprintf (fp, "\t\t\t\t\t0.0 0.0 %f]\n", 255.0); 648 | fprintf (fp, "\t\t\t\t}\n"); 649 | fprintf (fp, "\t\t\t\tcoordIndex [\n"); 650 | fprintf (fp, "\t\t\t\t\t0, 1, -1\n"); 651 | fprintf (fp, "\t\t\t\t\t0, 2, -1\n"); 652 | fprintf (fp, "\t\t\t\t\t0, 3, -1]\n"); 653 | fprintf (fp, "\t\t\t}\n"); 654 | fprintf (fp, "\t\t}\n"); 655 | 656 | 657 | fprintf (fp, "\t\tShape {\n"); 658 | fprintf (fp, "\t\t\tappearance Appearance {\n"); 659 | fprintf (fp, "\t\t\t\tmaterial Material {\n"); 660 | fprintf (fp, "\t\t\t\t\tdiffuseColor 0 0.8 0\n"); 661 | fprintf (fp, "\t\t\t\t\temissiveColor 1 1 1\n"); 662 | fprintf (fp, "\t\t\t\t\tshininess 0.8\n"); 663 | fprintf (fp, "\t\t\t\t}\n"); 664 | fprintf (fp, "\t\t\t}\n"); 665 | fprintf (fp, "\t\t\tgeometry PointSet {\n"); 666 | 667 | // fill in the points here 668 | fprintf (fp, "\t\t\t\tcoord Coordinate {\n"); 669 | fprintf (fp, "\t\t\t\t\tpoint [\n"); 670 | 671 | // We need to transverse all gamut hull. 672 | for (i=0; i < SECTORS; i++) 673 | for (j=0; j < SECTORS; j++) { 674 | 675 | cmsVEC3 v; 676 | 677 | pt = &gbd ->Gamut[i][j]; 678 | ToCartesian(&v, &pt ->p); 679 | 680 | fprintf (fp, "\t\t\t\t\t%g %g %g", v.n[0]+50, v.n[1], v.n[2]); 681 | 682 | if ((j == SECTORS - 1) && (i == SECTORS - 1)) 683 | fprintf (fp, "]\n"); 684 | else 685 | fprintf (fp, ",\n"); 686 | 687 | } 688 | 689 | fprintf (fp, "\t\t\t\t}\n"); 690 | 691 | 692 | 693 | // fill in the face colors 694 | fprintf (fp, "\t\t\t\tcolor Color {\n"); 695 | fprintf (fp, "\t\t\t\t\tcolor [\n"); 696 | 697 | for (i=0; i < SECTORS; i++) 698 | for (j=0; j < SECTORS; j++) { 699 | 700 | cmsVEC3 v; 701 | 702 | pt = &gbd ->Gamut[i][j]; 703 | 704 | 705 | ToCartesian(&v, &pt ->p); 706 | 707 | 708 | if (pt ->Type == GP_EMPTY) 709 | fprintf (fp, "\t\t\t\t\t%g %g %g", 0.0, 0.0, 0.0); 710 | else 711 | if (pt ->Type == GP_MODELED) 712 | fprintf (fp, "\t\t\t\t\t%g %g %g", 1.0, .5, .5); 713 | else { 714 | fprintf (fp, "\t\t\t\t\t%g %g %g", 1.0, 1.0, 1.0); 715 | 716 | } 717 | 718 | if ((j == SECTORS - 1) && (i == SECTORS - 1)) 719 | fprintf (fp, "]\n"); 720 | else 721 | fprintf (fp, ",\n"); 722 | } 723 | fprintf (fp, "\t\t\t}\n"); 724 | 725 | 726 | fprintf (fp, "\t\t\t}\n"); 727 | fprintf (fp, "\t\t}\n"); 728 | fprintf (fp, "\t]\n"); 729 | fprintf (fp, "}\n"); 730 | 731 | fclose (fp); 732 | 733 | return TRUE; 734 | } 735 | #endif 736 | 737 | -------------------------------------------------------------------------------- /ImageColoriser/Sources/Utils/littlecms/cmswtpnt.c: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------------------- 2 | // 3 | // Little Color Management System 4 | // Copyright (c) 1998-2020 Marti Maria Saguer 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining 7 | // a copy of this software and associated documentation files (the "Software"), 8 | // to deal in the Software without restriction, including without limitation 9 | // the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 | // and/or sell copies of the Software, and to permit persons to whom the Software 11 | // is furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 18 | // THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | // 24 | //--------------------------------------------------------------------------------- 25 | // 26 | 27 | #include "lcms2_internal.h" 28 | 29 | 30 | // D50 - Widely used 31 | const cmsCIEXYZ* CMSEXPORT cmsD50_XYZ(void) 32 | { 33 | static cmsCIEXYZ D50XYZ = {cmsD50X, cmsD50Y, cmsD50Z}; 34 | 35 | return &D50XYZ; 36 | } 37 | 38 | const cmsCIExyY* CMSEXPORT cmsD50_xyY(void) 39 | { 40 | static cmsCIExyY D50xyY; 41 | 42 | cmsXYZ2xyY(&D50xyY, cmsD50_XYZ()); 43 | 44 | return &D50xyY; 45 | } 46 | 47 | // Obtains WhitePoint from Temperature 48 | cmsBool CMSEXPORT cmsWhitePointFromTemp(cmsCIExyY* WhitePoint, cmsFloat64Number TempK) 49 | { 50 | cmsFloat64Number x, y; 51 | cmsFloat64Number T, T2, T3; 52 | // cmsFloat64Number M1, M2; 53 | 54 | _cmsAssert(WhitePoint != NULL); 55 | 56 | T = TempK; 57 | T2 = T*T; // Square 58 | T3 = T2*T; // Cube 59 | 60 | // For correlated color temperature (T) between 4000K and 7000K: 61 | 62 | if (T >= 4000. && T <= 7000.) 63 | { 64 | x = -4.6070*(1E9/T3) + 2.9678*(1E6/T2) + 0.09911*(1E3/T) + 0.244063; 65 | } 66 | else 67 | // or for correlated color temperature (T) between 7000K and 25000K: 68 | 69 | if (T > 7000.0 && T <= 25000.0) 70 | { 71 | x = -2.0064*(1E9/T3) + 1.9018*(1E6/T2) + 0.24748*(1E3/T) + 0.237040; 72 | } 73 | else { 74 | cmsSignalError(0, cmsERROR_RANGE, "cmsWhitePointFromTemp: invalid temp"); 75 | return FALSE; 76 | } 77 | 78 | // Obtain y(x) 79 | y = -3.000*(x*x) + 2.870*x - 0.275; 80 | 81 | // wave factors (not used, but here for futures extensions) 82 | 83 | // M1 = (-1.3515 - 1.7703*x + 5.9114 *y)/(0.0241 + 0.2562*x - 0.7341*y); 84 | // M2 = (0.0300 - 31.4424*x + 30.0717*y)/(0.0241 + 0.2562*x - 0.7341*y); 85 | 86 | WhitePoint -> x = x; 87 | WhitePoint -> y = y; 88 | WhitePoint -> Y = 1.0; 89 | 90 | return TRUE; 91 | } 92 | 93 | 94 | 95 | typedef struct { 96 | 97 | cmsFloat64Number mirek; // temp (in microreciprocal kelvin) 98 | cmsFloat64Number ut; // u coord of intersection w/ blackbody locus 99 | cmsFloat64Number vt; // v coord of intersection w/ blackbody locus 100 | cmsFloat64Number tt; // slope of ISOTEMPERATURE. line 101 | 102 | } ISOTEMPERATURE; 103 | 104 | static const ISOTEMPERATURE isotempdata[] = { 105 | // {Mirek, Ut, Vt, Tt } 106 | {0, 0.18006, 0.26352, -0.24341}, 107 | {10, 0.18066, 0.26589, -0.25479}, 108 | {20, 0.18133, 0.26846, -0.26876}, 109 | {30, 0.18208, 0.27119, -0.28539}, 110 | {40, 0.18293, 0.27407, -0.30470}, 111 | {50, 0.18388, 0.27709, -0.32675}, 112 | {60, 0.18494, 0.28021, -0.35156}, 113 | {70, 0.18611, 0.28342, -0.37915}, 114 | {80, 0.18740, 0.28668, -0.40955}, 115 | {90, 0.18880, 0.28997, -0.44278}, 116 | {100, 0.19032, 0.29326, -0.47888}, 117 | {125, 0.19462, 0.30141, -0.58204}, 118 | {150, 0.19962, 0.30921, -0.70471}, 119 | {175, 0.20525, 0.31647, -0.84901}, 120 | {200, 0.21142, 0.32312, -1.0182 }, 121 | {225, 0.21807, 0.32909, -1.2168 }, 122 | {250, 0.22511, 0.33439, -1.4512 }, 123 | {275, 0.23247, 0.33904, -1.7298 }, 124 | {300, 0.24010, 0.34308, -2.0637 }, 125 | {325, 0.24702, 0.34655, -2.4681 }, 126 | {350, 0.25591, 0.34951, -2.9641 }, 127 | {375, 0.26400, 0.35200, -3.5814 }, 128 | {400, 0.27218, 0.35407, -4.3633 }, 129 | {425, 0.28039, 0.35577, -5.3762 }, 130 | {450, 0.28863, 0.35714, -6.7262 }, 131 | {475, 0.29685, 0.35823, -8.5955 }, 132 | {500, 0.30505, 0.35907, -11.324 }, 133 | {525, 0.31320, 0.35968, -15.628 }, 134 | {550, 0.32129, 0.36011, -23.325 }, 135 | {575, 0.32931, 0.36038, -40.770 }, 136 | {600, 0.33724, 0.36051, -116.45 } 137 | }; 138 | 139 | #define NISO sizeof(isotempdata)/sizeof(ISOTEMPERATURE) 140 | 141 | 142 | // Robertson's method 143 | cmsBool CMSEXPORT cmsTempFromWhitePoint(cmsFloat64Number* TempK, const cmsCIExyY* WhitePoint) 144 | { 145 | cmsUInt32Number j; 146 | cmsFloat64Number us,vs; 147 | cmsFloat64Number uj,vj,tj,di,dj,mi,mj; 148 | cmsFloat64Number xs, ys; 149 | 150 | _cmsAssert(WhitePoint != NULL); 151 | _cmsAssert(TempK != NULL); 152 | 153 | di = mi = 0; 154 | xs = WhitePoint -> x; 155 | ys = WhitePoint -> y; 156 | 157 | // convert (x,y) to CIE 1960 (u,WhitePoint) 158 | 159 | us = (2*xs) / (-xs + 6*ys + 1.5); 160 | vs = (3*ys) / (-xs + 6*ys + 1.5); 161 | 162 | 163 | for (j=0; j < NISO; j++) { 164 | 165 | uj = isotempdata[j].ut; 166 | vj = isotempdata[j].vt; 167 | tj = isotempdata[j].tt; 168 | mj = isotempdata[j].mirek; 169 | 170 | dj = ((vs - vj) - tj * (us - uj)) / sqrt(1.0 + tj * tj); 171 | 172 | if ((j != 0) && (di/dj < 0.0)) { 173 | 174 | // Found a match 175 | *TempK = 1000000.0 / (mi + (di / (di - dj)) * (mj - mi)); 176 | return TRUE; 177 | } 178 | 179 | di = dj; 180 | mi = mj; 181 | } 182 | 183 | // Not found 184 | return FALSE; 185 | } 186 | 187 | 188 | // Compute chromatic adaptation matrix using Chad as cone matrix 189 | 190 | static 191 | cmsBool ComputeChromaticAdaptation(cmsMAT3* Conversion, 192 | const cmsCIEXYZ* SourceWhitePoint, 193 | const cmsCIEXYZ* DestWhitePoint, 194 | const cmsMAT3* Chad) 195 | 196 | { 197 | 198 | cmsMAT3 Chad_Inv; 199 | cmsVEC3 ConeSourceXYZ, ConeSourceRGB; 200 | cmsVEC3 ConeDestXYZ, ConeDestRGB; 201 | cmsMAT3 Cone, Tmp; 202 | 203 | 204 | Tmp = *Chad; 205 | if (!_cmsMAT3inverse(&Tmp, &Chad_Inv)) return FALSE; 206 | 207 | _cmsVEC3init(&ConeSourceXYZ, SourceWhitePoint -> X, 208 | SourceWhitePoint -> Y, 209 | SourceWhitePoint -> Z); 210 | 211 | _cmsVEC3init(&ConeDestXYZ, DestWhitePoint -> X, 212 | DestWhitePoint -> Y, 213 | DestWhitePoint -> Z); 214 | 215 | _cmsMAT3eval(&ConeSourceRGB, Chad, &ConeSourceXYZ); 216 | _cmsMAT3eval(&ConeDestRGB, Chad, &ConeDestXYZ); 217 | 218 | // Build matrix 219 | _cmsVEC3init(&Cone.v[0], ConeDestRGB.n[0]/ConeSourceRGB.n[0], 0.0, 0.0); 220 | _cmsVEC3init(&Cone.v[1], 0.0, ConeDestRGB.n[1]/ConeSourceRGB.n[1], 0.0); 221 | _cmsVEC3init(&Cone.v[2], 0.0, 0.0, ConeDestRGB.n[2]/ConeSourceRGB.n[2]); 222 | 223 | 224 | // Normalize 225 | _cmsMAT3per(&Tmp, &Cone, Chad); 226 | _cmsMAT3per(Conversion, &Chad_Inv, &Tmp); 227 | 228 | return TRUE; 229 | } 230 | 231 | // Returns the final chrmatic adaptation from illuminant FromIll to Illuminant ToIll 232 | // The cone matrix can be specified in ConeMatrix. If NULL, Bradford is assumed 233 | cmsBool _cmsAdaptationMatrix(cmsMAT3* r, const cmsMAT3* ConeMatrix, const cmsCIEXYZ* FromIll, const cmsCIEXYZ* ToIll) 234 | { 235 | cmsMAT3 LamRigg = {{ // Bradford matrix 236 | {{ 0.8951, 0.2664, -0.1614 }}, 237 | {{ -0.7502, 1.7135, 0.0367 }}, 238 | {{ 0.0389, -0.0685, 1.0296 }} 239 | }}; 240 | 241 | if (ConeMatrix == NULL) 242 | ConeMatrix = &LamRigg; 243 | 244 | return ComputeChromaticAdaptation(r, FromIll, ToIll, ConeMatrix); 245 | } 246 | 247 | // Same as anterior, but assuming D50 destination. White point is given in xyY 248 | static 249 | cmsBool _cmsAdaptMatrixToD50(cmsMAT3* r, const cmsCIExyY* SourceWhitePt) 250 | { 251 | cmsCIEXYZ Dn; 252 | cmsMAT3 Bradford; 253 | cmsMAT3 Tmp; 254 | 255 | cmsxyY2XYZ(&Dn, SourceWhitePt); 256 | 257 | if (!_cmsAdaptationMatrix(&Bradford, NULL, &Dn, cmsD50_XYZ())) return FALSE; 258 | 259 | Tmp = *r; 260 | _cmsMAT3per(r, &Bradford, &Tmp); 261 | 262 | return TRUE; 263 | } 264 | 265 | // Build a White point, primary chromas transfer matrix from RGB to CIE XYZ 266 | // This is just an approximation, I am not handling all the non-linear 267 | // aspects of the RGB to XYZ process, and assumming that the gamma correction 268 | // has transitive property in the transformation chain. 269 | // 270 | // the alghoritm: 271 | // 272 | // - First I build the absolute conversion matrix using 273 | // primaries in XYZ. This matrix is next inverted 274 | // - Then I eval the source white point across this matrix 275 | // obtaining the coeficients of the transformation 276 | // - Then, I apply these coeficients to the original matrix 277 | // 278 | cmsBool _cmsBuildRGB2XYZtransferMatrix(cmsMAT3* r, const cmsCIExyY* WhitePt, const cmsCIExyYTRIPLE* Primrs) 279 | { 280 | cmsVEC3 WhitePoint, Coef; 281 | cmsMAT3 Result, Primaries; 282 | cmsFloat64Number xn, yn; 283 | cmsFloat64Number xr, yr; 284 | cmsFloat64Number xg, yg; 285 | cmsFloat64Number xb, yb; 286 | 287 | xn = WhitePt -> x; 288 | yn = WhitePt -> y; 289 | xr = Primrs -> Red.x; 290 | yr = Primrs -> Red.y; 291 | xg = Primrs -> Green.x; 292 | yg = Primrs -> Green.y; 293 | xb = Primrs -> Blue.x; 294 | yb = Primrs -> Blue.y; 295 | 296 | // Build Primaries matrix 297 | _cmsVEC3init(&Primaries.v[0], xr, xg, xb); 298 | _cmsVEC3init(&Primaries.v[1], yr, yg, yb); 299 | _cmsVEC3init(&Primaries.v[2], (1-xr-yr), (1-xg-yg), (1-xb-yb)); 300 | 301 | 302 | // Result = Primaries ^ (-1) inverse matrix 303 | if (!_cmsMAT3inverse(&Primaries, &Result)) 304 | return FALSE; 305 | 306 | 307 | _cmsVEC3init(&WhitePoint, xn/yn, 1.0, (1.0-xn-yn)/yn); 308 | 309 | // Across inverse primaries ... 310 | _cmsMAT3eval(&Coef, &Result, &WhitePoint); 311 | 312 | // Give us the Coefs, then I build transformation matrix 313 | _cmsVEC3init(&r -> v[0], Coef.n[VX]*xr, Coef.n[VY]*xg, Coef.n[VZ]*xb); 314 | _cmsVEC3init(&r -> v[1], Coef.n[VX]*yr, Coef.n[VY]*yg, Coef.n[VZ]*yb); 315 | _cmsVEC3init(&r -> v[2], Coef.n[VX]*(1.0-xr-yr), Coef.n[VY]*(1.0-xg-yg), Coef.n[VZ]*(1.0-xb-yb)); 316 | 317 | 318 | return _cmsAdaptMatrixToD50(r, WhitePt); 319 | 320 | } 321 | 322 | 323 | // Adapts a color to a given illuminant. Original color is expected to have 324 | // a SourceWhitePt white point. 325 | cmsBool CMSEXPORT cmsAdaptToIlluminant(cmsCIEXYZ* Result, 326 | const cmsCIEXYZ* SourceWhitePt, 327 | const cmsCIEXYZ* Illuminant, 328 | const cmsCIEXYZ* Value) 329 | { 330 | cmsMAT3 Bradford; 331 | cmsVEC3 In, Out; 332 | 333 | _cmsAssert(Result != NULL); 334 | _cmsAssert(SourceWhitePt != NULL); 335 | _cmsAssert(Illuminant != NULL); 336 | _cmsAssert(Value != NULL); 337 | 338 | if (!_cmsAdaptationMatrix(&Bradford, NULL, SourceWhitePt, Illuminant)) return FALSE; 339 | 340 | _cmsVEC3init(&In, Value -> X, Value -> Y, Value -> Z); 341 | _cmsMAT3eval(&Out, &Bradford, &In); 342 | 343 | Result -> X = Out.n[0]; 344 | Result -> Y = Out.n[1]; 345 | Result -> Z = Out.n[2]; 346 | 347 | return TRUE; 348 | } 349 | 350 | 351 | -------------------------------------------------------------------------------- /ImageColoriser/Sources/Utils/littlecms/sRGB_ICC_v4_Appearance.icc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sgl0v/ImageColorizer/9d34b28e473ac2d7438cbece0979d00262c0d447/ImageColoriser/Sources/Utils/littlecms/sRGB_ICC_v4_Appearance.icc -------------------------------------------------------------------------------- /ImageColoriser/Sources/Utils/littlecms/sRGB_v4_ICC_preference.icc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sgl0v/ImageColorizer/9d34b28e473ac2d7438cbece0979d00262c0d447/ImageColoriser/Sources/Utils/littlecms/sRGB_v4_ICC_preference.icc -------------------------------------------------------------------------------- /ImageColoriser/Sources/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageColoriser 3 | // 4 | // Created by Maksym Shcheglov. 5 | // Copyright © 2021 Maksym Shcheglov. All rights reserved. 6 | // 7 | 8 | import UIKit 9 | 10 | class ViewController: UIViewController { 11 | enum State { 12 | case idle 13 | case loading 14 | case success(UIImage) 15 | case failure(Error) 16 | } 17 | 18 | @IBOutlet private weak var imageView: UIImageView! 19 | @IBOutlet private weak var activityIndicator: UIActivityIndicatorView! 20 | private let colorizer = ImageColorizer() 21 | private let scanner = ImageScanner() 22 | private let picker = ImagePicker() 23 | private var state: State = .idle { 24 | didSet { 25 | render(state) 26 | } 27 | } 28 | 29 | @IBAction func openImage(_ sender: Any) { 30 | selectImage { [weak self] result in 31 | guard let self = self else { return } 32 | switch result { 33 | case .success(let image): 34 | let scaledImage = image.scaled(with: self.imageView.bounds.width) 35 | self.colorize(scaledImage) 36 | case .failure(let error): 37 | self.showErrorAlert(error) 38 | } 39 | } 40 | } 41 | } 42 | 43 | // MARK: Private 44 | extension ViewController { 45 | 46 | private func colorize(_ image: UIImage) { 47 | state = .loading 48 | colorizer.colorize(image: image) {[unowned self] result in 49 | self.state = self.createState(from: result) 50 | } 51 | } 52 | 53 | private func createState(from result: Result) -> State { 54 | switch result { 55 | case .success(let image): 56 | return .success(image) 57 | case .failure(let error): 58 | return .failure(error) 59 | } 60 | } 61 | 62 | private func render(_ state: State) { 63 | switch state { 64 | case .idle: 65 | activityIndicator.isHidden = true 66 | imageView.image = nil 67 | case .loading: 68 | activityIndicator.isHidden = false 69 | imageView.image = nil 70 | case .failure(let error): 71 | activityIndicator.isHidden = true 72 | imageView.image = nil 73 | showErrorAlert(error) 74 | case .success(let image): 75 | activityIndicator.isHidden = true 76 | imageView.image = image 77 | } 78 | } 79 | 80 | private func showErrorAlert(_ error: Error) { 81 | let alert = UIAlertController(title: "Message", message: "Failed to colorize the image!", preferredStyle: .alert) 82 | alert.addAction(UIAlertAction(title: "Ok", style: .cancel)) 83 | UIViewController.topmostViewContoller.present(alert, animated: true) 84 | } 85 | 86 | private func selectImage(_ completion: @escaping ImageProvider.Completion) { 87 | let alert = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) 88 | alert.addAction(UIAlertAction(title: "Photos", style: .default) { _ in 89 | self.picker.image(with: completion) 90 | }) 91 | alert.addAction(UIAlertAction(title: "Camera", style: .default) { _ in 92 | self.scanner.image(with: completion) 93 | }) 94 | alert.addAction(UIAlertAction(title: "Cancel", style: .cancel)) 95 | UIViewController.topmostViewContoller.present(alert, animated: true) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #

Grayscale image colorizer

2 | 3 | This is the demo project of the article written in [my blog](https://onswiftwings.com/posts/image-colorization-coreml). The application colorizes grayscale images using CoreML, Vision and CoreImage frameworks. 4 | 5 |

6 | 7 |

8 | -------------------------------------------------------------------------------- /screenshots/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sgl0v/ImageColorizer/9d34b28e473ac2d7438cbece0979d00262c0d447/screenshots/demo.gif --------------------------------------------------------------------------------