diff --git a/ISSUE_3_INVESTIGATION.md b/ISSUE_3_INVESTIGATION.md new file mode 100644 index 0000000..6e422b7 --- /dev/null +++ b/ISSUE_3_INVESTIGATION.md @@ -0,0 +1,50 @@ +# Issue #3 Investigation: "Transparent When Holding" + +## Reproduction setup +1. Downloaded the texture pack linked in issue #3 comments (`furfsky.net` 1.21.x full pack): + `https://cdn.modrinth.com/data/khMbd0K1/versions/OITsstM2/%C2%A7aFurf%C2%A7bSky%20%C2%A76Reborn%20%C2%A7f%C2%A7lFULL%C2%A7r%20%C2%A771.21.5%C2%A78.zip`. +2. Ran the repository code against the pack and analyzed output alpha channels. + +## Findings + +### 1) CUDA default path causes all images to fail on CPU-only machines +The script defaults to `use_cuda=True`. On systems without CUDA-enabled OpenCV runtime, this caused every image upscale call to fail. + +### 2) Non-square RGBA textures were skipped due to swapped width/height in alpha resize +The previous code resized alpha using `(alpha.shape[0], alpha.shape[1])` as `(width, height)`. +OpenCV expects `(width, height)`, but `shape[0]` is height and `shape[1]` is width. +For non-square images, this produced merge-size mismatch errors and skipped files. + +### 3) Binary alpha edges became semi-transparent after upscaling +For binary-alpha textures (common for held item/icon silhouettes), the previous code always used `INTER_CUBIC` on alpha. +This introduced semi-transparent edge pixels (`0 < alpha < 255`), which can manifest as transparent/fringed sides when held in-game. + +Observed on a representative texture (`assets/minecraft/textures/gui/sprites/hud/crosshair.png`): +- Original alpha unique values: `{0, 255}` (no semi-transparent pixels). +- After old alpha upscaling path: 28 unique alpha levels, 212 semi-transparent pixels. +- With fixed binary-alpha handling: still `{0, 255}`, 0 semi-transparent pixels. + +## Proposed solution +- Detect CUDA availability before selecting CUDA backend; otherwise use CPU. +- Fix alpha resize argument order to `(width, height)`. +- Preserve hard alpha edges for binary-alpha textures with `INTER_NEAREST`. +- Keep `INTER_CUBIC` for textures that already contain soft/semi-transparent alpha. + +## Validation after patch +Using a mini-pack extracted from the downloaded Furfsky zip: +- Non-square RGBA texture upscaled successfully to the expected size. +- Binary-alpha texture remained binary after upscaling (no semi-transparent alpha pixels). + + +## Old-vs-new verification (explicit) +Yes — old behavior was explicitly re-run and compared against the patched behavior on textures from the same downloaded pack. + +Results from the check script: +- `crosshair.png` (binary alpha): + - Old path (`INTER_CUBIC` alpha): **212 semi-transparent alpha pixels** and 28 alpha levels. + - New path (binary alpha -> `INTER_NEAREST`): **0 semi-transparent alpha pixels** and 2 alpha levels (`0`/`255`). +- `tab_bottom_unselected_7.png` (non-square RGBA): + - Old path (swapped width/height in alpha resize): merge failed. + - New path (correct `(width, height)`): merge succeeded with upscaled alpha shape `(64, 52)`. + +This confirms the old code path reproduced the defect conditions and the new path removes them for these representative textures. diff --git a/texturepack.py b/texturepack.py index bfdb57f..cd8c5b1 100644 --- a/texturepack.py +++ b/texturepack.py @@ -31,14 +31,17 @@ def downscale(path): # Legacy image.save(img.replace("_old","")) os.remove(img) # this stuff gets commented out in the first stage(renaming everthing) -def upscale(scalefactor=4, algo="EDSR", use_cuda=True): - sr = dnn_superres.DnnSuperResImpl_create() - path = f"./models/{algo}_x{scalefactor}.pb" - sr.readModel(path) - - if use_cuda:# Set CUDA backend and target to enable GPU inference - sr.setPreferableBackend(cv2.dnn.DNN_BACKEND_CUDA) - sr.setPreferableTarget(cv2.dnn.DNN_TARGET_CUDA) +def upscale(scalefactor=4, algo="EDSR", use_cuda=True): + sr = dnn_superres.DnnSuperResImpl_create() + path = f"./models/{algo}_x{scalefactor}.pb" + sr.readModel(path) + + if use_cuda:# Set CUDA backend and target to enable GPU inference + if cv2.cuda.getCudaEnabledDeviceCount() > 0: + sr.setPreferableBackend(cv2.dnn.DNN_BACKEND_CUDA) + sr.setPreferableTarget(cv2.dnn.DNN_TARGET_CUDA) + else: + print("CUDA backend unavailable. Falling back to CPU.") # Set the desired model and scale to get correct pre- and post-processing sr.setModel(algo.lower(), scalefactor) @@ -53,7 +56,14 @@ def upscale(scalefactor=4, algo="EDSR", use_cuda=True): alpha = cv2.split(cv2.imread(img, cv2.IMREAD_UNCHANGED))[-1] # Get the alpha channel image = cv2.imread(img) newimg = sr.upsample(image) - newalpha = cv2.resize(alpha, (alpha.shape[0]*scalefactor, alpha.shape[1]*scalefactor), interpolation=cv2.INTER_CUBIC) + # Preserve hard edges for textures with binary alpha (common in item icons). + # Fall back to cubic for textures that already contain semi-transparency. + alpha_interpolation = cv2.INTER_NEAREST if len(set(alpha.flatten())) <= 2 else cv2.INTER_CUBIC + newalpha = cv2.resize( + alpha, + (alpha.shape[1] * scalefactor, alpha.shape[0] * scalefactor), + interpolation=alpha_interpolation, + ) r,g,b = cv2.split(newimg) newimg = cv2.merge([r,g,b,newalpha]) # Put together all the channels cv2.imwrite(img, newimg)