use core::ffi::c_void;
use core::ptr::NonNull;
use denise::{BufferAge, Frame, PixelFormat, Rect, Size, Surface, SurfaceError};
use windows::Win32::Foundation::HWND;
use windows::Win32::Graphics::Gdi::{
BI_RGB, BITMAPINFO, BITMAPINFOHEADER, CreateCompatibleDC, CreateDIBSection, DIB_RGB_COLORS,
DeleteDC, DeleteObject, HBITMAP, HDC, HGDIOBJ, SRCCOPY, SelectObject,
};
use crate::Error;
pub struct DibSurface {
dc: HDC,
bitmap: HBITMAP,
previous: HGDIOBJ,
pixels: NonNull<u32>,
size: Size,
scale_factor: f32,
}
impl DibSurface {
pub fn new(size: Size, scale_factor: f32) -> Result<Self, Error> {
if size.is_empty() {
return Err(Error::EmptySurface);
}
let mut info = BITMAPINFO::default();
info.bmiHeader.biSize = size_of::<BITMAPINFOHEADER>() as u32;
info.bmiHeader.biWidth = size.width as i32;
info.bmiHeader.biHeight = -(size.height as i32);
info.bmiHeader.biPlanes = 1;
info.bmiHeader.biBitCount = 32;
info.bmiHeader.biCompression = BI_RGB.0;
let mut bits: *mut c_void = core::ptr::null_mut();
let bitmap = unsafe { CreateDIBSection(None, &info, DIB_RGB_COLORS, &mut bits, None, 0) }
.map_err(|_| Error::DibSection)?;
let Some(pixels) = NonNull::new(bits.cast::<u32>()) else {
unsafe {
let _ = DeleteObject(bitmap.into());
}
return Err(Error::DibSection);
};
let dc = unsafe { CreateCompatibleDC(None) };
if dc.is_invalid() {
unsafe {
let _ = DeleteObject(bitmap.into());
}
return Err(Error::MemoryDc);
}
let previous = unsafe { SelectObject(dc, bitmap.into()) };
Ok(Self {
dc,
bitmap,
previous,
pixels,
size,
scale_factor,
})
}
pub fn resize(&mut self, size: Size, scale_factor: f32) -> Result<bool, Error> {
if size == self.size && scale_factor == self.scale_factor {
return Ok(false);
}
*self = Self::new(size, scale_factor)?;
Ok(true)
}
#[inline]
pub const fn stride(&self) -> u32 {
self.size.width
}
pub unsafe fn blit(&self, target: HDC, rects: &[Rect]) {
for rect in rects {
let Some(clipped) = rect.intersect(&Rect::from_size(self.size)) else {
continue;
};
let ok = unsafe {
windows::Win32::Graphics::Gdi::BitBlt(
target,
clipped.x,
clipped.y,
clipped.width,
clipped.height,
Some(self.dc),
clipped.x,
clipped.y,
SRCCOPY,
)
};
let _ = ok;
}
}
#[inline]
pub const fn dc(&self) -> HDC {
self.dc
}
}
impl Drop for DibSurface {
fn drop(&mut self) {
unsafe {
SelectObject(self.dc, self.previous);
let _ = DeleteDC(self.dc);
let _ = DeleteObject(self.bitmap.into());
}
}
}
impl Surface for DibSurface {
fn size(&self) -> Size {
self.size
}
fn scale_factor(&self) -> f32 {
self.scale_factor
}
fn format(&self) -> PixelFormat {
PixelFormat::Xrgb8888
}
fn acquire(&mut self) -> Result<Frame<'_>, SurfaceError> {
let len = self.stride() as usize * self.size.height as usize;
let pixels = unsafe { core::slice::from_raw_parts_mut(self.pixels.as_ptr(), len) };
Frame::new(
pixels,
self.size,
self.stride(),
PixelFormat::Xrgb8888,
BufferAge::Frames(1),
)
}
fn present(&mut self, _damage: &[Rect]) -> Result<(), SurfaceError> {
Ok(())
}
}
pub(crate) fn screen_to_client(hwnd: HWND, x: i32, y: i32) -> (i32, i32) {
let mut point = windows::Win32::Foundation::POINT { x, y };
unsafe {
let _ = windows::Win32::Graphics::Gdi::ScreenToClient(hwnd, &mut point);
}
(point.x, point.y)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn an_empty_surface_is_refused_rather_than_allocated() {
assert!(DibSurface::new(Size::new(0, 40), 1.0).is_err());
assert!(DibSurface::new(Size::new(40, 0), 1.0).is_err());
}
#[test]
fn a_thirty_two_bit_dib_needs_no_padding() {
let surface = DibSurface::new(Size::new(101, 7), 1.0).expect("surface");
assert_eq!(surface.stride(), 101);
assert_eq!(surface.format(), PixelFormat::Xrgb8888);
}
#[test]
fn the_surface_is_addressable_to_its_last_pixel() {
let mut surface = DibSurface::new(Size::new(16, 9), 1.0).expect("surface");
let mut frame = surface.acquire().expect("frame");
let last = frame.row_mut(8).expect("last row");
assert_eq!(last.len(), 16);
last[15] = 0x00FF_00FF;
assert_eq!(frame.row_mut(8).expect("last row")[15], 0x00FF_00FF);
}
}