Skip to content

Instantly share code, notes, and snippets.

@stephancasas
Created June 28, 2023 15:51
Show Gist options
  • Save stephancasas/2f7141999fe6b686d689d94f0029fac9 to your computer and use it in GitHub Desktop.
Save stephancasas/2f7141999fe6b686d689d94f0029fac9 to your computer and use it in GitHub Desktop.
Use Swift to flood/fill a PNG-format image with color
//
// 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