cocoa_image 1.1.0

Read RGBA image using macOS Cocoa API
Documentation
/// Implementation using [core graphics](https://lib.rs/crates/core-graphics).
///
use objc::{msg_send, sel, sel_impl};
use core_foundation::data::CFData;
use core_graphics::image::CGImageRef;
use core_graphics::color_space::{CGColorSpace, kCGColorSpaceSRGB};
use core_graphics::geometry::{CGPoint, CGRect, CGSize};
use core_graphics::context::CGContext;
use core_graphics::base::{CGFloat, kCGImageAlphaPremultipliedLast};
use imgref::ImgVec;
use rgb::RGBA8;
use crate::error::Error;
use objc::runtime::{Class, Object};

/// Returns an RGBA 8-bit bitmap in sRGB color space with premultiplied alpha.
///
/// See [`ImgRef`](https://lib.rs/crates/rgb) for instructions how to use the returned bitmap.
pub fn decode_image_as_rgba_premultiplied(image_data: &[u8]) -> Result<ImgVec<RGBA8>, Error> {
    unsafe {
        let pool = AutoreleasePool(msg_send![Class::get("NSAutoreleasePool").ok_or(Error::ObjcError)?, new]);
        let data = CFData::from_buffer(image_data);
        let rep: *mut Object = msg_send![Class::get("NSBitmapImageRep").ok_or(Error::ObjcError)?, imageRepWithData:data];
        if rep.is_null() {
            return Err(Error::DecodeError);
        }
        let wow_this_is_a_dodgy_hack: &CGImageRef = msg_send![rep, CGImage];
        let image = wow_this_is_a_dodgy_hack.to_owned();

        let width = image.width();
        let height = image.height();
        let mut pixel_data = vec![RGBA8::new(0,0,0,0); width * height];
        let colorspace = CGColorSpace::create_with_name(kCGColorSpaceSRGB).ok_or(Error::ObjcError)?;
        let context = CGContext::create_bitmap_context(Some(pixel_data.as_mut_ptr().cast()),
            width, height,
            8, width * 4,
            &colorspace,
            kCGImageAlphaPremultipliedLast);
        context.draw_image(CGRect::new(&CGPoint::new(0., 0.), &CGSize::new(width as CGFloat, height as CGFloat)), &image);
        drop(pool);
        Ok(ImgVec::new(pixel_data, width, height))
    }
}

pub fn decode_image_as_rgba(image_data: &[u8]) -> Result<ImgVec<RGBA8>, Error> {
    // macOS is so silly it doesn't have non-premultiplied bitmaps
    let mut img = decode_image_as_rgba_premultiplied(image_data)?;
    for px in img.pixels_mut() {
        if px.a > 0 && px.a != 255 {
            *px = RGBA8 {
                r: (u16::from(px.r) * 255 / u16::from(px.a)) as u8,
                g: (u16::from(px.g) * 255 / u16::from(px.a)) as u8,
                b: (u16::from(px.b) * 255 / u16::from(px.a)) as u8,
                a: px.a,
            }
        }
    }
    Ok(img)
}

#[link(name = "AppKit", kind = "framework")]
extern {
}

struct AutoreleasePool(*mut Object);
impl Drop for AutoreleasePool {
    fn drop(&mut self) {
        unsafe {
            let _: () = msg_send![self.0, drain];
            self.0 = ::std::ptr::null_mut();
        }
    }
}

#[test]
fn read_test_png_file() {
    let img = decode_image_as_rgba_premultiplied(&std::fs::read("test.png").expect("test.png")).expect("decoded");
    assert_eq!(2, img.width());
    assert_eq!(2, img.height());
    assert_eq!(RGBA8::new(0,0,0,255), img.buf()[0]);
    assert_eq!(RGBA8::new(255,0,0,255), img.buf()[1]);
    assert_eq!(RGBA8::new(0,0,0,0), img.buf()[2]);
    assert_eq!(RGBA8::new(0,0,0,0), img.buf()[3]);
}