Last active
April 25, 2023 16:45
-
-
Save JoshuaSullivan/5951e08ff0f3e155ef52220a181864e8 to your computer and use it in GitHub Desktop.
The Color Cube Image Creator creates the specially formatted images needed to create data for the CIColorCube filter.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// ColorCubeImageCreator.swift | |
// ColorCubeImageCreator | |
// | |
// Created by Joshua Sullivan on 4/25/16. | |
// Copyright © 2016 Joshua Sullivan. All rights reserved. | |
// | |
import UIKit | |
public final class ColorCubeImageCreator { | |
enum Dimension: Int { | |
/// A very small color cube. May exhibit posterization. | |
case four = 4 | |
/// This size is good enough for many applications using noisy or lower-quality input. | |
case sixteen = 16 | |
/// Higher quality. There is rarely a need to go beyond this setting. | |
case sixtyFour = 64 | |
/// Excessive quality. Image is 4096x4096 and almost 35MB. | |
case twoHundredFiftySix = 256 | |
} | |
/// Creates a reference color cube image for the indicated size and writes it to disk. | |
/// It is necessary to write to disk as part of this method in order to avoid either | |
/// leaking the image buffer or having it garbage collected before the file can be written. | |
/// | |
/// - Parameter size: A value from the `ColorCubeImageCreator.Dimension` enum. | |
/// - Parameter saveLocation: An optional URL for where to store the created image. | |
/// If left `nil` the method will place the image in the Documents folder in the app sandbox. | |
/// - Returns: A Bool indicating the success or failure of the operation. | |
static func createColorCube(size: Dimension, saveLocation: NSURL?) -> Bool { | |
let cubeSize = size.rawValue | |
/// The total number of pixels we'll need to represnt all points in the cube. | |
let pixels = cubeSize * cubeSize * cubeSize | |
/// The square dimensions of the image that will store exactly enough pixels. | |
/// This is why I limit the available dimensions. The other powers of 2 have | |
/// non-integral square roots. | |
let imageSize = Int(sqrt(Double(pixels))) | |
/// We're only encoding RGB. No alpha. | |
let channels = 3 | |
/// A UInt8 is pretty obviously single-byte. | |
let bytesPerChannel = 1 | |
/// The total number of bytes we will need to encode all of the data. | |
let memorySize = pixels * channels * bytesPerChannel | |
/// Pre-calculate the square of the cube size. | |
let cubeSizeSquared = cubeSize * cubeSize | |
/// This is the storage we'll be writing our RGB values to. | |
let imageBuffer = UnsafeMutablePointer<UInt8>.alloc(memorySize) | |
// For the 2 smallest sizes, the dealloc command can have the effect of destroying the bytes | |
// backing the image before it can be written to disk, resulting in a corrupted image. That | |
// is why we save the image as part of this block. | |
defer { imageBuffer.dealloc(memorySize) } | |
/// The amount to vary the red, green, and blue channel values by at each step of the calculation. | |
let colorStep = 255.0 / Float(cubeSize - 1) | |
/// The offset in the imageBuffer we're working on. | |
var offset = 0 | |
for i in 0..<pixels { | |
offset = i * channels | |
// Red value | |
imageBuffer[offset + 0] = UInt8(round(Float(i % cubeSize) * colorStep)) | |
// Green value | |
imageBuffer[offset + 1] = UInt8(round(Float((i / cubeSize) % cubeSize) * colorStep)) | |
// Blue value | |
imageBuffer[offset + 2] = UInt8(round(Float(i / cubeSizeSquared) * colorStep)) | |
} | |
/// Data provide created with the calculated values in the imageBuffer. | |
let dataProvider = CGDataProviderCreateWithData(nil, imageBuffer, memorySize, nil) | |
// The following are a bunch of setup values for the CGImageCreate() function. | |
let bitsPerComponent = 8 * bytesPerChannel | |
let bitsPerPixel = channels * bitsPerComponent | |
let bytesPerRow = imageSize * channels | |
let colorSpace = CGColorSpaceCreateDeviceRGB() | |
let alphaInfo = CGImageAlphaInfo.None.rawValue | |
let bitmapInfo: CGBitmapInfo = [.ByteOrderDefault, CGBitmapInfo(rawValue: alphaInfo)] | |
let renderingIntent: CGColorRenderingIntent = .RenderingIntentDefault | |
// Attempt to process the buffer of bytes into an image. | |
guard let imageRef = CGImageCreate(imageSize, imageSize, bitsPerComponent, bitsPerPixel, bytesPerRow, colorSpace, bitmapInfo, dataProvider, nil, false, renderingIntent) else { | |
assertionFailure("Unable to create CGImage.") | |
return false | |
} | |
/// The UIImage created from the buffer data. | |
let cubeImage = UIImage(CGImage: imageRef) | |
/// The PNG representation of the `cubeImage`. | |
let imageData = UIImagePNGRepresentation(cubeImage) | |
// Check that we can establish a URL for saving. | |
guard let imageURL = saveLocation ?? defaultLocationForSize(size) else { | |
assertionFailure("Can't access save location.") | |
return false | |
} | |
// Try to write the image to disk. | |
do { | |
try imageData?.writeToURL(imageURL, options: []) | |
print("Image successfully written to:", imageURL.absoluteString) | |
return true | |
} catch let error { | |
print("Failed to write image:", error) | |
return false | |
} | |
} | |
/// Generates default URLs for the various Dimension values. | |
private static func defaultLocationForSize(size: Dimension) -> NSURL? { | |
let imageName = "ColorCubeReferenceImage\(size.rawValue).png" | |
guard let docsDir = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).first else { | |
assertionFailure("Can't get documents directory!") | |
return nil | |
} | |
let imageURL = docsDir.URLByAppendingPathComponent(imageName) | |
return imageURL | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// ColorCubeImageCreator.swift | |
// ColorCubeImageCreator | |
// | |
// Created by Joshua Sullivan on 10/01/16. | |
// Copyright © 2016 Joshua Sullivan. All rights reserved. | |
// | |
import UIKit | |
public final class ColorCubeImageCreator { | |
enum Dimension: Int { | |
/// A very small color cube. May exhibit posterization. | |
case four = 4 | |
/// This size is good enough for many applications using noisy or lower-quality input. | |
case sixteen = 16 | |
/// Higher quality. There is rarely a need to go beyond this setting. | |
case sixtyFour = 64 | |
/// Excessive quality. Image is 4096x4096 and almost 35MB. | |
case twoHundredFiftySix = 256 | |
} | |
/// Creates a reference color cube image for the indicated size and writes it to disk. | |
/// It is necessary to write to disk as part of this method in order to avoid either | |
/// leaking the image buffer or having it garbage collected before the file can be written. | |
/// | |
/// - Parameter size: A value from the `ColorCubeImageCreator.Dimension` enum. | |
/// - Parameter saveLocation: An optional URL for where to store the created image. | |
/// If left `nil` the method will place the image in the Documents folder in the app sandbox. | |
/// - Returns: A Bool indicating the success or failure of the operation. | |
static func createColorCube(size: Dimension, saveLocation: URL?) -> Bool { | |
let cubeSize = size.rawValue | |
/// The total number of pixels we'll need to represnt all points in the cube. | |
let pixels = cubeSize * cubeSize * cubeSize | |
/// The square dimensions of the image that will store exactly enough pixels. | |
/// This is why I limit the available dimensions. The other powers of 2 have | |
/// non-integral square roots. | |
let imageSize = Int(sqrt(Double(pixels))) | |
/// We're only encoding RGB. No alpha. | |
let channels = 3 | |
/// A UInt8 is pretty obviously single-byte. | |
let bytesPerChannel = 1 | |
/// The total number of bytes we will need to encode all of the data. | |
let memorySize = pixels * channels * bytesPerChannel | |
/// Pre-calculate the square of the cube size. | |
let cubeSizeSquared = cubeSize * cubeSize | |
/// This is the storage we'll be writing our RGB values to. | |
let imageBuffer = UnsafeMutablePointer<UInt8>.allocate(capacity: memorySize) | |
// For the 2 smallest sizes, the dealloc command can have the effect of destroying the bytes | |
// backing the image before it can be written to disk, resulting in a corrupted image. That | |
// is why we save the image as part of this block. | |
defer { imageBuffer.deallocate(capacity: memorySize) } | |
/// The amount to vary the red, green, and blue channel values by at each step of the calculation. | |
let colorStep = 255.0 / Float(cubeSize - 1) | |
/// The offset in the imageBuffer we're working on. | |
var offset = 0 | |
for i in 0..<pixels { | |
offset = i * channels | |
// Red value | |
imageBuffer[offset + 0] = UInt8(round(Float(i % cubeSize) * colorStep)) | |
// Green value | |
imageBuffer[offset + 1] = UInt8(round(Float((i / cubeSize) % cubeSize) * colorStep)) | |
// Blue value | |
imageBuffer[offset + 2] = UInt8(round(Float(i / cubeSizeSquared) * colorStep)) | |
} | |
/// Data provide created with the calculated values in the imageBuffer. | |
let callback: CGDataProviderReleaseDataCallback = { _,_,_ in } | |
guard let dataProvider = CGDataProvider(dataInfo: nil, data: imageBuffer, size: memorySize, releaseData: callback) else { | |
preconditionFailure("Couldn't create CGDataProvider.") | |
} | |
// The following are a bunch of setup values for the CGImageCreate() function. | |
let bitsPerComponent = 8 * bytesPerChannel | |
let bitsPerPixel = channels * bitsPerComponent | |
let bytesPerRow = imageSize * channels | |
let colorSpace = CGColorSpaceCreateDeviceRGB() | |
let alphaInfo = CGImageAlphaInfo.none.rawValue | |
let bitmapInfo: CGBitmapInfo = [CGBitmapInfo(rawValue: alphaInfo)] | |
let renderingIntent: CGColorRenderingIntent = .defaultIntent | |
// Attempt to process the buffer of bytes into an image. | |
guard let imageRef = CGImage(width: imageSize, height: imageSize, bitsPerComponent: bitsPerComponent, bitsPerPixel: bitsPerPixel, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo, provider: dataProvider, decode: nil, shouldInterpolate: false, intent: renderingIntent) else { | |
assertionFailure("Unable to create CGImage.") | |
return false | |
} | |
/// The UIImage created from the buffer data. | |
let cubeImage = UIImage(cgImage: imageRef) | |
/// The PNG representation of the `cubeImage`. | |
let imageData = UIImagePNGRepresentation(cubeImage) | |
// Check that we can establish a URL for saving. | |
guard let imageURL = saveLocation ?? defaultLocationForSize(size: size) else { | |
assertionFailure("Can't access save location.") | |
return false | |
} | |
// Try to write the image to disk. | |
do { | |
try imageData?.write(to: imageURL, options: []) | |
print("Image successfully written to:", imageURL.absoluteString) | |
return true | |
} catch let error { | |
print("Failed to write image:", error) | |
return false | |
} | |
} | |
/// Generates default URLs for the various Dimension values. | |
private static func defaultLocationForSize(size: Dimension) -> URL? { | |
let imageName = "ColorCubeReferenceImage\(size.rawValue).png" | |
guard let docsDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { | |
assertionFailure("Can't get documents directory!") | |
return nil | |
} | |
let imageURL = docsDir.appendingPathComponent(imageName) | |
return imageURL | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// ViewController.swift | |
// ColorCubeImageCreator | |
// | |
// Created by Joshua Sullivan on 4/25/16. | |
// Copyright © 2016 Joshua Sullivan. All rights reserved. | |
// | |
import UIKit | |
class ViewController: UIViewController { | |
override func viewDidLoad() { | |
super.viewDidLoad() | |
// Do any additional setup after loading the view, typically from a nib. | |
let sizes: [ColorCubeImageCreator.Dimension] = [.four, .sixteen, .sixtyFour, .twoHundredFiftySix] | |
let results = sizes.map { | |
ColorCubeImageCreator.createColorCube($0, saveLocation: nil) | |
} | |
print("results:", results) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment