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())),
}
}
}