Idea
Replace the current linear min/dist tile encoding with a circular center/range encoding that treats pixel values as points on a circle (0-255 wrapping).
Current Approach (min/dist)
Each 4×4 tile stores:
min: minimum pixel value
dist: max - min
indices: each pixel stored as pixel - min using ceil(log2(dist + 1)) bits
Proposed Approach (center/range)
Each 4×4 tile stores:
center: center of the smallest circular arc containing all values
range: width of that arc
indices: each pixel stored as circular offset using ceil(log2(range + 1)) bits
Why This Could Be Better
Circular range is always ≤ linear dist:
| Pixels |
Linear (min/dist) |
Circular (center/range) |
| [100, 105, 110, 115] |
dist=15, 4 bits |
range=15, 4 bits (equal) |
| [250, 255, 0, 5] |
dist=255, 8 bits |
range=11, 4 bits (better!) |
| [128, 130, 126, 132] |
dist=6, 3 bits |
range=6, 3 bits (equal) |
For values that wrap around 0/255, circular encoding can significantly reduce bits needed.
Algorithm
Encoding (find optimal center)
fn find_circular_range(pixels: &[u8; 16]) -> (u8, u8) {
// Sort unique values
let mut sorted: Vec<u8> = pixels.iter().copied().collect();
sorted.sort();
sorted.dedup();
if sorted.len() == 1 {
return (sorted[0], 0);
}
// Find largest gap between consecutive values (circularly)
let mut max_gap = 0u16;
let mut gap_end = sorted[0];
for i in 0..sorted.len() {
let next = if i + 1 < sorted.len() {
sorted[i + 1]
} else {
sorted[0].wrapping_add(256 - sorted[sorted.len() - 1]) // wrap
};
let gap = next.wrapping_sub(sorted[i]) as u16;
if gap > max_gap {
max_gap = gap;
gap_end = next;
}
}
// Range starts at gap_end, spans 256 - max_gap
let range = (256 - max_gap) as u8;
let start = gap_end;
let center = start.wrapping_add(range / 2);
(center, range)
}
Decoding
fn decode_pixel(center: u8, range: u8, index: u8) -> u8 {
let start = center.wrapping_sub(range / 2);
start.wrapping_add(index)
}
Testing Plan
- Implement both encodings side-by-side
- Run on test corpus of grayscale images
- Measure:
- Total bits saved across all tiles
- Percentage of tiles where circular is better
- Compression ratio improvement
- Benchmark encoding/decoding speed difference
Expected Outcomes
- Best case: Images with high pixel values near 255 (bright regions with noise) could see meaningful improvement
- Typical case: Most natural images won't wrap, so equal performance
- Worst case: Equal to current (never worse)
Considerations
- Format change: Would require new format version if adopted
- Compatibility: Could be opt-in via a flag bit in the header
- Complexity: Slightly more complex encode/decode, but O(1) operations
Questions to Answer
- How often do real-world tiles benefit from circular encoding?
- Is the improvement significant enough to justify format change?
- Any edge cases where the algorithm breaks down?
Idea
Replace the current linear
min/disttile encoding with a circularcenter/rangeencoding that treats pixel values as points on a circle (0-255 wrapping).Current Approach (min/dist)
Each 4×4 tile stores:
min: minimum pixel valuedist: max - minindices: each pixel stored aspixel - minusingceil(log2(dist + 1))bitsProposed Approach (center/range)
Each 4×4 tile stores:
center: center of the smallest circular arc containing all valuesrange: width of that arcindices: each pixel stored as circular offset usingceil(log2(range + 1))bitsWhy This Could Be Better
Circular range is always ≤ linear dist:
For values that wrap around 0/255, circular encoding can significantly reduce bits needed.
Algorithm
Encoding (find optimal center)
Decoding
Testing Plan
Expected Outcomes
Considerations
Questions to Answer