fix: decode TEXM 888 channels in native order
Docs Deploy / Build and Deploy MkDocs (push) Successful in 39s
Docs Deploy / Build and Deploy MkDocs (push) Successful in 39s
This commit is contained in:
@@ -825,17 +825,17 @@ fn decode_luminance_alpha88(word: u16) -> [u8; 4] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn decode_rgb888x(dword: u32) -> [u8; 4] {
|
fn decode_rgb888x(dword: u32) -> [u8; 4] {
|
||||||
let red = u8::try_from(dword & 0xFF).unwrap_or(u8::MAX);
|
let red = u8::try_from((dword >> 16) & 0xFF).unwrap_or(u8::MAX);
|
||||||
let green = u8::try_from((dword >> 8) & 0xFF).unwrap_or(u8::MAX);
|
let green = u8::try_from((dword >> 8) & 0xFF).unwrap_or(u8::MAX);
|
||||||
let blue = u8::try_from((dword >> 16) & 0xFF).unwrap_or(u8::MAX);
|
let blue = u8::try_from(dword & 0xFF).unwrap_or(u8::MAX);
|
||||||
[red, green, blue, 255]
|
[red, green, blue, 255]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decode_argb8888(dword: u32) -> [u8; 4] {
|
fn decode_argb8888(dword: u32) -> [u8; 4] {
|
||||||
let alpha = u8::try_from(dword & 0xFF).unwrap_or(u8::MAX);
|
let alpha = u8::try_from((dword >> 24) & 0xFF).unwrap_or(u8::MAX);
|
||||||
let red = u8::try_from((dword >> 8) & 0xFF).unwrap_or(u8::MAX);
|
let red = u8::try_from((dword >> 16) & 0xFF).unwrap_or(u8::MAX);
|
||||||
let green = u8::try_from((dword >> 16) & 0xFF).unwrap_or(u8::MAX);
|
let green = u8::try_from((dword >> 8) & 0xFF).unwrap_or(u8::MAX);
|
||||||
let blue = u8::try_from((dword >> 24) & 0xFF).unwrap_or(u8::MAX);
|
let blue = u8::try_from(dword & 0xFF).unwrap_or(u8::MAX);
|
||||||
[red, green, blue, alpha]
|
[red, green, blue, alpha]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -906,6 +906,53 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn decodes_8888_and_888x_in_native_argb_dword_order() {
|
||||||
|
let transparent_white_bytes = [0xFF, 0xFF, 0xFF, 0x00];
|
||||||
|
let transparent_white = decode_texm(Arc::from(
|
||||||
|
payload(1, 1, 8888, &[&transparent_white_bytes]).into_boxed_slice(),
|
||||||
|
))
|
||||||
|
.expect("8888 transparent white texture");
|
||||||
|
assert_eq!(
|
||||||
|
decode_mip_rgba8(&transparent_white, 0)
|
||||||
|
.expect("decode transparent white")
|
||||||
|
.rgba8,
|
||||||
|
[0xFF, 0xFF, 0xFF, 0x00]
|
||||||
|
);
|
||||||
|
|
||||||
|
let argb_bytes = 0xF123_4567_u32.to_le_bytes();
|
||||||
|
let argb = decode_texm(Arc::from(
|
||||||
|
payload(1, 1, 8888, &[&argb_bytes]).into_boxed_slice(),
|
||||||
|
))
|
||||||
|
.expect("8888 ARGB texture");
|
||||||
|
assert_eq!(
|
||||||
|
decode_mip_rgba8(&argb, 0).expect("decode ARGB").rgba8,
|
||||||
|
[0x23, 0x45, 0x67, 0xF1]
|
||||||
|
);
|
||||||
|
|
||||||
|
let rgb888x_bytes = [0x67, 0x45, 0x23, 0xA9];
|
||||||
|
let rgb888x = decode_texm(Arc::from(
|
||||||
|
payload(1, 1, 888, &[&rgb888x_bytes]).into_boxed_slice(),
|
||||||
|
))
|
||||||
|
.expect("888 RGB texture");
|
||||||
|
assert_eq!(rgb888x.mip_level(0).expect("raw mip").bytes, rgb888x_bytes);
|
||||||
|
assert_eq!(
|
||||||
|
decode_mip_rgba8(&rgb888x, 0).expect("decode RGB888X").rgba8,
|
||||||
|
[0x23, 0x45, 0x67, 0xFF]
|
||||||
|
);
|
||||||
|
|
||||||
|
let native_4444 = decode_texm(Arc::from(
|
||||||
|
payload(1, 1, 4444, &[&0xF246_u16.to_le_bytes()]).into_boxed_slice(),
|
||||||
|
))
|
||||||
|
.expect("4444 ARGB texture");
|
||||||
|
assert_eq!(
|
||||||
|
decode_mip_rgba8(&native_4444, 0)
|
||||||
|
.expect("decode native 4444")
|
||||||
|
.rgba8,
|
||||||
|
[0x22, 0x44, 0x66, 0xFF]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn rejects_zero_dimensions() {
|
fn rejects_zero_dimensions() {
|
||||||
let err = decode_texm(Arc::from(
|
let err = decode_texm(Arc::from(
|
||||||
@@ -972,7 +1019,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
decode_mip_rgba8(&document, 0).expect("rgba").rgba8,
|
decode_mip_rgba8(&document, 0).expect("rgba").rgba8,
|
||||||
vec![0x11, 0x22, 0x33, 0xFF]
|
vec![0x33, 0x22, 0x11, 0xFF]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,12 +24,16 @@ struct TexmHeader32 {
|
|||||||
556 R5 G5 B6
|
556 R5 G5 B6
|
||||||
4444 A4 R4 G4 B4
|
4444 A4 R4 G4 B4
|
||||||
88 L8 A8
|
88 L8 A8
|
||||||
888 RGB8 in four-byte element
|
888 packed 0xXXRRGGBB; little-endian bytes [B,G,R,X], RGBA output uses alpha 255
|
||||||
8888 A8 R8 G8 B8
|
8888 packed 0xAARRGGBB; little-endian bytes [B,G,R,A], decoded as RGBA
|
||||||
```
|
```
|
||||||
|
|
||||||
Короткие каналы расширяются до 8 bits повторением значимых bits. Для 888
|
Короткие каналы расширяются до 8 bits повторением значимых bits. Для 888
|
||||||
служебный четвёртый byte сохраняется при roundtrip.
|
служебный четвёртый byte сохраняется в raw mip, но не попадает в RGBA output.
|
||||||
|
Native Ngi32 code confirms this layout: VA `0x10005930` packs `0xAARRGGBB` into
|
||||||
|
ARGB4444, while VA `0x1000F620`/`0x1000F70C` route 888/8888 through the byte-copy
|
||||||
|
path at `0x1000F730`, without a channel swizzle. On disk, little-endian bytes
|
||||||
|
are `[B,G,R,A]` for 8888 and `[B,G,R,X]` for 888.
|
||||||
|
|
||||||
## Layout
|
## Layout
|
||||||
|
|
||||||
|
|||||||
@@ -521,14 +521,18 @@ struct TexmHeader32 {
|
|||||||
556 R5 G5 B6
|
556 R5 G5 B6
|
||||||
4444 A4 R4 G4 B4
|
4444 A4 R4 G4 B4
|
||||||
88 L8 A8
|
88 L8 A8
|
||||||
888 RGB8 в четырёхбайтовом element
|
888 packed 0xXXRRGGBB; little-endian bytes [B,G,R,X], RGBA output использует alpha 255
|
||||||
8888 A8 R8 G8 B8
|
8888 packed 0xAARRGGBB; little-endian bytes [B,G,R,A], декодируется как RGBA
|
||||||
```
|
```
|
||||||
|
|
||||||
Formats 556 и 88 являются loader-confirmed, но не corpus-verified для
|
Formats 556 и 88 являются loader-confirmed, но не corpus-verified для
|
||||||
доступных игровых payload. CPU decoder расширяет короткие каналы до 8 bit через
|
доступных игровых payload. CPU decoder расширяет короткие каналы до 8 bit через
|
||||||
повторение значимых bit, а не простым shift. Для 888 служебный четвёртый byte
|
повторение значимых bit, а не простым shift. Для 888 служебный четвёртый byte
|
||||||
сохраняется при roundtrip.
|
сохраняется в raw mip, но не попадает в RGBA output.
|
||||||
|
Нативный код Ngi32 подтверждает layout: VA `0x10005930` упаковывает
|
||||||
|
`0xAARRGGBB` в ARGB4444, а VA `0x1000F620`/`0x1000F70C` направляют 888/8888
|
||||||
|
по byte-copy пути `0x1000F730` без перестановки каналов. На диске little-endian
|
||||||
|
байты имеют порядок `[B,G,R,A]` для 8888 и `[B,G,R,X]` для 888.
|
||||||
|
|
||||||
Layout:
|
Layout:
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user