preserve 16-bit if present

This commit is contained in:
Nathan
2025-12-11 14:46:02 -07:00
parent 01599a8f48
commit f85fbf3441
3 changed files with 419 additions and 68 deletions

View File

@@ -1197,7 +1197,6 @@ _**Agent (model default, mode Agent)**_
---
Creating a diagnostic script to identify which sequences were skipped and why:
@@ -1337,7 +1336,6 @@ UnicodeEncodeError: 'charmap' codec can't encode character '\u2713' in position
---
Fixing the Unicode issue and improving the diagnostic script:
@@ -1476,7 +1474,6 @@ TypeError: expected str, bytes or os.PathLike object, not list
---
Fixing the script and listing all folders in the input directory:
@@ -1638,7 +1635,6 @@ HAS PNGs Pack2BagScan_animation 6d (600 PNG files)
---
Found 4 folders in the input directory with no PNG files, so they were skipped:
1. **Bag2PackScan_animation 2a** (0 PNG files)
@@ -1666,7 +1662,6 @@ _**Agent (model default, mode Agent)**_
---
Updating the script to process JPG files and report when we do:
@@ -1684,7 +1679,6 @@ Updating the script to process JPG files and report when we do:
---
Updating the script to skip JPG files and report when we do:
@@ -1789,7 +1783,6 @@ Lines added: 2, lines removed: undefined
---
Ensuring `jpg_files` is accessible in the final summary:
@@ -1809,6 +1802,356 @@ Ensuring `jpg_files` is accessible in the final summary:
---
<tool-use data-tool-type="generic" data-tool-name="read_lints">
<details>
<summary>Tool use: **read_lints** • Read lints for 1 file</summary>
Lint paths:
- `compress_pngs.py`
Lint results
**No lint errors found**
</details>
</tool-use>
---
---
Updated the script to:
1. Scan for both PNG and JPG/JPEG files
2. Report at the start how many JPG files were found and that they're being ignored
3. Process only PNG files
4. Include in the final summary how many JPG files were ignored
When you run it, it will report any JPG files found and skip them, so you'll know which sequences contain JPG files instead of PNGs.
---
_**User (2025-12-11 18:21Z)**_
create a gitignore and ignore the input and output folders pls.
---
_**Agent (model default, mode Agent)**_
---
---
<tool-use data-tool-type="write" data-tool-name="write">
<details><summary>Tool use: **write** • Edit file: .gitignore</summary>
**Chunk 1**
Lines added: 3, lines removed: 1
```diff
@@ -1,1 +1,3 @@
-
+ input/
+ output/
+
```
</details>
</tool-use>
---
---
Created a `.gitignore` file that ignores the `input/` and `output/` folders. Git will ignore these directories and their contents.
---
_**User (2025-12-11 21:39Z)**_
What method does it use to compress the PNGs? are they 16-bit, or 8-bit color? Does it specify?
---
_**Agent (model default, mode Agent)**_
---
Checking the compression method used in the script:
---
<tool-use data-tool-type="read" data-tool-name="read_file">
<details><summary>Tool use: **read_file** • Read file: compress_pngs.py</summary>
</details>
</tool-use>
---
---
The script uses:
1. Compression method: PIL/Pillow's PNG save with:
- `optimize=True` — enables PNG optimization
- `compress_level=9` — maximum zlib compression (0-9)
2. Bit depth: Not explicitly set. PIL/Pillow defaults to 8-bit per channel for RGB/RGBA. If the input is 16-bit, it will be converted to 8-bit during the `convert()` call.
3. Color mode: Converts to RGB or RGBA (preserves RGBA for transparency), but doesn't preserve 16-bit data.
If you want to preserve 16-bit depth or use a different compression method, I can update the script. Options:
- Preserve 16-bit depth if present
- Use a different compression tool (e.g., pngquant for lossy, optipng/pngcrush for lossless)
- Add explicit bit depth control
Should I update it?
---
_**User (2025-12-11 21:39Z)**_
Yes, let's preserve 16-bit if present.
---
_**Agent (model default, mode Agent)**_
---
Updating the script to preserve 16-bit depth when present:
---
<tool-use data-tool-type="read" data-tool-name="read_file">
<details><summary>Tool use: **read_file** • Read file: compress_pngs.py</summary>
</details>
</tool-use>
---
---
---
<tool-use data-tool-type="write" data-tool-name="search_replace">
<details><summary>Tool use: **search_replace** • Edit file: compress_pngs.py</summary>
**Chunk 1**
Lines added: 41, lines removed: 8
```diff
@@ -33,13 +33,46 @@
img = Image.open(input_path)
- # Convert to RGB if RGBA and no transparency needed
- if img.mode == 'RGBA':
- # Keep RGBA for transparency support
- img = img.convert('RGBA')
- elif img.mode != 'RGB':
- img = img.convert('RGB')
+ # Check if image is 16-bit by examining mode and pixel values
+ is_16bit = False
+ if img.mode == 'I':
+ # 'I' mode (32-bit integer) often represents 16-bit grayscale
+ is_16bit = True
+ elif img.mode in ('RGB', 'RGBA'):
+ # Check if max pixel value exceeds 8-bit range
+ try:
+ # Sample a few pixels to check bit depth
+ sample = img.crop((0, 0, min(100, img.width), min(100, img.height)))
+ max_val = max(sample.getdata())
+ if max_val &gt; 255:
+ is_16bit = True
+ except:
+ pass
+
+ # Preserve 16-bit depth if present, otherwise convert as needed
+ if is_16bit:
+ # Preserve original mode for 16-bit images
+ if img.mode == 'I':
+ # Keep grayscale 16-bit
+ pass
+ elif img.mode == 'RGBA':
+ # Keep RGBA 16-bit
+ pass
+ elif img.mode == 'RGB':
+ # Keep RGB 16-bit
+ pass
+ else:
+ # Convert other 16-bit modes to RGB
+ img = img.convert('RGB')
+ else:
+ # 8-bit images: convert as needed
+ if img.mode == 'RGBA':
+ # Keep RGBA for transparency support
+ img = img.convert('RGBA')
+ elif img.mode != 'RGB':
+ img = img.convert('RGB')
- # Save with maximum compression to output path
- img.save(str(output_path), 'PNG', optimize=True, compress_level=9)
+ # Save with maximum compression, preserving bit depth
+ save_kwargs = {'optimize': True, 'compress_level': 9}
+ img.save(str(output_path), 'PNG', **save_kwargs)
new_size = os.path.getsize(output_path)
savings = original_size - new_size
```
</details>
</tool-use>
---
---
Refining the 16-bit detection to be more accurate:
---
<tool-use data-tool-type="write" data-tool-name="search_replace">
<details><summary>Tool use: **search_replace** • Edit file: compress_pngs.py</summary>
**Chunk 1**
Lines added: 29, lines removed: 17
```diff
@@ -33,35 +33,47 @@
img = Image.open(input_path)
- # Check if image is 16-bit by examining mode and pixel values
+ # Check if image is 16-bit
+ # PIL represents 16-bit grayscale as 'I' mode (32-bit integer)
+ # For color images, check pixel values to determine bit depth
is_16bit = False
+ original_mode = img.mode
+
if img.mode == 'I':
- # 'I' mode (32-bit integer) often represents 16-bit grayscale
+ # 'I' mode typically represents 16-bit grayscale
is_16bit = True
- elif img.mode in ('RGB', 'RGBA'):
+ elif img.mode in ('RGB', 'RGBA', 'LA'):
# Check if max pixel value exceeds 8-bit range
try:
- # Sample a few pixels to check bit depth
- sample = img.crop((0, 0, min(100, img.width), min(100, img.height)))
- max_val = max(sample.getdata())
- if max_val &gt; 255:
- is_16bit = True
+ # Get a sample of pixels to check
+ pixels = list(img.getdata())
+ if pixels:
+ # Flatten if needed (for multi-channel modes)
+ if isinstance(pixels[0], (tuple, list)):
+ max_val = max(max(p) for p in pixels[:1000]) # Sample first 1000 pixels
+ else:
+ max_val = max(pixels[:1000])
+ if max_val &gt; 255:
+ is_16bit = True
except:
+ # If we can't determine, assume 8-bit to be safe
pass
# Preserve 16-bit depth if present, otherwise convert as needed
if is_16bit:
- # Preserve original mode for 16-bit images
+ # For 16-bit images, preserve the mode
+ # PIL will save 16-bit when mode is 'I' (grayscale) or when using specific modes
if img.mode == 'I':
- # Keep grayscale 16-bit
+ # Keep 16-bit grayscale
pass
- elif img.mode == 'RGBA':
- # Keep RGBA 16-bit
- pass
- elif img.mode == 'RGB':
- # Keep RGB 16-bit
+ elif img.mode in ('RGB', 'RGBA'):
+ # Keep color mode - PIL may preserve 16-bit depending on how it was loaded
+ # Note: PIL's PNG save may convert 16-bit RGB to 8-bit, but we preserve the mode
pass
else:
- # Convert other 16-bit modes to RGB
- img = img.convert('RGB')
+ # Convert other modes while trying to preserve bit depth
+ if 'A' in img.mode:
+ img = img.convert('RGBA')
+ else:
+ img = img.convert('RGB')
else:
# 8-bit images: convert as needed
```
**Chunk 2**
Lines added: 4, lines removed: 3
```diff
@@ -72,7 +84,8 @@
img = img.convert('RGB')
- # Save with maximum compression, preserving bit depth
- save_kwargs = {'optimize': True, 'compress_level': 9}
- img.save(str(output_path), 'PNG', **save_kwargs)
+ # Save with maximum compression
+ # PIL will preserve 16-bit for 'I' mode automatically
+ # For color 16-bit, PIL may convert to 8-bit, but we've preserved the mode
+ img.save(str(output_path), 'PNG', optimize=True, compress_level=9)
new_size = os.path.getsize(output_path)
savings = original_size - new_size
```
</details>
</tool-use>
---
---
---
@@ -1835,62 +2178,23 @@ Lint results
---
Updated the script to:
Updated the script to preserve 16-bit depth when present. It:
1. Scan for both PNG and JPG/JPEG files
2. Report at the start how many JPG files were found and that they're being ignored
3. Process only PNG files
4. Include in the final summary how many JPG files were ignored
1. **Detects 16-bit images** by:
- Checking if the mode is `'I'` (16-bit grayscale)
- Sampling pixel values to see if they exceed 255 (indicating 16-bit color)
When you run it, it will report any JPG files found and skip them, so you'll know which sequences contain JPG files instead of PNGs.
---
_**User (2025-12-11 18:21Z)**_
create a gitignore and ignore the input and output folders pls.
---
_**Agent (model default, mode Agent)**_
---
---
<tool-use data-tool-type="write" data-tool-name="write">
<details><summary>Tool use: **write** • Edit file: .gitignore</summary>
**Chunk 1**
Lines added: 3, lines removed: 1
```diff
@@ -1,1 +1,3 @@
-
+ input/
+ output/
+
```
</details>
</tool-use>
---
---
Created a `.gitignore` file that ignores the `input/` and `output/` folders. Git will ignore these directories and their contents.
2. **Preserves 16-bit** by:
- Keeping the original mode for 16-bit images
- Avoiding unnecessary conversions that would reduce bit depth
3. **Handles both**:
- **16-bit grayscale** (`'I'` mode): Preserved as 16-bit
- **16-bit color** (RGB/RGBA): Mode is preserved; PIL may still save as 8-bit due to library limitations
**Note**: PIL/Pillow fully supports 16-bit grayscale PNGs. For 16-bit color PNGs, PIL may convert to 8-bit when saving, which is a library limitation. If you need true 16-bit color preservation, a different tool (e.g., `pngcrush` or `optipng`) would be needed.
The script now preserves 16-bit depth where possible and avoids unnecessary conversions.
---

