Skip to content

feat(libScePngDec): implement 16-bit PNG output - #1271

Closed
MGXAe wants to merge 1 commit into
boykopovar:mainfrom
MGXAe:arena/40a13e68-anyps5
Closed

MGXAe wants to merge 1 commit into
boykopovar:mainfrom
MGXAe:arena/40a13e68-anyps5

Conversation

@MGXAe

@MGXAe MGXAe commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

What

scePngDecDecode has accepted the attribute = 1 (16-bit) create attribute, but threw 16-bit output is not implemented for any 16-bit PNG. This implements that path.

  • core/Decoder/Png: Decode16() returns a full-precision RGBA16 image through stbi_load_16_from_memory (std::nullopt on the same failures as Decode).
  • core/libs/prx/libScePngDec: when the header bit depth is 16 and the handle attribute is 1, the decode writes 16-bit output - 8 bytes per pixel, each channel a little-endian uint16_t, with pixel_format 1 giving B,G,R,A. A source without alpha (tRNS counts as alpha) fills all four channels, with alpha_value repeated into both bytes; the return value remains (width << 16) | height, and image_pitch / image_mem_size are validated against the 8-byte stride. A PNG of 8 bits or less keeps decoding to 8-bit output with either attribute, so the output stride still follows PngDecImageInfo.bit_depth.
  • The old throw std::runtime_error("scePngDecDecode: 16-bit output is not implemented") and its test assertion are removed; no other path changes.

No reference implements 16-bit output (shadPS4 rejects the attribute, OpenOrbis/ProsperoLight/Kyty define only the create-time attribute), so the little-endian RGBA16 guest layout follows the existing 8-bit output path and the SharpProspero attribute description, and is recorded in docs/dev/TechnicalDebt.md.

Tested

  • decoder_png_tests and guest_png_dec built from the exact changed sources with g++ -std=c++20 (Linux x86-64) and run: 16-bit gray, RGB and RGBA fixtures, alpha fill, both pixel formats, pitch padding, INVALID_PARAM / INVALID_SIZE / INVALID_ADDR / DECODE_ERROR paths, and 8-bit RGB and palette+tRNS images through the same 16-bit-capable handle.
  • Falsification: the new tests fail against the old decoder with the old not-implemented throw; the old test file fails only on the removed throw assertion.
  • Not tested: the full CMake build and ctest (the sandbox has no submodules initialized and the JPEG dependencies are unavailable), and console hardware; CI runs the complete suite.

Checklist

  • Based on current main; no other open PR implements the same functions
  • One topic per PR; follow-ups go in a new PR
  • No comments in code except technical debt
  • Unimplemented paths throw (NotImplemented_nid_no_patch); silent stubs are listed in TechnicalDebt
  • No notes, investigation .md files or images added to the repository (attach them to this PR)
  • New third-party code is a submodule built from source
  • Generic behaviour, not specific to one title
  • Depends on: none
  • AI-assisted: yes

A PNG whose header bit depth is 16 decoded through a handle created with
attribute 1 now writes 16-bit rows instead of throwing: each row is 8
bytes per pixel, the samples are little-endian R16G16B16A16 (B16G16R16A16
for the B8G8R8A8 pixel format), and an image without alpha is filled with
alpha_value in both bytes of the 16-bit value. PNGs of 8 bits or less keep
8-bit output with either attribute.

Decoder::Png gains Decode16 on stb's 16-bit loader, and the guest test
covers grayscale, RGB, RGBA, padded pitch, buffer checks and 8-bit files
through the 16-bit handle.

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
@MGXAe
MGXAe force-pushed the arena/40a13e68-anyps5 branch from bdd4fe1 to b59853b Compare October 8, 2026 03:25
@oneandonlydean

Copy link
Copy Markdown
Collaborator

Which title decodes a 16-bit PNG through a handle created with attribute = 1 on main? The throw this replaces is where such a title stops; with the title ID and that line this can go in. Without one I'd close it until a title reaches it, since the 16-bit guest layout is inferred, not confirmed.

@MGXAe

MGXAe commented Oct 8, 2026

Copy link
Copy Markdown
Contributor Author

There is no known title reaching this path on main. The throw was leftover debt from PR #175 rather than a blocker identified from a game log, and the 16-bit output layout in my PR was inferred rather than confirmed.

@MGXAe MGXAe closed this Oct 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs needs rebase Conflicts with main, rebase needed prx

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants