Files
Crosspoint/scripts/patch_jpegdec.py
Jeremy Klein acc1ed4358 fix: guard DC writes in JPEGDEC MCU_SKIP path (#2058)
EIGHT_BIT_GRAYSCALE decode of a 3-component progressive JPEG calls
JPEGDecodeMCU_P with MCU_SKIP for Cb and Cr after every Y MCU. The
existing safe-pMCU patch redirects the wild pointer to &sMCUs[0] but
leaves the DC store unguarded, so each chroma skip overwrites the
just-decoded Y DC with the chroma DC predictor. Output reads sMCUs[0],
gets the trailing Cr DC (~0), and renders an all-black image.

Add `if (iMCU >= 0)` guards to the two pMCU[0] writes (main DC store and
successive-approximation update). The pointer redirect stays as the AC
wild-pointer defense; the new guards stop the silent corruption at
sMCUs[0]. The two fixes are independent and both required.

fixes the progressive 8bit grayscale jpeg regression in 1.3.0

Did you use AI tools to help write this code? partial
2026-05-19 09:52:29 -04:00

93 lines
3.1 KiB
Python

"""
PlatformIO pre-build script: apply CrossPoint's JPEGDEC patches via `git apply`.
The upstream JPEGDEC pin still has the wild-pointer + DC-write bugs in
JPEGDecodeMCU_P that surface when EIGHT_BIT_GRAYSCALE decodes a 3-component
progressive JPEG (each Y MCU drags two MCU_SKIP calls behind it for Cb/Cr).
The patches in `scripts/jpegdec_patches/` carry the fix; this script applies
each one against the libdep working tree.
Each patch's idempotency is decided by git itself:
* `git apply --check --reverse` succeeds -> already applied, skip
* `git apply --check` succeeds -> apply
* neither succeeds -> abort the build
Patches live in `scripts/jpegdec_patches/` as one-commit-per-fix files
(see the file headers for context). Applied in lexical order.
"""
Import("env") # noqa: F821 (SCons-injected global)
import os
import subprocess
import sys
PATCH_DIR = os.path.join(env["PROJECT_DIR"], "scripts", "jpegdec_patches") # noqa: F821
def patch_jpegdec(env):
libdeps_dir = os.path.join(env["PROJECT_DIR"], ".pio", "libdeps")
if not os.path.isdir(libdeps_dir):
return
patches = _patch_files()
for env_dir in os.listdir(libdeps_dir):
jpeg_dir = os.path.join(libdeps_dir, env_dir, "JPEGDEC")
if not os.path.isdir(os.path.join(jpeg_dir, ".git")):
continue
for patch in patches:
_apply_one(jpeg_dir, patch)
def _patch_files():
if not os.path.isdir(PATCH_DIR):
raise RuntimeError(
"JPEGDEC patches missing -- aborting build (expected directory %s)"
% PATCH_DIR
)
patches = sorted(
os.path.join(PATCH_DIR, name)
for name in os.listdir(PATCH_DIR)
if name.endswith(".patch")
)
if not patches:
raise RuntimeError(
"JPEGDEC patches missing -- aborting build (no .patch files in %s)"
% PATCH_DIR
)
return patches
def _apply_one(jpeg_dir, patch_path):
name = os.path.basename(patch_path)
if _git_apply_succeeds(jpeg_dir, patch_path, reverse=True):
return
if not _git_apply_succeeds(jpeg_dir, patch_path, reverse=False):
# Not applied, not appliable -- the libdep source has diverged from
# what the patch expects. Don't write a half-patched file.
result = subprocess.run(
["git", "apply", "--check", patch_path],
cwd=jpeg_dir,
capture_output=True,
text=True,
)
sys.stderr.write(
"ERROR: JPEGDEC patch %s does not apply cleanly:\n%s%s\n"
% (name, result.stdout, result.stderr)
)
raise SystemExit(1)
subprocess.run(["git", "apply", patch_path], cwd=jpeg_dir, check=True)
print("Applied JPEGDEC patch: %s" % name)
def _git_apply_succeeds(jpeg_dir, patch_path, *, reverse):
cmd = ["git", "apply", "--check"]
if reverse:
cmd.append("--reverse")
cmd.append(patch_path)
return subprocess.run(
cmd, cwd=jpeg_dir, capture_output=True, text=True
).returncode == 0
patch_jpegdec(env) # noqa: F821