x-graphics 0.2.1

Graphics framework for X
Documentation
use core_graphics::{
    color_space::{CGColorRenderingIntent, CGColorSpace},
    context::CGContext,
    data_provider,
    image::{self, CGImage},
};

use super::{canvas::CoreGraphicsCanvas, device::CoreGraphicsDeviceContext};
use crate::{
    base::Dimension,
    bitmap::{BitmapBackend, BitmapPixelFormat},
    error::GraphicsError,
    geometry::{FSize, ISize},
    Float,
};

const DEFAULT_ALIGNMENT: usize = 4;

#[derive(Clone, Debug)]
pub struct CoreGraphicsBitmap {
    image: CGImage,
    context: CGContext,
    data: Option<Vec<u8>>,
}

impl Dimension for CoreGraphicsBitmap {
    fn size(&self) -> FSize {
        FSize::new(self.image.width() as Float, self.image.height() as Float)
    }

    fn pixel_size(&self) -> ISize {
        ISize::new(self.image.width() as i32, self.image.height() as i32)
    }
}

impl BitmapBackend for CoreGraphicsBitmap {
    type DeviceContextType = CoreGraphicsDeviceContext;
    type CanvasType = CoreGraphicsCanvas;

    fn new(
        _context: Option<&Self::DeviceContextType>,
        width: usize,
        height: usize,
        pixel_format: BitmapPixelFormat,
        _canvas: &Self::CanvasType,
    ) -> Result<Self, GraphicsError> {
        let cs = Self::get_color_space(pixel_format)?;
        let bitmap_info = Self::get_bitmap_info(pixel_format)?;
        let bytes_per_row = width + (DEFAULT_ALIGNMENT - 1) & !(DEFAULT_ALIGNMENT - 1);
        let mut data = vec![0; width * height * pixel_format.bytes_per_pixel()];
        let dp = unsafe { data_provider::CGDataProvider::from_slice(data.as_slice()) };
        let img = CGImage::new(
            width,
            height,
            8,
            pixel_format.bits_per_pixel(),
            bytes_per_row,
            cs.as_ref(),
            bitmap_info,
            dp.as_ref(),
            None,
            false,
            CGColorRenderingIntent::Default,
        )
        .ok_or(GraphicsError::CreationFailed(stringify!(CGImage).to_string()))?;
        let ctx = unsafe {
            CGContext::new_bitmap_context_with_data(data.as_mut_slice(), width, height, 8, bytes_per_row, cs.as_ref(), bitmap_info)
                .ok_or(GraphicsError::CreationFailed(stringify!(CGContext).to_string()))?
        };
        Ok(Self {
            image: img,
            context: ctx,
            data: Some(data),
        })
    }

    fn from_buffer(
        _context: Option<&Self::DeviceContextType>,
        buffer: &[u8],
        pitch: usize,
        width: usize,
        height: usize,
        pixel_format: BitmapPixelFormat,
        _canvas: &Self::CanvasType,
    ) -> Result<Self, GraphicsError> {
        let cs = Self::get_color_space(pixel_format)?;
        let bitmap_info = Self::get_bitmap_info(pixel_format)?;
        let dp = unsafe { data_provider::CGDataProvider::from_slice(buffer) };
        let img = CGImage::new(
            width,
            height,
            8,
            pixel_format.bits_per_pixel(),
            pitch,
            cs.as_ref(),
            bitmap_info,
            dp.as_ref(),
            None,
            false,
            CGColorRenderingIntent::Default,
        )
        .ok_or(GraphicsError::CreationFailed(stringify!(CGImage).to_string()))?;
        let ctx = unsafe {
            let buffer_slice = std::slice::from_raw_parts_mut(buffer as *const _ as *mut u8, pitch * height);

            CGContext::new_bitmap_context_with_data(buffer_slice, width, height, 8, pitch, cs.as_ref(), bitmap_info)
                .ok_or(GraphicsError::CreationFailed(stringify!(CGContext).to_string()))?
        };
        Ok(Self {
            image: img,
            context: ctx,
            data: None,
        })
    }
}

impl CoreGraphicsBitmap {
    pub(super) fn context(&self) -> &CGContext {
        &self.context
    }

    pub(super) fn context_mut(&mut self) -> &mut CGContext {
        &mut self.context
    }

    pub(super) fn image(&self) -> &CGImage {
        &self.image
    }

    fn get_color_space(pixel_format: BitmapPixelFormat) -> Result<Option<CGColorSpace>, GraphicsError> {
        match pixel_format {
            BitmapPixelFormat::A8 => Ok(CGColorSpace::new_device_gray()),
            BitmapPixelFormat::ARGB32 | BitmapPixelFormat::BGRA32 | BitmapPixelFormat::ABGR32 | BitmapPixelFormat::RGBA32 => {
                Ok(CGColorSpace::new_device_rgb())
            }
            _ => Err(GraphicsError::Unsupported(pixel_format.to_string())),
        }
    }

    fn get_bitmap_info(pixel_format: BitmapPixelFormat) -> Result<u32, GraphicsError> {
        match pixel_format {
            BitmapPixelFormat::A8 => Ok(image::kCGImageAlphaNone),
            BitmapPixelFormat::ARGB32 => Ok(image::kCGImageAlphaPremultipliedFirst | image::kCGImageByteOrder32Big),
            BitmapPixelFormat::BGRA32 => Ok(image::kCGImageAlphaPremultipliedFirst | image::kCGImageByteOrder32Little),
            BitmapPixelFormat::ABGR32 => Ok(image::kCGImageAlphaPremultipliedLast | image::kCGImageByteOrder32Little),
            BitmapPixelFormat::RGBA32 => Ok(image::kCGImageAlphaPremultipliedLast | image::kCGImageByteOrder32Big),
            _ => Err(GraphicsError::Unsupported(pixel_format.to_string())),
        }
    }
}