mcap/modules/Image_Decode/imageio.jai

150 lines
7.4 KiB
Plaintext

// 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;