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
93 lines
3.1 KiB
Python
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
|