150 lines
7.4 KiB
Plaintext
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;
|