Skip to content

Commit b2b21da

Browse files
committed
add support for VQ-compressed PVR
1 parent 1763c0e commit b2b21da

File tree

5 files changed

+130
-73
lines changed

5 files changed

+130
-73
lines changed

kidfile/src/image.rs

Lines changed: 70 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@ pub enum PixelFormat {
99
Bgra,
1010
Bgrx,
1111
Bgr,
12-
Rgba16,
13-
Rgb16,
12+
Rgba5551,
13+
Rgb565,
1414
Rgba4444,
15-
Bgra16,
16-
Bgr16,
15+
Bgra5551,
16+
Bgr565,
1717
Bgra4444,
1818
RgbaClut8,
1919
RgbxClut8,
@@ -29,41 +29,12 @@ pub enum PixelFormat {
2929
BgrClut4,
3030
PsxClut4,
3131
PsxClut8,
32-
Psx
33-
}
34-
35-
impl PixelFormat {
36-
pub fn bpp(self) -> usize {
37-
match self {
38-
PixelFormat::Rgba => 32,
39-
PixelFormat::Rgbx => 32,
40-
PixelFormat::Rgb => 24,
41-
PixelFormat::Bgra => 32,
42-
PixelFormat::Bgrx => 32,
43-
PixelFormat::Bgr => 24,
44-
PixelFormat::Rgba16 => 16,
45-
PixelFormat::Rgb16 => 16,
46-
PixelFormat::Rgba4444 => 16,
47-
PixelFormat::Bgra16 => 16,
48-
PixelFormat::Bgr16 => 16,
49-
PixelFormat::Bgra4444 => 16,
50-
PixelFormat::RgbaClut8 => 8,
51-
PixelFormat::RgbxClut8 => 8,
52-
PixelFormat::RgbClut8 => 8,
53-
PixelFormat::BgraClut8 => 8,
54-
PixelFormat::BgrxClut8 => 8,
55-
PixelFormat::BgrClut8 => 8,
56-
PixelFormat::RgbaClut4 => 4,
57-
PixelFormat::RgbxClut4 => 4,
58-
PixelFormat::RgbClut4 => 4,
59-
PixelFormat::BgraClut4 => 4,
60-
PixelFormat::BgrxClut4 => 4,
61-
PixelFormat::BgrClut4 => 4,
62-
PixelFormat::PsxClut4 => 4,
63-
PixelFormat::PsxClut8 => 8,
64-
PixelFormat::Psx => 16
65-
}
66-
}
32+
Psx,
33+
Bgra5551Vq8,
34+
Bgr565Vq8,
35+
Bgra4444Vq8,
36+
Gray8,
37+
Gray4
6738
}
6839

6940
impl<'a> From<&png::Info<'a>> for PixelFormat {
@@ -87,11 +58,11 @@ impl Display for PixelFormat {
8758
Self::Bgra => write!(f, "BGRA"),
8859
Self::Bgrx => write!(f, "BGRX"),
8960
Self::Bgr => write!(f, "BGR"),
90-
Self::Rgba16 => write!(f, "RGBA5551"),
91-
Self::Rgb16 => write!(f, "RGB565"),
61+
Self::Rgba5551 => write!(f, "RGBA5551"),
62+
Self::Rgb565 => write!(f, "RGB565"),
9263
Self::Rgba4444 => write!(f, "RGBA4444"),
93-
Self::Bgra16 => write!(f, "BGRA5551"),
94-
Self::Bgr16 => write!(f, "BGR565"),
64+
Self::Bgra5551 => write!(f, "BGRA5551"),
65+
Self::Bgr565 => write!(f, "BGR565"),
9566
Self::Bgra4444 => write!(f, "BGRA4444"),
9667
Self::RgbaClut8 => write!(f, "RGBA clut8"),
9768
Self::RgbxClut8 => write!(f, "RGBX clut8"),
@@ -105,9 +76,14 @@ impl Display for PixelFormat {
10576
Self::BgraClut4 => write!(f, "BGRA clut4"),
10677
Self::BgrxClut4 => write!(f, "BGRX clut4"),
10778
Self::BgrClut4 => write!(f, "BGR clut4"),
108-
PixelFormat::PsxClut4 => write!(f, "PSX clut4"),
109-
PixelFormat::PsxClut8 => write!(f, "PSX clut8"),
110-
PixelFormat::Psx => write!(f, "PSX 16-bit")
79+
Self::PsxClut4 => write!(f, "PSX clut4"),
80+
Self::PsxClut8 => write!(f, "PSX clut8"),
81+
Self::Psx => write!(f, "PSX 16-bit"),
82+
Self::Bgra5551Vq8 => write!(f, "BGRA5551 vq8"),
83+
Self::Bgr565Vq8 => write!(f, "BGR565 vq8"),
84+
Self::Bgra4444Vq8 => write!(f, "BGRA4444 vq8"),
85+
Self::Gray8 => write!(f, "gray8"),
86+
Self::Gray4 => write!(f, "gray4")
11187
}
11288
}
11389
}
@@ -128,6 +104,16 @@ pub struct Frame {
128104
pub pixels: Box<[Pixel]>
129105
}
130106

107+
fn bits_2_to_8(x: u8) -> u8 {
108+
let x = x & 0x3;
109+
x | x << 2 | x << 4 | x << 6
110+
}
111+
112+
fn bits_3_to_8(x: u8) -> u8 {
113+
let x = x & 0x7;
114+
x << 5 | x << 2 | x >> 1
115+
}
116+
131117
fn bits_4_to_8(x: u8) -> u8 {
132118
x << 4 | (x & 0xF)
133119
}
@@ -140,7 +126,7 @@ fn bits_6_to_8(x: u8) -> u8 {
140126
x << 2 | (x & 1) << 1 | (x & 1)
141127
}
142128

143-
fn bit_twiddle(x: usize) -> usize {
129+
pub fn bit_twiddle(x: usize) -> usize {
144130
(x & 1) | (x & 2) << 1 | (x & 4) << 2 | (x & 8) << 3 | (x & 16) << 4 | (x & 32) << 5 | (x & 64) << 6 | (x & 128) << 7 | (x & 256) << 8 | (x & 512) << 9
145131
}
146132

@@ -162,12 +148,12 @@ impl Frame {
162148
}
163149
}
164150

165-
pub fn from_rgba16(width: u32, height: u32, buf: &[u8]) -> Self {
151+
pub fn from_rgba5551(width: u32, height: u32, buf: &[u8]) -> Self {
166152
let needed_size = width * height * 2;
167153
assert!(buf.len() as u32 >= needed_size);
168154
let buf = &buf[0..needed_size as usize];
169155
Self {
170-
width, height, og_fmt: PixelFormat::Rgba16,
156+
width, height, og_fmt: PixelFormat::Rgba5551,
171157
pixels: buf.chunks_exact(2).map(|x| Pixel {
172158
r: bits_5_to_8(x[0]),
173159
g: bits_5_to_8(x[0] >> 5 | x[1] << 3),
@@ -177,12 +163,12 @@ impl Frame {
177163
}
178164
}
179165

180-
pub fn from_bgra16(width: u32, height: u32, buf: &[u8]) -> Self {
166+
pub fn from_bgra5551(width: u32, height: u32, buf: &[u8]) -> Self {
181167
let needed_size = width * height * 2;
182168
assert!(buf.len() as u32 >= needed_size);
183169
let buf = &buf[0..needed_size as usize];
184170
Self {
185-
width, height, og_fmt: PixelFormat::Bgra16,
171+
width, height, og_fmt: PixelFormat::Bgra5551,
186172
pixels: buf.chunks_exact(2).map(|x| Pixel {
187173
r: bits_5_to_8(x[1] >> 3),
188174
g: bits_5_to_8(x[0] >> 5 | x[1] << 3),
@@ -227,7 +213,7 @@ impl Frame {
227213
assert!(buf.len() as u32 >= needed_size);
228214
let buf = &buf[0..needed_size as usize];
229215
Self {
230-
width, height, og_fmt: PixelFormat::Rgb16,
216+
width, height, og_fmt: PixelFormat::Rgb565,
231217
pixels: buf.chunks_exact(2).map(|x| Pixel {
232218
r: bits_5_to_8(x[0]),
233219
g: bits_6_to_8(x[0] >> 5 | x[1] << 3),
@@ -237,12 +223,12 @@ impl Frame {
237223
}
238224
}
239225

240-
pub fn from_bgr16(width: u32, height: u32, buf: &[u8]) -> Self {
226+
pub fn from_bgr565(width: u32, height: u32, buf: &[u8]) -> Self {
241227
let needed_size = width * height * 2;
242228
assert!(buf.len() as u32 >= needed_size);
243229
let buf = &buf[0..needed_size as usize];
244230
Self {
245-
width, height, og_fmt: PixelFormat::Bgr16,
231+
width, height, og_fmt: PixelFormat::Bgr565,
246232
pixels: buf.chunks_exact(2).map(|x| Pixel {
247233
r: bits_5_to_8(x[1] >> 3),
248234
g: bits_6_to_8(x[0] >> 5 | x[1] << 3),
@@ -440,6 +426,35 @@ impl Frame {
440426
}
441427
}
442428

429+
pub fn from_gray8(width: u32, height: u32, buf: &[u8]) -> Self {
430+
let needed_size = width * height;
431+
assert!(buf.len() as u32 >= needed_size);
432+
let buf = &buf[0..needed_size as usize];
433+
Self {
434+
width, height, og_fmt: PixelFormat::Gray8,
435+
pixels: buf.iter().map(|&x| Pixel {
436+
r: bits_3_to_8(x >> 5),
437+
g: bits_3_to_8(x >> 2),
438+
b: bits_2_to_8(x),
439+
a: 255
440+
}).collect()
441+
}
442+
}
443+
444+
pub fn from_gray4(width: u32, height: u32, buf: &[u8]) -> Self {
445+
let needed_size = width * height / 2;
446+
assert!(buf.len() as u32 >= needed_size);
447+
let buf = &buf[0..needed_size as usize];
448+
Self {
449+
width, height, og_fmt: PixelFormat::Bgra,
450+
pixels: buf.iter().map(|&x| {
451+
let a = bits_4_to_8(x);
452+
let b = bits_4_to_8(x >> 4);
453+
[Pixel {r: a, g: a, b: a, a: 255}, Pixel {r: b, g: b, b: b, a: 255}]
454+
}).flatten().collect()
455+
}
456+
}
457+
443458
pub fn with_og_fmt(mut self, og_fmt: PixelFormat) -> Self {
444459
self.og_fmt = og_fmt;
445460
self
@@ -556,4 +571,4 @@ impl Frame {
556571

557572
pub struct Image {
558573
pub frames: Box<[Frame]>
559-
}
574+
}

kidfile/src/image_formats/gim.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ pub const ENTRY_GIM: Decoder<Image> = Decoder {
6969
};
7070
let frame = match format {
7171
0 => Frame::from_rgb16(aligned_width, height, &pixel_data),
72-
1 => Frame::from_rgba16(aligned_width, height, &pixel_data),
72+
1 => Frame::from_rgba5551(aligned_width, height, &pixel_data),
7373
3 => Frame::from_rgba(aligned_width, height, &pixel_data),
7474
4 => Frame::from_rgba_clut4(aligned_width, height, cur_palette, &pixel_data),
7575
5 => Frame::from_rgba_clut8(aligned_width, height, cur_palette, &pixel_data),
@@ -85,4 +85,4 @@ pub const ENTRY_GIM: Decoder<Image> = Decoder {
8585
Ok(Image {frames: frames.into_boxed_slice()})
8686
}
8787
}
88-
};
88+
};

kidfile/src/image_formats/ogdt.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ pub const ENTRY_OGDT: Decoder<Image> = Decoder {
2323
),
2424
Some(1) => if buf.len() < 32 + frame_count * tile_width * tile_height * 3 {
2525
(
26-
PixelFormat::Rgba16,
26+
PixelFormat::Rgba5551,
2727
tile_width * tile_height * 2,
2828
Cow::Borrowed(&[] as &[u8])
2929
)
@@ -56,7 +56,7 @@ pub const ENTRY_OGDT: Decoder<Image> = Decoder {
5656
let tile = match fmt {
5757
PixelFormat::Rgba => Frame::from_rgba(tile_width as u32, tile_height as u32, frame_bytes).with_double_alpha(),
5858
PixelFormat::Rgb => Frame::from_rgb(tile_width as u32, tile_height as u32, frame_bytes),
59-
PixelFormat::Rgba16 => Frame::from_rgba16(tile_width as u32, tile_height as u32, frame_bytes),
59+
PixelFormat::Rgba5551 => Frame::from_rgba5551(tile_width as u32, tile_height as u32, frame_bytes),
6060
PixelFormat::RgbaClut8 => Frame::from_rgba_clut8(tile_width as u32, tile_height as u32, &clut, frame_bytes).with_double_alpha(),
6161
PixelFormat::RgbaClut4 => Frame::from_rgba_clut4(tile_width as u32, tile_height as u32, &clut, frame_bytes).with_double_alpha(),
6262
_ => unreachable!()
@@ -90,4 +90,4 @@ fn fix_clut(clut: &[u8]) -> Vec<u8> {
9090
}
9191

9292
bytemuck::cast_vec(out)
93-
}
93+
}

kidfile/src/image_formats/pvr.rs

Lines changed: 53 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
use crate::{byte_slice::ByteSlice, image::{Frame, Image}, Certainty, Decoder};
1+
use std::{fs::File, io::{Read, Seek, SeekFrom}};
2+
3+
use crate::{Certainty, Decoder, byte_slice::ByteSlice, image::{Frame, Image, PixelFormat, bit_twiddle}};
24

35
// https://www.fabiensanglard.net/Mykaruga/tools/segaPVRFormat.txt
46
// https://dreamcast.wiki/Twiddling
@@ -31,36 +33,76 @@ pub const ENTRY_PVR: Decoder<Image> = Decoder {
3133
let width = buf.read_u16(12)? as usize;
3234
let height = buf.read_u16(14)? as usize;
3335
let mut frame = match pixel_fmt {
34-
0 => Frame::from_bgra16(width as u32, height as u32, buf.get(16..16 + width * height * 2).ok_or("not enough pixel data for BGRA5551")?),
35-
1 => Frame::from_bgr16(width as u32, height as u32, buf.get(16..16 + width * height * 2).ok_or("not enough pixel data for BGR565")?),
36-
2 => Frame::from_bgra4444(width as u32, height as u32, buf.get(16..16 + width * height * 2).ok_or("not enough pixel data for BGRA4444")?),
36+
0 | 1 | 2 => {
37+
if twiddle_type == 3 { // vq compression
38+
let codebook = buf.get(16..16 + 2048).ok_or("not enough 16-bit VQ codebook data")?;
39+
let indices = buf.get(16 + 2048..16 + 2048 + width * height / 4).ok_or("not enough VQ index data")?;
40+
let mut pixels = vec![0u8; width * height * 2];
41+
for block_y in 0..height / 2 {
42+
let twiddled_block_y = bit_twiddle(block_y);
43+
for block_x in 0..width / 2 {
44+
let twiddled_block_idx = twiddled_block_y | bit_twiddle(block_x) << 1;
45+
let codebook_pos = indices[twiddled_block_idx] as usize * 8;
46+
let x = block_x * 2;
47+
let y = block_y * 2;
48+
pixels[(y * width + x) * 2] = codebook[codebook_pos];
49+
pixels[(y * width + x) * 2 + 1] = codebook[codebook_pos + 1];
50+
pixels[(y * width + x + 1) * 2] = codebook[codebook_pos + 4];
51+
pixels[(y * width + x + 1) * 2 + 1] = codebook[codebook_pos + 5];
52+
pixels[((y + 1) * width + x) * 2] = codebook[codebook_pos + 2];
53+
pixels[((y + 1) * width + x) * 2 + 1] = codebook[codebook_pos + 3];
54+
pixels[((y + 1) * width + x + 1) * 2] = codebook[codebook_pos + 6];
55+
pixels[((y + 1) * width + x + 1) * 2 + 1] = codebook[codebook_pos + 7];
56+
}
57+
}
58+
if pixel_fmt == 0 {
59+
Frame::from_bgra5551(width as u32, height as u32, &pixels).with_og_fmt(PixelFormat::Bgra5551Vq8)
60+
} else if pixel_fmt == 1 {
61+
Frame::from_bgr565(width as u32, height as u32, &pixels).with_og_fmt(PixelFormat::Bgr565Vq8)
62+
} else {
63+
Frame::from_bgra4444(width as u32, height as u32, &pixels).with_og_fmt(PixelFormat::Bgra4444Vq8)
64+
}
65+
} else {
66+
if pixel_fmt == 0 {
67+
Frame::from_bgra5551(width as u32, height as u32, buf.get(16..16 + width * height * 2).ok_or("not enough pixel data for BGRA5551")?)
68+
} else if pixel_fmt == 1 {
69+
Frame::from_bgr565(width as u32, height as u32, buf.get(16..16 + width * height * 2).ok_or("not enough pixel data for BGR565")?)
70+
} else {
71+
Frame::from_bgra4444(width as u32, height as u32, buf.get(16..16 + width * height * 2).ok_or("not enough pixel data for BGRA4444")?)
72+
}
73+
}
74+
}
3775
5 => {
38-
if palette_bytes.is_empty() {
76+
if twiddle_type == 7 {
77+
return Err("file needs external palette, unimplemented".into());
78+
} else if palette_bytes.is_empty() {
3979
Frame::from_bgra_clut4(
4080
width as u32, height as u32,
4181
buf.get(16..16 + 1024).ok_or("not enough palette data for BGRA clut4")?,
42-
buf.get(16 + 1024..16 + 1024 + width * height / 2).ok_or("not enough pixel data for BGRA clut4")?
82+
buf.get(16 + 1024..16 + 1024 + width * height / 2).ok_or("not enough index data for BGRA clut4")?
4383
)
4484
} else {
4585
Frame::from_bgra_clut4(
4686
width as u32, height as u32,
4787
&palette_bytes,
48-
buf.get(16..16 + width * height / 2).ok_or("not enough pixel data for BGRA clut4")?
88+
buf.get(16..16 + width * height / 2).ok_or("not enough data for BGRA clut4")?
4989
)
5090
}
5191
}
5292
6 => {
53-
if palette_bytes.is_empty() {
93+
if twiddle_type == 7 {
94+
return Err("file needs external palette, unimplemented".into());
95+
} else if palette_bytes.is_empty() {
5496
Frame::from_bgra_clut8(
5597
width as u32, height as u32,
5698
buf.get(16..16 + 1024).ok_or("not enough palette data for BGRA clut8")?,
57-
buf.get(16 + 1024..16 + 1024 + width * height).ok_or("not enough pixel data for BGRA clut8")?
99+
buf.get(16 + 1024..16 + 1024 + width * height).ok_or("not enough index data for BGRA clut8")?
58100
)
59101
} else {
60102
Frame::from_bgra_clut8(
61103
width as u32, height as u32,
62104
&palette_bytes,
63-
buf.get(16..16 + width * height).ok_or("not enough pixel data for BGRA clut8")?
105+
buf.get(16..16 + width * height).ok_or("not enough data for BGRA clut8")?
64106
)
65107
}
66108
}
@@ -71,4 +113,4 @@ pub const ENTRY_PVR: Decoder<Image> = Decoder {
71113
}
72114
Ok(Image {frames: Box::new([frame])})
73115
}
74-
};
116+
};

kidfile/src/image_formats/tim2.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ pub const ENTRY_TIM2: Decoder<Image> = Decoder {
1717
tim2::Format::Indexed8 => PixelFormat::RgbaClut8,
1818
tim2::Format::Rgb888 => PixelFormat::Rgb,
1919
tim2::Format::Rgba8888 => PixelFormat::Rgba,
20-
tim2::Format::Abgr1555 => PixelFormat::Rgba16
20+
tim2::Format::Abgr1555 => PixelFormat::Rgba5551
2121
};
2222
if matches!(frame.og_fmt, PixelFormat::RgbaClut4 | PixelFormat::RgbaClut8 | PixelFormat::Rgba) {
2323
Some(frame.with_double_alpha())
@@ -32,4 +32,4 @@ pub const ENTRY_TIM2: Decoder<Image> = Decoder {
3232
Ok(Image {frames})
3333
}
3434
}
35-
};
35+
};

0 commit comments

Comments
 (0)