I'm really really really close here but I just don't know enough with why this is going wrong. It looks like either NSData or CGDataProvider or CGImage is skipping through every 8th row and then filling the remaining canvas with garbage so I'm only getting 1/8th of my image bunched at the top.
I think this must be some math error, but after going through the code several times, I'm not sure where this might be.
I'm trying to learn some Swift on MacOS and after a couple weeks I've managed to get this far and start doing some interesting things. If someone could help me out here I'd really appreciate it!
Also, it'd be good to know if I'm the right track for efficiently plotting individual pixels. It doesn't seem to be a thing people do so hard to find good examples of this.
import Foundation
import Cocoa
import UniformTypeIdentifiers
extension CGImage
{
//https://stackoverflow.com/questions/1320988/saving-cgimageref-to-a-png-file
var png: Data?
{
let cfdata: CFMutableData = CFDataCreateMutable(nil, 0)
if let destination = CGImageDestinationCreateWithData(cfdata, String(describing: UTType.png) as CFString, 1, nil)
{
CGImageDestinationAddImage(destination, self, nil)
if CGImageDestinationFinalize(destination)
{
return cfdata as Data
}
}
return nil
}
}
//https://blog.human-friendly.com/drawing-images-from-pixel-data-in-swift
struct PixelData {
var a:UInt8 = 255
var r:UInt8
var g:UInt8
var b:UInt8
}
func pixeldata_to_image(pixels: [PixelData], width: Int, height: Int)->CGImage
{
assert(pixels.count == Int(width * height))
let bitsPerComponent: Int = 8
let bitsPerPixel: Int = 32
let rgbColorSpace = CGColorSpaceCreateDeviceRGB()
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedFirst.rawValue)
var data = pixels
guard
let providerRef = CGDataProvider(data: NSData(bytes: &data, length: data.count * bitsPerPixel))
else
{
fatalError("CGDataProvider failure")
}
guard
let image = CGImage(
width: width,
height: height,
bitsPerComponent:bitsPerComponent,
bitsPerPixel: bitsPerPixel,
bytesPerRow: width * bitsPerPixel,
space: rgbColorSpace,
bitmapInfo: bitmapInfo,
provider: providerRef,
decode: nil,
shouldInterpolate: true,
intent: .defaultIntent
)
else
{
fatalError("CGImage failure")
}
return image
}
func testUsage()
{
let range_x: Int = 200
let range_y: Int = 200
let greyPixel = PixelData(a: 255, r: 128, g: 128, b: 128)
let blackPixel = PixelData(a: 255, r: 0, g: 0, b: 0)
let whitePixel = PixelData(a: 255, r: 255, g: 255, b: 255)
let yellowPixel = PixelData(a: 255, r: 255, g: 255, b: 0)
let greenPixel = PixelData(a: 255, r: 0, g: 255, b: 0)
let redPixel = PixelData(a: 255, r: 255, g: 0, b: 0)
var pixelData = [PixelData](repeating: greyPixel, count: Int(range_x * range_y))
var index: Int = 0
//populate pixelData
for x in 0 ..< range_x
{
for y in 0 ..< range_y
{
let pixel: PixelData
if x == 0 && y == 0 || x == 0 && y == range_x - 1 || x == 8 && y == 8
{
//grey draw specfic pixels
pixel = greyPixel
}
else if x == y
{
//yellow draw diagonal from top-left to bottom-right
pixel = yellowPixel
}
else if y == 0 || x == 0
{
//dblack raw top line and draw left line
pixel = blackPixel
}
else if y == range_y - 1 || x == range_y - 1
{
//white draw bottom line and draw right line
pixel = whitePixel
}
else if y == range_y / 2 || x == range_x / 2
{
//green draw horizontal center line and draw vertical center line
pixel = greenPixel
}
else
{
pixel = redPixel
}
//print("x:", x, "y:", y, "index:", index, "pixel:", pixel) //sanity check
pixelData[index] = pixel
index += 1
}
}
let image: CGImage = pixeldata_to_image(pixels: pixelData, width: range_x, height: range_y)
try? image.png!.write(to: URL(fileURLWithPath: "pixeldata.png"))
}
As pointed out in a comment I confused some bits and bytes. I added these two lines:
let bitsPerByte = 8
let bytesPerPixel: Int = bitsPerPixel/bitsPerByte
Then modified these two lines:
-let providerRef = CGDataProvider(data: NSData(bytes: &data, length: data.count * bitsPerPixel))
+let providerRef = CGDataProvider(data: NSData(bytes: &data, length: data.count * bytesPerPixel))
and
-bytesPerRow: width * bitsPerPixel,
+bytesPerRow: width * bytesPerPixel,
Works like a charm now!