-
Notifications
You must be signed in to change notification settings - Fork 0
Fix alpha upscaling artifacts and add CUDA fallback (addresses #3) #4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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. | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -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: | ||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. CUDA check can crash without exception handlingLow Severity The call to |
||||||||||||||||||||||||||||||||||||||
| sr.setPreferableBackend(cv2.dnn.DNN_BACKEND_CUDA) | ||||||||||||||||||||||||||||||||||||||
| sr.setPreferableTarget(cv2.dnn.DNN_TARGET_CUDA) | ||||||||||||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||||||||||||
| print("CUDA backend unavailable. Falling back to CPU.") | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+39
to
+44
|
||||||||||||||||||||||||||||||||||||||
| 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.") | |
| if use_cuda: # Set CUDA backend and target to enable GPU inference | |
| if hasattr(cv2, "cuda"): | |
| try: | |
| 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.") | |
| except cv2.error: | |
| print("OpenCV CUDA support not available in this build. Falling back to CPU.") | |
| else: | |
| print("OpenCV CUDA module not found. Falling back to CPU.") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Restrict nearest-alpha path to true binary masks
The new len(set(alpha.flatten())) <= 2 check treats any two-level alpha image as “binary,” so textures with semi-transparency encoded as two values (for example {0, 128} or {128, 255}) will incorrectly use INTER_NEAREST instead of cubic and lose soft edges. This contradicts the stated behavior of preserving semi-transparent textures and can visibly degrade icons/GUI assets that use quantized alpha ramps.
Useful? React with 👍 / 👎.
Copilot
AI
Feb 10, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Binary-alpha detection via len(set(alpha.flatten())) <= 2 is both expensive (creates a Python set over every pixel) and not equivalent to “binary alpha”: an alpha channel with exactly two non-{0,255} levels (e.g., {0, 64}) will be treated as binary and forced to INTER_NEAREST. Prefer a vectorized NumPy check like np.any((alpha != 0) & (alpha != 255)) (or np.isin) to decide between nearest/cubic, which avoids Python-level per-pixel work and matches the stated intent.


There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Debugging investigation document committed to repository root
Low Severity
ISSUE_3_INVESTIGATION.mdis a Codex task debugging artifact containing ephemeral details like specific pixel counts, test script outputs, and reproduction steps. This content belongs in the GitHub issue or PR comments, not as a permanent file in the repository root. It will become stale as code evolves and adds confusion for contributors who may mistake it for essential project documentation.