View File

@@ -2,3 +2,4 @@
python compress_pngs.py
pause

View File

@@ -32,14 +32,60 @@ def compress_png(input_path, output_path):
original_size = os.path.getsize(input_path)
img = Image.open(input_path)
# Convert to RGB if RGBA and no transparency needed
if img.mode == 'RGBA':
# Keep RGBA for transparency support
img = img.convert('RGBA')
elif img.mode != 'RGB':
img = img.convert('RGB')
# Check if image is 16-bit
# PIL represents 16-bit grayscale as 'I' mode (32-bit integer)
# For color images, check pixel values to determine bit depth
is_16bit = False
original_mode = img.mode
# Save with maximum compression to output path
if img.mode == 'I':
# 'I' mode typically represents 16-bit grayscale
is_16bit = True
elif img.mode in ('RGB', 'RGBA', 'LA'):
# Check if max pixel value exceeds 8-bit range
try:
# Get a sample of pixels to check
pixels = list(img.getdata())
if pixels:
# Flatten if needed (for multi-channel modes)
if isinstance(pixels[0], (tuple, list)):
max_val = max(max(p) for p in pixels[:1000]) # Sample first 1000 pixels
else:
max_val = max(pixels[:1000])
if max_val > 255:
is_16bit = True
except:
# If we can't determine, assume 8-bit to be safe
pass
# Preserve 16-bit depth if present, otherwise convert as needed
if is_16bit:
# For 16-bit images, preserve the mode
# PIL will save 16-bit when mode is 'I' (grayscale) or when using specific modes
if img.mode == 'I':
# Keep 16-bit grayscale
pass
elif img.mode in ('RGB', 'RGBA'):
# Keep color mode - PIL may preserve 16-bit depending on how it was loaded
# Note: PIL's PNG save may convert 16-bit RGB to 8-bit, but we preserve the mode
pass
else:
# Convert other modes while trying to preserve bit depth
if 'A' in img.mode:
img = img.convert('RGBA')
else:
img = img.convert('RGB')
else:
# 8-bit images: convert as needed
if img.mode == 'RGBA':
# Keep RGBA for transparency support
img = img.convert('RGBA')
elif img.mode != 'RGB':
img = img.convert('RGB')
# Save with maximum compression
# PIL will preserve 16-bit for 'I' mode automatically
# For color 16-bit, PIL may convert to 8-bit, but we've preserved the mode
img.save(str(output_path), 'PNG', optimize=True, compress_level=9)
new_size = os.path.getsize(output_path)
savings = original_size - new_size