use crate::{
capture::traits::{DefaultPixelConverter, PixelFormatConverter},
encoder::simd::global_simd_converter,
error::{CaptureError, CaptureResult},
memory_pool::global_pool,
types::{CaptureRegion, DisplayInfo, PixelFormat, RawImage},
};
use core_foundation::base::{CFRelease, TCFType};
use core_graphics::{
display::{CGDisplay, CGDisplayCreateImage, CGRect},
image::CGImage,
};
use std::slice;
pub struct CoreGraphicsCapture {
pixel_converter: DefaultPixelConverter,
}
impl CoreGraphicsCapture {
pub fn new() -> CaptureResult<Self> {
Ok(Self {
pixel_converter: DefaultPixelConverter,
})
}
pub fn capture_display(&self, display_index: usize) -> CaptureResult<RawImage> {
let displays = super::MacOSCapture::enumerate_displays()?;
let display = displays
.get(display_index)
.ok_or_else(|| CaptureError::DisplayNotFound(display_index))?;
let rect = CGRect::new(
&core_graphics::geometry::CGPoint::new(display.x as f64, display.y as f64),
&core_graphics::geometry::CGSize::new(display.width as f64, display.height as f64),
);
self.capture_rect(rect, display.scale_factor)
}
pub fn capture_region(&self, region: CaptureRegion) -> CaptureResult<RawImage> {
let rect = CGRect::new(
&core_graphics::geometry::CGPoint::new(region.x as f64, region.y as f64),
&core_graphics::geometry::CGSize::new(region.width as f64, region.height as f64),
);
self.capture_rect(rect, 1.0)
}
fn capture_rect(&self, rect: CGRect, scale_factor: f32) -> CaptureResult<RawImage> {
unsafe {
let image = CGDisplayCreateImage(CGDisplay::main().id, Some(rect));
if image.is_null() {
return Err(CaptureError::CaptureFailed(
"CGDisplayCreateImage failed".to_string(),
));
}
let width = CGImageGetWidth(image) as u32;
let height = CGImageGetHeight(image) as u32;
let bytes_per_row = CGImageGetBytesPerRow(image) as usize;
let bits_per_pixel = CGImageGetBitsPerPixel(image);
let bits_per_component = CGImageGetBitsPerComponent(image);
let data_provider = CGImageGetDataProvider(image);
if data_provider.is_null() {
CFRelease(image as _);
return Err(CaptureError::CaptureFailed(
"Failed to get data provider".to_string(),
));
}
let data_ref = CGDataProviderCopyData(data_provider);
if data_ref.is_null() {
CFRelease(image as _);
return Err(CaptureError::CaptureFailed(
"Failed to copy image data".to_string(),
));
}
let data_ptr = CFDataGetBytePtr(data_ref);
let data_len = CFDataGetLength(data_ref) as usize;
let expected_size = (height as usize) * bytes_per_row;
if data_len < expected_size {
CFRelease(data_ref as _);
CFRelease(image as _);
return Err(CaptureError::CaptureFailed(
format!("Invalid data size: got {}, expected {}", data_len, expected_size),
));
}
let pool = global_pool();
let buffer_size = (width * height * 4) as usize; let mut pooled_buffer = pool
.acquire(buffer_size)
.map_err(|_| CaptureError::MemoryAllocationFailed { size: buffer_size })?;
let src_slice = slice::from_raw_parts(data_ptr, data_len);
let pixel_format = if bits_per_pixel == 32 && bits_per_component == 8 {
let color_space = CGImageGetColorSpace(image);
let is_bgra = self.is_bgra_format(color_space);
if is_bgra {
self.convert_bgra_to_rgba(
src_slice,
pooled_buffer.data_mut(),
width,
height,
bytes_per_row,
);
} else {
self.copy_rgba_data(
src_slice,
pooled_buffer.data_mut(),
width,
height,
bytes_per_row,
);
}
PixelFormat::RGBA8
} else if bits_per_pixel == 24 && bits_per_component == 8 {
self.copy_rgb_data(
src_slice,
pooled_buffer.data_mut(),
width,
height,
bytes_per_row,
);
PixelFormat::RGB8
} else {
CFRelease(data_ref as _);
CFRelease(image as _);
return Err(CaptureError::CaptureFailed(
format!("Unsupported pixel format: {}bpp, {}bpc", bits_per_pixel, bits_per_component),
));
};
CFRelease(data_ref as _);
CFRelease(image as _);
let data = pooled_buffer.into_vec();
Ok(RawImage::new(data, width, height, pixel_format))
}
}
fn is_bgra_format(&self, color_space: CGColorSpaceRef) -> bool {
true
}
fn convert_bgra_to_rgba(
&self,
src: &[u8],
dst: &mut [u8],
width: u32,
height: u32,
src_stride: usize,
) {
let dst_stride = (width * 4) as usize;
for y in 0..height as usize {
let src_row = &src[y * src_stride..];
let dst_row = &mut dst[y * dst_stride..];
for x in 0..width as usize {
let src_offset = x * 4;
let dst_offset = x * 4;
dst_row[dst_offset] = src_row[src_offset + 2]; dst_row[dst_offset + 1] = src_row[src_offset + 1]; dst_row[dst_offset + 2] = src_row[src_offset]; dst_row[dst_offset + 3] = src_row[src_offset + 3]; }
}
}
fn copy_rgba_data(
&self,
src: &[u8],
dst: &mut [u8],
width: u32,
height: u32,
src_stride: usize,
) {
let dst_stride = (width * 4) as usize;
for y in 0..height as usize {
let src_row = &src[y * src_stride..y * src_stride + dst_stride];
let dst_row = &mut dst[y * dst_stride..y * dst_stride + dst_stride];
dst_row.copy_from_slice(src_row);
}
}
fn copy_rgb_data(
&self,
src: &[u8],
dst: &mut [u8],
width: u32,
height: u32,
src_stride: usize,
) {
let dst_stride = (width * 3) as usize;
for y in 0..height as usize {
let src_row = &src[y * src_stride..y * src_stride + dst_stride];
let dst_row = &mut dst[y * dst_stride..y * dst_stride + dst_stride];
dst_row.copy_from_slice(src_row);
}
}
pub fn capture_with_cursor(&self, region: CaptureRegion) -> CaptureResult<RawImage> {
let mut image = self.capture_region(region)?;
unsafe {
use core_graphics::event::{CGEvent, CGEventType};
if let Ok(event) = CGEvent::new(CGEventType::MouseMoved) {
let cursor_pos = event.location();
if cursor_pos.x >= region.x as f64
&& cursor_pos.y >= region.y as f64
&& cursor_pos.x < (region.x + region.width as i32) as f64
&& cursor_pos.y < (region.y + region.height as i32) as f64
{
let rel_x = (cursor_pos.x - region.x as f64) as u32;
let rel_y = (cursor_pos.y - region.y as f64) as u32;
log::debug!("Cursor at ({}, {}) in captured region", rel_x, rel_y);
}
}
}
Ok(image)
}
}
type CGColorSpaceRef = *mut std::ffi::c_void;
type CGImageRef = *mut std::ffi::c_void;
type CGDataProviderRef = *mut std::ffi::c_void;
type CFDataRef = *mut std::ffi::c_void;
extern "C" {
fn CGImageGetWidth(image: CGImageRef) -> usize;
fn CGImageGetHeight(image: CGImageRef) -> usize;
fn CGImageGetBytesPerRow(image: CGImageRef) -> usize;
fn CGImageGetBitsPerPixel(image: CGImageRef) -> usize;
fn CGImageGetBitsPerComponent(image: CGImageRef) -> usize;
fn CGImageGetColorSpace(image: CGImageRef) -> CGColorSpaceRef;
fn CGImageGetDataProvider(image: CGImageRef) -> CGDataProviderRef;
fn CGDataProviderCopyData(provider: CGDataProviderRef) -> CFDataRef;
fn CFDataGetBytePtr(data: CFDataRef) -> *const u8;
fn CFDataGetLength(data: CFDataRef) -> isize;
}