1use std::{
2 fs::{self},
3 io,
4 path::Path,
5 ptr, slice,
6};
7
8use rayon::iter::{IntoParallelIterator, ParallelIterator};
9use windows::{
10 Foundation::TimeSpan,
11 Graphics::DirectX::Direct3D11::IDirect3DSurface,
12 Win32::Graphics::{
13 Direct3D11::{
14 ID3D11Device, ID3D11DeviceContext, ID3D11Texture2D, D3D11_BOX, D3D11_CPU_ACCESS_READ,
15 D3D11_CPU_ACCESS_WRITE, D3D11_MAPPED_SUBRESOURCE, D3D11_MAP_READ_WRITE,
16 D3D11_TEXTURE2D_DESC, D3D11_USAGE_STAGING,
17 },
18 Dxgi::Common::{DXGI_FORMAT, DXGI_SAMPLE_DESC},
19 },
20};
21
22use crate::{
23 encoder::{self, ImageEncoder},
24 settings::ColorFormat,
25};
26
27#[derive(thiserror::Error, Debug)]
28pub enum Error {
29 #[error("Invalid box size")]
30 InvalidSize,
31 #[error("This color format is not supported for saving as image")]
32 UnsupportedFormat,
33 #[error("Failed to encode image buffer to image bytes with specified format: {0}")]
34 ImageEncoderError(#[from] encoder::ImageEncoderError),
35 #[error("IO error: {0}")]
36 IoError(#[from] io::Error),
37 #[error("Windows API error: {0}")]
38 WindowsError(#[from] windows::core::Error),
39}
40
41#[derive(Eq, PartialEq, Clone, Copy, Debug)]
42pub enum ImageFormat {
43 Jpeg,
44 Png,
45 Gif,
46 Tiff,
47 Bmp,
48 JpegXr,
49}
50
51pub struct Frame<'a> {
60 d3d_device: &'a ID3D11Device,
61 frame_surface: IDirect3DSurface,
62 frame_texture: ID3D11Texture2D,
63 time: TimeSpan,
64 context: &'a ID3D11DeviceContext,
65 buffer: &'a mut Vec<u8>,
66 width: u32,
67 height: u32,
68 color_format: ColorFormat,
69}
70
71impl<'a> Frame<'a> {
72 #[allow(clippy::too_many_arguments)]
90 #[must_use]
91 #[inline]
92 pub fn new(
93 d3d_device: &'a ID3D11Device,
94 frame_surface: IDirect3DSurface,
95 frame_texture: ID3D11Texture2D,
96 time: TimeSpan,
97 context: &'a ID3D11DeviceContext,
98 buffer: &'a mut Vec<u8>,
99 width: u32,
100 height: u32,
101 color_format: ColorFormat,
102 ) -> Self {
103 Self {
104 d3d_device,
105 frame_surface,
106 frame_texture,
107 time,
108 context,
109 buffer,
110 width,
111 height,
112 color_format,
113 }
114 }
115
116 #[must_use]
122 #[inline]
123 pub const fn width(&self) -> u32 {
124 self.width
125 }
126
127 #[must_use]
133 #[inline]
134 pub const fn height(&self) -> u32 {
135 self.height
136 }
137
138 #[must_use]
144 #[inline]
145 pub const fn timespan(&self) -> TimeSpan {
146 self.time
147 }
148
149 #[must_use]
155 #[inline]
156 pub const fn color_format(&self) -> ColorFormat {
157 self.color_format
158 }
159
160 #[allow(clippy::missing_safety_doc)]
170 #[must_use]
171 #[inline]
172 pub const unsafe fn as_raw_surface(&self) -> &IDirect3DSurface {
173 &self.frame_surface
174 }
175
176 #[allow(clippy::missing_safety_doc)]
182 #[must_use]
183 #[inline]
184 pub const unsafe fn as_raw_texture(&self) -> &ID3D11Texture2D {
185 &self.frame_texture
186 }
187
188 #[inline]
194 pub fn buffer(&mut self) -> Result<FrameBuffer, Error> {
195 let texture_desc = D3D11_TEXTURE2D_DESC {
197 Width: self.width,
198 Height: self.height,
199 MipLevels: 1,
200 ArraySize: 1,
201 Format: DXGI_FORMAT(self.color_format as i32),
202 SampleDesc: DXGI_SAMPLE_DESC {
203 Count: 1,
204 Quality: 0,
205 },
206 Usage: D3D11_USAGE_STAGING,
207 BindFlags: 0,
208 CPUAccessFlags: D3D11_CPU_ACCESS_READ.0 as u32 | D3D11_CPU_ACCESS_WRITE.0 as u32,
209 MiscFlags: 0,
210 };
211
212 let mut texture = None;
214 unsafe {
215 self.d3d_device
216 .CreateTexture2D(&texture_desc, None, Some(&mut texture))?;
217 };
218
219 let texture = texture.unwrap();
220
221 unsafe {
223 self.context.CopyResource(&texture, &self.frame_texture);
224 };
225
226 let mut mapped_resource = D3D11_MAPPED_SUBRESOURCE::default();
228 unsafe {
229 self.context.Map(
230 &texture,
231 0,
232 D3D11_MAP_READ_WRITE,
233 0,
234 Some(&mut mapped_resource),
235 )?;
236 };
237
238 let mapped_frame_data = unsafe {
240 slice::from_raw_parts_mut(
241 mapped_resource.pData.cast(),
242 (self.height * mapped_resource.RowPitch) as usize,
243 )
244 };
245
246 let frame_buffer = FrameBuffer::new(
248 mapped_frame_data,
249 self.buffer,
250 self.width,
251 self.height,
252 mapped_resource.RowPitch,
253 mapped_resource.DepthPitch,
254 self.color_format,
255 );
256
257 Ok(frame_buffer)
258 }
259
260 #[inline]
273 pub fn buffer_crop(
274 &mut self,
275 start_width: u32,
276 start_height: u32,
277 end_width: u32,
278 end_height: u32,
279 ) -> Result<FrameBuffer, Error> {
280 if start_width >= end_width || start_height >= end_height {
281 return Err(Error::InvalidSize);
282 }
283
284 let texture_width = end_width - start_width;
285 let texture_height = end_height - start_height;
286
287 let texture_desc = D3D11_TEXTURE2D_DESC {
289 Width: texture_width,
290 Height: texture_height,
291 MipLevels: 1,
292 ArraySize: 1,
293 Format: DXGI_FORMAT(self.color_format as i32),
294 SampleDesc: DXGI_SAMPLE_DESC {
295 Count: 1,
296 Quality: 0,
297 },
298 Usage: D3D11_USAGE_STAGING,
299 BindFlags: 0,
300 CPUAccessFlags: D3D11_CPU_ACCESS_READ.0 as u32 | D3D11_CPU_ACCESS_WRITE.0 as u32,
301 MiscFlags: 0,
302 };
303
304 let mut texture = None;
306 unsafe {
307 self.d3d_device
308 .CreateTexture2D(&texture_desc, None, Some(&mut texture))?;
309 };
310 let texture = texture.unwrap();
311
312 let resource_box = D3D11_BOX {
314 left: start_width,
315 top: start_height,
316 front: 0,
317 right: end_width,
318 bottom: end_height,
319 back: 1,
320 };
321
322 unsafe {
324 self.context.CopySubresourceRegion(
325 &texture,
326 0,
327 0,
328 0,
329 0,
330 &self.frame_texture,
331 0,
332 Some(&resource_box),
333 );
334 };
335
336 let mut mapped_resource = D3D11_MAPPED_SUBRESOURCE::default();
338 unsafe {
339 self.context.Map(
340 &texture,
341 0,
342 D3D11_MAP_READ_WRITE,
343 0,
344 Some(&mut mapped_resource),
345 )?;
346 };
347
348 let mapped_frame_data = unsafe {
350 slice::from_raw_parts_mut(
351 mapped_resource.pData.cast(),
352 (texture_height * mapped_resource.RowPitch) as usize,
353 )
354 };
355
356 let frame_buffer = FrameBuffer::new(
358 mapped_frame_data,
359 self.buffer,
360 texture_width,
361 texture_height,
362 mapped_resource.RowPitch,
363 mapped_resource.DepthPitch,
364 self.color_format,
365 );
366
367 Ok(frame_buffer)
368 }
369
370 #[inline]
381 pub fn save_as_image<T: AsRef<Path>>(
382 &mut self,
383 path: T,
384 format: ImageFormat,
385 ) -> Result<(), Error> {
386 let mut frame_buffer = self.buffer()?;
387
388 frame_buffer.save_as_image(path, format)?;
389
390 Ok(())
391 }
392}
393
394pub struct FrameBuffer<'a> {
403 raw_buffer: &'a mut [u8],
404 buffer: &'a mut Vec<u8>,
405 width: u32,
406 height: u32,
407 row_pitch: u32,
408 depth_pitch: u32,
409 color_format: ColorFormat,
410}
411
412impl<'a> FrameBuffer<'a> {
413 #[must_use]
429 #[inline]
430 pub fn new(
431 raw_buffer: &'a mut [u8],
432 buffer: &'a mut Vec<u8>,
433 width: u32,
434 height: u32,
435 row_pitch: u32,
436 depth_pitch: u32,
437 color_format: ColorFormat,
438 ) -> Self {
439 Self {
440 raw_buffer,
441 buffer,
442 width,
443 height,
444 row_pitch,
445 depth_pitch,
446 color_format,
447 }
448 }
449
450 #[must_use]
452 #[inline]
453 pub const fn width(&self) -> u32 {
454 self.width
455 }
456
457 #[must_use]
459 #[inline]
460 pub const fn height(&self) -> u32 {
461 self.height
462 }
463
464 #[must_use]
466 #[inline]
467 pub const fn row_pitch(&self) -> u32 {
468 self.row_pitch
469 }
470
471 #[must_use]
473 #[inline]
474 pub const fn depth_pitch(&self) -> u32 {
475 self.depth_pitch
476 }
477
478 #[must_use]
480 #[inline]
481 pub const fn has_padding(&self) -> bool {
482 self.width * 4 != self.row_pitch
483 }
484
485 #[must_use]
487 #[inline]
488 pub fn as_raw_buffer(&mut self) -> &mut [u8] {
489 self.raw_buffer
490 }
491
492 #[inline]
498 pub fn as_nopadding_buffer(&mut self) -> Result<&mut [u8], Error> {
499 if !self.has_padding() {
500 return Ok(self.raw_buffer);
501 }
502
503 let multiplyer = match self.color_format {
504 ColorFormat::Rgba16F => 8,
505 ColorFormat::Rgba8 => 4,
506 ColorFormat::Bgra8 => 4,
507 };
508
509 let frame_size = (self.width * self.height * multiplyer) as usize;
510 if self.buffer.capacity() < frame_size {
511 self.buffer.resize(frame_size, 0);
512 }
513
514 let width_size = (self.width * multiplyer) as usize;
515 let buffer_address = self.buffer.as_mut_ptr() as isize;
516 (0..self.height).into_par_iter().for_each(|y| {
517 let index = (y * self.row_pitch) as usize;
518 let ptr = buffer_address as *mut u8;
519
520 unsafe {
521 ptr::copy_nonoverlapping(
522 self.raw_buffer.as_ptr().add(index),
523 ptr.add(y as usize * width_size),
524 width_size,
525 );
526 }
527 });
528
529 Ok(&mut self.buffer[0..frame_size])
530 }
531
532 #[inline]
543 pub fn save_as_image<T: AsRef<Path>>(
544 &mut self,
545 path: T,
546 format: ImageFormat,
547 ) -> Result<(), Error> {
548 let width = self.width;
549 let height = self.height;
550
551 let bytes = ImageEncoder::new(format, self.color_format).encode(
552 self.as_nopadding_buffer()?,
553 width,
554 height,
555 )?;
556
557 fs::write(path, bytes)?;
558
559 Ok(())
560 }
561}