I am trying to create an NSImage in macOS using NSBitmapImageRep while having full control over its pixel data. Specifically, I want to:
Manually allocate and fill pixel data with a solid color.
Support both RGB (when hasAlpha = false) and RGBA (when hasAlpha = true).
Ensure no automatic padding is added when hasAlpha = false (i.e., the image should be stored in memory as tightly packed RGB data without extra unused bytes).
Avoid using NSGraphicsContext or CGContext, relying solely on NSBitmapImageRep to construct the image, because, based on what I learned from other questions and issues, NSGraphicsContext does not support RGB-only images.
Here is my function, but I still see unwanted padding added to my image. Here is the output of this code:
Expected Bytes Per Row: 3072 bytes/row
Actual Bytes Per Row: 4096 bytes/row
Image has padding! Extra 1024 bytes per row.
use case:
let size: NSSize = NSSize(width: 1024, height: 1024)
let myImage = createImage(hasAlpha: false, pixelSize: size, color: .red)
if let validImage: NSImage = myImage {
if let tiffData: Data = validImage.tiffRepresentation, let nsBitmapImageRep: NSBitmapImageRep = NSBitmapImageRep(data: tiffData) {
let expectedBytesPerRow = nsBitmapImageRep.samplesPerPixel*nsBitmapImageRep.pixelsWide
let actualBytesPerRow = nsBitmapImageRep.bytesPerRow
print("Expected Bytes Per Row: \(expectedBytesPerRow) bytes/row")
print("Actual Bytes Per Row: \(actualBytesPerRow) bytes/row")
if actualBytesPerRow > expectedBytesPerRow {
print("Image has padding! Extra \(actualBytesPerRow - expectedBytesPerRow) bytes per row.")
} else {
print("No padding detected.")
}
}
}
func createImage(hasAlpha: Bool, pixelSize: NSSize, color: NSColor) -> NSImage? {
let width = Int(pixelSize.width)
let height = Int(pixelSize.height)
let components = hasAlpha ? 4 : 3 // RGBA or RGB
let bitsPerSample = 8
let bytesPerPixel = components
let bytesPerRow = width * bytesPerPixel // Ensure exact memory layout, no padding
// Allocate memory for pixel buffer
guard let pixelData = malloc(height * bytesPerRow)?.assumingMemoryBound(to: UInt8.self) else {
print("Failed to allocate memory.")
return nil
}
defer { free(pixelData) } // Free memory on function exit
// Convert color to RGB space
let colorSpace = NSColorSpace.deviceRGB
guard let colorInRGB = color.usingColorSpace(colorSpace) else {
print("Failed to convert color to RGB space.")
return nil
}
let red = UInt8(colorInRGB.redComponent * 255)
let green = UInt8(colorInRGB.greenComponent * 255)
let blue = UInt8(colorInRGB.blueComponent * 255)
let alpha = hasAlpha ? UInt8(colorInRGB.alphaComponent * 255) : 255
// Fill pixel buffer manually
for y in 0..<height {
for x in 0..<width {
let pixelIndex = (y * bytesPerRow) + (x * bytesPerPixel)
pixelData[pixelIndex] = red
pixelData[pixelIndex + 1] = green
pixelData[pixelIndex + 2] = blue
if hasAlpha {
pixelData[pixelIndex + 3] = alpha
}
}
}
// Create a pointer to the pixel buffer
var pixelDataPlane: UnsafeMutablePointer<UInt8>? = pixelData
// Create NSBitmapImageRep without extra padding
guard let bitmapRep = NSBitmapImageRep(
bitmapDataPlanes: &pixelDataPlane, // Pass pointer to pointer
pixelsWide: width,
pixelsHigh: height,
bitsPerSample: bitsPerSample,
samplesPerPixel: components,
hasAlpha: hasAlpha,
isPlanar: false,
colorSpaceName: .deviceRGB,
bytesPerRow: bytesPerRow, // Prevent unwanted padding
bitsPerPixel: bitsPerSample * bytesPerPixel
) else {
print("Failed to create bitmap representation.")
return nil
}
// Create final NSImage
let image = NSImage(size: pixelSize)
image.addRepresentation(bitmapRep)
return image
}