psp/
screenshot.rs

1use crate::sys::{self, DisplayPixelFormat};
2use crate::{SCREEN_HEIGHT, SCREEN_WIDTH};
3use core::{ffi::c_void, ptr};
4
5// RGBA
6const BYTES_PER_PIXEL: usize = 4;
7
8const NUM_PIXELS: usize = (SCREEN_WIDTH * SCREEN_HEIGHT) as usize;
9
10#[repr(C, packed)]
11#[derive(Clone, Copy)]
12struct BmpHeader {
13    pub file_type: [u8; 2],
14    pub file_size: u32,
15    pub reserved_1: u16,
16    pub reserved_2: u16,
17    pub image_data_start: u32,
18    pub dib_header_size: u32,
19    pub image_width: u32,
20    pub image_height: u32,
21    pub color_planes: u16,
22    pub bpp: u16,
23    pub compression: u32,
24    pub image_data_len: u32,
25    pub print_resolution_x: u32,
26    pub print_resolution_y: u32,
27    pub palette_color_count: u32,
28    pub important_colors: u32,
29}
30
31impl BmpHeader {
32    const BYTES: usize = core::mem::size_of::<Self>();
33
34    fn to_bytes(self) -> [u8; Self::BYTES] {
35        unsafe { core::mem::transmute(self) }
36    }
37}
38
39fn rgba_to_bgra(rgba: u32) -> u32 {
40    // 0xAABBGGRR -> 0xAARRGGBB
41
42    core::intrinsics::bswap(rgba << 8 | rgba >> 24)
43}
44
45fn rgb565_to_bgra(rgb565: u16) -> u32 {
46    let rgb565 = rgb565 as u32;
47
48    // bbbb bggg gggr rrrr -> 0xffRRGGBB
49    (((rgb565 & 0x1f) << 16) * 0x100 / 0x20)
50        | (((rgb565 & 0x7e0) << 3) * 0x100 / 0x40)
51        | (((rgb565 & 0xf800) >> 11) * 0x100 / 0x20)
52        | 0xff00_0000
53}
54
55fn rgba5551_to_bgra(rgba5551: u16) -> u32 {
56    let rgba5551 = rgba5551 as u32;
57
58    // abbb bbgg gggr rrrr -> 0xAARRGGBB
59    (((rgba5551 & 0x1f) << 16) * 0x100 / 0x20)
60        | (((rgba5551 & 0x3e0) << 3) * 0x100 / 0x20)
61        | (((rgba5551 & 0x7c00) >> 10) * 0x100 / 0x20)
62        | (((rgba5551 & 0x8000) >> 15) * 0xff00_0000)
63}
64
65fn rgba4444_to_bgra(rgba4444: u16) -> u32 {
66    let rgba4444 = rgba4444 as u32;
67
68    // aaaa bbbb gggg rrrr -> 0xAARRGGBB
69    (((rgba4444 & 0x000f) << 16) * 0x100 / 0x10)
70        | (((rgba4444 & 0x00f0) << 4) * 0x100 / 0x10)
71        | (((rgba4444 & 0x0f00) >> 8) * 0x100 / 0x10)
72        | (((rgba4444 & 0xf000) << 12) * 0x100 / 0x10)
73}
74
75/// Take a screenshot, returning a raw ARGB (big-endian) array.
76pub fn screenshot_argb_be() -> alloc::vec::Vec<u32> {
77    let mut screenshot_buffer = alloc::vec![0; NUM_PIXELS];
78    let mut buffer_width: usize = 0;
79    let mut pixel_format = DisplayPixelFormat::Psm5650;
80    let mut top_addr: *mut c_void = ptr::null_mut();
81
82    unsafe {
83        sys::sceDisplayGetFrameBuf(
84            &mut top_addr,
85            &mut buffer_width,
86            &mut pixel_format,
87            sys::DisplaySetBufSync::Immediate,
88        );
89    }
90
91    // http://uofw.github.io/upspd/docs/hardware/PSPTEK.htm#memmap
92
93    // If this is a kernel address...
94    if top_addr as u32 & 0x80000000 != 0 {
95        // Set the kernel cache-through bit.
96        top_addr = (top_addr as u32 | 0xA0000000) as _;
97    } else {
98        // Else set the regular cache-through bit.
99        top_addr = (top_addr as u32 | 0x40000000) as _;
100    }
101
102    for x in 0..SCREEN_WIDTH {
103        for y in 0..SCREEN_HEIGHT {
104            // BGRA is reversed ARGB. We do this for little-endian based copying.
105            let bgra = match pixel_format {
106                sys::DisplayPixelFormat::Psm8888 => {
107                    let rgba = unsafe {
108                        *(top_addr as *mut u32).add(x as usize + y as usize * buffer_width)
109                    };
110
111                    rgba_to_bgra(rgba)
112                }
113
114                sys::DisplayPixelFormat::Psm5650 => {
115                    let rgb565 = unsafe {
116                        *(top_addr as *mut u16).add(x as usize + y as usize * buffer_width)
117                    };
118
119                    rgb565_to_bgra(rgb565)
120                }
121
122                sys::DisplayPixelFormat::Psm5551 => {
123                    let rgba5551 = unsafe {
124                        *(top_addr as *mut u16).add(x as usize + y as usize * buffer_width)
125                    };
126
127                    rgba5551_to_bgra(rgba5551)
128                }
129
130                sys::DisplayPixelFormat::Psm4444 => {
131                    let rgba4444 = unsafe {
132                        *(top_addr as *mut u16).add(x as usize + y as usize * buffer_width)
133                    };
134
135                    rgba4444_to_bgra(rgba4444)
136                }
137            };
138
139            // Display buffer is flipped upside down.
140            let y_inv = SCREEN_HEIGHT - y - 1;
141            screenshot_buffer[x as usize + y_inv as usize * SCREEN_WIDTH as usize] = bgra;
142        }
143    }
144
145    screenshot_buffer
146}
147
148/// Take a screenshot, returning a valid bitmap file.
149pub fn screenshot_bmp() -> alloc::vec::Vec<u8> {
150    let mut screenshot_buffer = alloc::vec![0; BmpHeader::BYTES + NUM_PIXELS * BYTES_PER_PIXEL];
151
152    let payload = screenshot_argb_be();
153
154    let bmp_header = BmpHeader {
155        file_type: *b"BM",
156        file_size: BmpHeader::BYTES as u32 + payload.len() as u32 * 4,
157        reserved_1: 0,
158        reserved_2: 0,
159        image_data_start: BmpHeader::BYTES as u32,
160        dib_header_size: 40,
161        image_width: SCREEN_WIDTH,
162        image_height: SCREEN_HEIGHT,
163        color_planes: 1,
164        bpp: 32,
165        compression: 0,
166        image_data_len: payload.len() as u32 * 4,
167        print_resolution_x: 2835, // 72 DPI
168        print_resolution_y: 2835, // 72 DPI
169        palette_color_count: 0,
170        important_colors: 0,
171    };
172
173    screenshot_buffer[0..BmpHeader::BYTES].copy_from_slice(&bmp_header.to_bytes());
174
175    unsafe {
176        core::ptr::copy_nonoverlapping(
177            &payload[0] as *const _ as _,
178            &mut screenshot_buffer[BmpHeader::BYTES] as *mut u8,
179            NUM_PIXELS * BYTES_PER_PIXEL,
180        );
181    }
182
183    screenshot_buffer
184}