// ImageIO, macOS's own codecs: HEIC and AVIF, WebP, TIFF and the camera RAW formats, as WIC reads them // on Windows. Only the few Core Foundation, ImageIO and Core Graphics calls used are declared. // The primary image in bytes, as non-premultiplied RGBA (default allocator), as stored: not turned. // orientation is what ImageIO says the image's orientation is (1-8, from a HEIF's irot and imir, or // its EXIF), for image_decode to apply where exif.jai cannot read it. // // The pixels keep the image's own RGB color space, as WIC and stb_image leave them; only gray, CMYK // and the like are converted, to sRGB. imageio_decode :: (bytes: [] u8) -> Image, ok: bool, error: string, orientation: s64 { image: Image; // Not copied: the source is let go of before this returns. data := CFDataCreateWithBytesNoCopy(null, bytes.data, bytes.count, kCFAllocatorNull); if !data return image, false, "ImageIO could not take the bytes", 1; defer CFRelease(data); source := CGImageSourceCreateWithData(data, null); if !source return image, false, "ImageIO cannot read it", 1; defer CFRelease(source); if CGImageSourceGetCount(source) < 1 return image, false, "ImageIO finds no image in it", 1; index := CGImageSourceGetPrimaryImageIndex(source); cg := CGImageSourceCreateImageAtIndex(source, index, null); if !cg return image, false, "ImageIO cannot decode it", 1; defer CGImageRelease(cg); orientation: s32 = 1; properties := CGImageSourceCopyPropertiesAtIndex(source, index, null); if properties { number := CFDictionaryGetValue(properties, kCGImagePropertyOrientation); if number CFNumberGetValue(number, kCFNumberSInt32Type, *orientation); CFRelease(properties); } w := CGImageGetWidth(cg); h := CGImageGetHeight(cg); if !w || !h return image, false, "ImageIO gives it no size", orientation; if w * h * 4 > U32_MAX return image, false, tprint("it is % x %, too large to decode in one piece", w, h), orientation; // Opaque images are drawn as 8-bit RGB with the fourth byte left out, so their pixels are exact. // Others are drawn premultiplied, the only RGBA a bitmap context takes, at 16 bits a channel so // that dividing back gives the 8-bit colors again, even where alpha is small. alpha_info := CGImageGetAlphaInfo(cg); opaque := alpha_info == kCGImageAlphaNone || alpha_info == kCGImageAlphaNoneSkipLast || alpha_info == kCGImageAlphaNoneSkipFirst; bits: u64 = 16; bitmap_info: u32 = kCGBitmapByteOrder16Little | kCGImageAlphaPremultipliedLast; if opaque { bits = 8; bitmap_info = kCGBitmapByteOrder32Big | kCGImageAlphaNoneSkipLast; } row_bytes := w * bits / 2; count := cast(s64) (w * h); pixels := cast(*u8) alloc(count * 4,, context.default_allocator); drawn := pixels; if !opaque drawn = cast(*u8) alloc(count * 8,, context.default_allocator); defer if drawn != pixels free(drawn,, context.default_allocator); memset(drawn, 0, cast(s64) (row_bytes * h)); space := CGImageGetColorSpace(cg); own_space := space && CGColorSpaceGetModel(space) == kCGColorSpaceModelRGB; bitmap: CGContextRef; if own_space bitmap = CGBitmapContextCreate(drawn, w, h, bits, row_bytes, space, bitmap_info); if !bitmap { // Not RGB, or a space a bitmap context cannot draw in (an extended range one): converted to sRGB. srgb := CGColorSpaceCreateWithName(kCGColorSpaceSRGB); defer CGColorSpaceRelease(srgb); bitmap = CGBitmapContextCreate(drawn, w, h, bits, row_bytes, srgb, bitmap_info); } if !bitmap { free(pixels,, context.default_allocator); return image, false, "Core Graphics could not make a bitmap to draw it in", orientation; } CGContextSetBlendMode(bitmap, kCGBlendModeCopy); CGContextDrawImage(bitmap, .{.{0, 0}, .{cast(float64) w, cast(float64) h}}, cg); CGContextRelease(bitmap); if opaque { for 0..count - 1 pixels[it * 4 + 3] = 255; } else { wide := cast(*u16) drawn; for 0..count - 1 { in := wide + it * 4; out := pixels + it * 4; a := cast(u32) in[3]; out[3] = cast(u8) ((a * 255 + 32767) / 65535); if a == 0 { out[0], out[1], out[2] = 0; continue; } for c: 0..2 out[c] = cast(u8) min(255, (cast(u32) in[c] * 255 + a / 2) / a); } } image = .{pixels = pixels, width = cast(s32) w, height = cast(s32) h}; return image, true, "", orientation; } #scope_file CFTypeRef :: *void; CFDataRef :: *void; CFDictionaryRef :: *void; CFStringRef :: *void; CFAllocatorRef :: *void; CGImageSourceRef :: *void; CGImageRef :: *void; CGColorSpaceRef :: *void; CGContextRef :: *void; CGPoint :: struct { x, y: float64; } CGSize :: struct { width, height: float64; } CGRect :: struct { origin: CGPoint; size: CGSize; } kCFNumberSInt32Type :: 3; kCGColorSpaceModelRGB :: 1; kCGBlendModeCopy :: 17; kCGImageAlphaNone :: 0; kCGImageAlphaPremultipliedLast :: 1; kCGImageAlphaNoneSkipLast :: 5; kCGImageAlphaNoneSkipFirst :: 6; kCGBitmapByteOrder16Little :: 1 << 12; kCGBitmapByteOrder32Big :: 4 << 12; CoreFoundation :: #library,system "CoreFoundation"; ImageIO :: #library,system "ImageIO"; CoreGraphics :: #library,system "CoreGraphics"; kCFAllocatorNull: CFAllocatorRef #elsewhere CoreFoundation; kCGImagePropertyOrientation: CFStringRef #elsewhere ImageIO; kCGColorSpaceSRGB: CFStringRef #elsewhere CoreGraphics; CFDataCreateWithBytesNoCopy :: (allocator: CFAllocatorRef, bytes: *u8, length: s64, deallocator: CFAllocatorRef) -> CFDataRef #foreign CoreFoundation; CFRelease :: (object: CFTypeRef) #foreign CoreFoundation; CFDictionaryGetValue :: (dictionary: CFDictionaryRef, key: *void) -> *void #foreign CoreFoundation; CFNumberGetValue :: (number: *void, type: s64, value: *void) -> u8 #foreign CoreFoundation; CGImageSourceCreateWithData :: (data: CFDataRef, options: CFDictionaryRef) -> CGImageSourceRef #foreign ImageIO; CGImageSourceGetCount :: (source: CGImageSourceRef) -> u64 #foreign ImageIO; CGImageSourceGetPrimaryImageIndex :: (source: CGImageSourceRef) -> u64 #foreign ImageIO; CGImageSourceCreateImageAtIndex :: (source: CGImageSourceRef, index: u64, options: CFDictionaryRef) -> CGImageRef #foreign ImageIO; CGImageSourceCopyPropertiesAtIndex :: (source: CGImageSourceRef, index: u64, options: CFDictionaryRef) -> CFDictionaryRef #foreign ImageIO; CGImageGetWidth :: (image: CGImageRef) -> u64 #foreign CoreGraphics; CGImageGetHeight :: (image: CGImageRef) -> u64 #foreign CoreGraphics; CGImageGetAlphaInfo :: (image: CGImageRef) -> u32 #foreign CoreGraphics; CGImageGetColorSpace :: (image: CGImageRef) -> CGColorSpaceRef #foreign CoreGraphics; CGImageRelease :: (image: CGImageRef) #foreign CoreGraphics; CGColorSpaceGetModel :: (space: CGColorSpaceRef) -> s32 #foreign CoreGraphics; CGColorSpaceCreateWithName :: (name: CFStringRef) -> CGColorSpaceRef #foreign CoreGraphics; CGColorSpaceRelease :: (space: CGColorSpaceRef) #foreign CoreGraphics; CGBitmapContextCreate :: (data: *void, width: u64, height: u64, bits_per_component: u64, bytes_per_row: u64, space: CGColorSpaceRef, bitmap_info: u32) -> CGContextRef #foreign CoreGraphics; CGContextSetBlendMode :: (context_ref: CGContextRef, mode: s32) #foreign CoreGraphics; CGContextDrawImage :: (context_ref: CGContextRef, rect: CGRect, image: CGImageRef) #foreign CoreGraphics; CGContextRelease :: (context_ref: CGContextRef) #foreign CoreGraphics;