Fix plMipmap JPEG decompression using the wrong channel for JPEG alpha#310
Conversation
JPEG alpha images store the alpha data in the red channel, but our decompression code incorrectly extracted the blue channel instead, leading to corrupted alpha channels for mipmaps with JPEG alpha. The uncompressed buffers in question are stored in BGRA order in memory, but the channel extraction/swapping code accesses them as an array of 32-bit ints. From this perspective, on little-endian systems, the buffer "looks like" ARGB. So to use the red channel as alpha, we need to shift each 32-bit int 8 bits to the left and mask out the low 24 bits.
|
To clarify: the corruption only occurred when the JPEG alpha data was decompressed. As far as I can tell, there was never any corruption when compressing alpha data to JPEG (unless, of course, the alpha data came from decompressing an alpha JPEG) or when exporting the alpha JPEG as-is in compressed form. |
|
JPEG images are used relatively infrequently in Uru data (mostly for GUI elements like books and journals), and even fewer images have alpha channels, so hopefully any breakage resulting from this would be minimal 🤞🏼 |
|
Good news: after some further testing, it seems that JPEG alpha decompression was not fully broken since 2010. For example, the last release of PlasmaShop (2023.03.21) does not seem to suffer from this bug. It looks like #127 indirectly broke the alpha channel swapping code, because it changed #127 was opened in 2018, but only merged in March 2024, so it seems this bug was only present for about a year and a half. |
|
That explains some questions I had about the JPEG Alpha codepath. Every time I've messed with that code I've been concerned about breaking it because a lot of it felt unusual for reasons beyond my awareness. As an aside: at this point I feel that relying on code and data that quietly stores alpha in a color channel is a trap, and we have better formats for whatever this was used for. It might be worth converting the game data that needs alpha using this format, although we'd still need to support it here for reading and writing old formats. Better documentation in the code to prevent mistakes in the future would be appreciated. |
|
There are a lot of undocumented assumptions in the mipmap IO and conversion code, yeah. When I looked into the bug yesterday, I "livetweeted" my understanding of the code in the OpenUru Discord (thanks to @Hoikas for the initial pointers on how the memory layout is supposed to look!). I want to make another PR to document this properly in the code, but until then, here are the relevant bits of the chat, for reference. Partial chat log
|
|
Thanks for sharing that info here. :) |
These changes were performed with a buggy version of libHSPlasma that corrupted the alpha channels of mipmaps with a JPEG-compressed alpha channel (see H-uru/libhsplasma#310). Affected textures include multiple Relto page symbols (xYeeshaPageAlphaSketch...) and the Bahro stone share symbol (xBahroYeeshaShare). These textures were corrupted both in the PRP (and thus in-game) and in the extracted image files. As a temporary fix, this commit reverts that PR. It should be safe to redo the PR using a libHSPlasma version that isn't affected by this bug. This reverts the following commits: * 7fcbac4: Add sources for GUI_BkBookImages. * 14d3ed1: Remove unused linking panels.
JPEG alpha images store the alpha data in the red channel, but libHSPlasma's decompression code incorrectly extracted the blue channel instead, leading to corrupted alpha channels for mipmaps with JPEG alpha.
In particular, this caused @Hoikas's script in H-uru/moul-assets#287 to corrupt the alpha channels of all Relto page symbols, making them nearly invisible in-game. (They would be completely invisible if it weren't for JPEG artifacts making the alpha data in the red channel "bleed" into the blue channel.) Thanks to @TheScarFr for reporting this.
Once this fix is merged, @Hoikas's process in H-uru/moul-assets#287 should be rerun based on the previous, non-corrupted version of the PRP. I think the process itself is safe as long as libHSPlasma is working correctly.
I'm not aware of any other textures being affected so far, but because this bug has apparently existed since
2010 (!!!)March 2024 probably, it might have caused unnoticed corruption elsewhere as well :(Explanation for the code change: The uncompressed buffers in question are stored in BGRA order in memory, but the channel extraction/swapping code accesses them as an array of 32-bit ints. From this perspective, on little-endian systems, the buffer "looks like" ARGB. So to use the red channel as alpha, we need to shift each 32-bit int 8 bits to the left and mask out the low 24 bits.