Created
June 28, 2023 15:51
-
-
Save stephancasas/2f7141999fe6b686d689d94f0029fac9 to your computer and use it in GitHub Desktop.
Use Swift to flood/fill a PNG-format image with color
This file contains hidden or 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
// | |
// ColorFloodExample.swift | |
// ColorFloodExample | |
// | |
// Created by Stephan Casas on 6/28/23. | |
// | |
import SwiftUI; | |
import Accelerate; | |
import CoreGraphics; | |
let imagePath = "\(NSHomeDirectory())/Desktop/sample.png"; | |
/// Load the target image. | |
/// | |
guard let image = NSImage( | |
contentsOfFile: imagePath | |
)?.cgImage(forProposedRect: nil, context: nil, hints: nil) else { | |
exit(1); | |
} | |
// MARK: - Image Context | |
/// Assuming the *ARGB8888* pixel format, use 8 bits per color | |
/// component and 4 bytes per pixel — one byte per component | |
/// in *red, green, blue, alpha.* | |
/// | |
let bitsPerComponent = 8; | |
let bytesPerPixel = 4; | |
let bytesPerRow = image.width * bytesPerPixel; | |
/// Describe the format of the pixel storage. | |
/// | |
/// For *ARGB8888*, the color values are pre-multiplied by the | |
/// alpha value and the alpha value is stored in the most | |
/// significant bit for each pixel value — using little-endian | |
/// format. | |
/// | |
let bitmapInfo = CGBitmapInfo( | |
rawValue: CGImageAlphaInfo.premultipliedFirst.rawValue | | |
CGBitmapInfo.byteOrder32Little.rawValue); | |
/// Create the bitmap "canvas" in which the mutable | |
/// image will draw. | |
/// | |
guard let context = CGContext( | |
data: nil, | |
width: image.width, | |
height: image.height, | |
bitsPerComponent: bitsPerComponent, | |
bytesPerRow: bytesPerRow, | |
space: CGColorSpaceCreateDeviceRGB(), | |
bitmapInfo: bitmapInfo.rawValue | |
) else { | |
exit(1); | |
} | |
/// Draw the image on the "canvas." | |
/// | |
context.draw(image, | |
in: CGRectMake( | |
0, 0, | |
CGFloat(image.width), | |
CGFloat(image.height) | |
)); | |
// MARK: - Flood Parameters | |
/// # Flood Color | |
/// | |
/// The color with which to flood the image's pixel geometry | |
/// given as, `[blue, green, red, alpha]`. | |
/// | |
/// You can flood with an alpha value of 0 to "erase" pixels. | |
/// | |
/// This example uses RGB hex color `#288DCE`. | |
/// | |
var floodColor: [UInt8] = [206, 141, 40, 255]; | |
/// # Flood Origin | |
/// | |
/// From the uppermost-leftmost point in the image's frame, | |
/// where should the flood operation begin? | |
/// | |
let floodOrigin: (x: vImagePixelCount, y: vImagePixelCount) = (x: 80, y: 50); | |
// MARK: - Flood Operation | |
/// Prepare a buffer in which to apply the flood | |
/// operation. | |
/// | |
var buffer = vImage_Buffer( | |
data: context.data, | |
height: vImagePixelCount(image.height), | |
width: vImagePixelCount(image.width), | |
rowBytes: bytesPerRow); | |
/// Perform the flood operation. | |
/// | |
let floodResult = vImageFloodFill_ARGB8888( | |
&buffer, | |
nil, | |
floodOrigin.x, | |
floodOrigin.y, | |
&floodColor, | |
4, | |
vImage_Flags(kvImageNoFlags)); | |
switch floodResult { | |
case kvImageNullPointerArgument: | |
print("Invalid nil pointer passed to flood function."); | |
exit(1); | |
case kvImageInvalidParameter: | |
print("Invalid parameter passed to flood function.") | |
exit(1); | |
case kvImageUnknownFlagsBit: | |
print("Invalid flag argument passed to flood function.") | |
exit(1); | |
default: | |
// no-op | |
break; | |
} | |
// MARK: - Flood Output | |
/// Get the flood output as a CGImage. | |
/// | |
guard let cgFloodOutput = context.makeImage() else { | |
exit(1); | |
} | |
/// Cast the CGImage as an NSImage. | |
/// | |
let nsFloodOutput = NSImage( | |
cgImage: cgFloodOutput, | |
size: NSMakeSize( | |
CGFloat(image.width), | |
CGFloat(image.height))); | |
/// Write the modified image to disk. | |
/// | |
if let tiffData = nsFloodOutput.tiffRepresentation, | |
let imageRep = NSBitmapImageRep(data: tiffData), | |
let imageData = imageRep.representation(using: .png, properties: [:]) { | |
do { | |
try imageData.write(to: URL(filePath: "\(NSHomeDirectory())/Desktop/filled.png")); | |
print("Done"); | |
} catch { | |
print( | |
"The resulting image could not be written to the disk.", | |
"Check your app's sandbox and entitlement settings." | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment