Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions ISSUE_3_INVESTIGATION.md
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.

Copy link
Copy Markdown

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.md is 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.

Fix in Cursor Fix in Web

28 changes: 19 additions & 9 deletions texturepack.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CUDA check can crash without exception handling

Low Severity

The call to cv2.cuda.getCudaEnabledDeviceCount() at line 40 is not wrapped in a try/except. On some OpenCV installations (older versions, custom builds, or minimal packages), the cv2.cuda submodule may not exist, raising an unhandled AttributeError that crashes the entire upscale() function before processing any images. Since the whole purpose of this change is graceful CPU fallback, the CUDA availability check itself needs to be guarded.

Fix in Cursor Fix in Web

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

Copilot AI Feb 10, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cv2.cuda.getCudaEnabledDeviceCount() can raise cv2.error (and/or cv2.cuda may be absent) when OpenCV is built without CUDA support. In that case this fallback logic will still crash before reaching the CPU path. Consider guarding with hasattr(cv2, "cuda") and wrapping the call in try/except cv2.error, defaulting to CPU when CUDA APIs are unavailable.

Suggested change
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.")

Copilot uses AI. Check for mistakes.

# Set the desired model and scale to get correct pre- and post-processing
sr.setModel(algo.lower(), scalefactor)
Expand All @@ -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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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

Copy link

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.

Copilot uses AI. Check for mistakes.
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)
Expand Down