|
1 |
| -# RCD330 Image Utilities |
| 1 | +# RCD330 Image Utilities: OVG Image Format Converter |
2 | 2 |
|
3 |
| -Python 3 only because who uses 2 any more? Nerds, that's who. To run these, you need Pillow and numpy. Install via pip: |
| 3 | +This repository contains tools for converting OVG (proprietary image format) files to PNG and back, specifically designed for car stereo firmware modification. |
4 | 4 |
|
5 |
| -`pip install Pillow numpy` |
| 5 | +## Background |
6 | 6 |
|
7 |
| -## Tools |
| 7 | +OVG files are a proprietary image format used in the RCD330 (and likely other) car stereo firmware. They use RLE (Run-Length Encoding) compression with RGBA color data, saved to a binary file extension. |
8 | 8 |
|
9 |
| -### Boot logos |
| 9 | +This project was spun up out of a desire to skin more of the interface than ust the bootlogo. Much credit goes to [@cr3ative](https://github.com/cr3ative/) for sharing his initial discoveries publicly on GitHub in the [RCD 330 Image Utilities repo](https://github.com/cr3ative/rcd_330g_image_utilities). |
10 | 10 |
|
11 |
| -* To convert an RCD `logo.bin` file to a PNG called `logo.png`, edit and run `python3 rcd_to_png.py` |
12 |
| -* To convert any 800x480 PNG called `logo.png` to RCD `logo.bin`, run `python3 png_to_rcd.py` |
| 11 | +## File Format Description |
13 | 12 |
|
14 |
| -I haven't yet flashed any results to my personal RCD330. This is a work in progress, but the BIN conversion works both ways and matches exactly the expected format. |
| 13 | +The converter supports two different OVG file formats: |
15 | 14 |
|
16 |
| -- Credit to `mengxp` for the original image update tarball and conversion utility. |
17 |
| -- Credit to @tef and @marksteward for helping me understand XORs! |
| 15 | +### 1. RLE-Compressed OVG Format |
| 16 | +The traditional OVG format uses RLE (Run-Length Encoding) compression: |
| 17 | +- **Command Block**: 1 byte indicating compression type and pixel count |
| 18 | +- **Pixel Data**: RGBA data (4 bytes per pixel) |
| 19 | +- **RLE Compression**: Efficient encoding for repeated pixels |
18 | 20 |
|
19 |
| -### OVG bin files (flags, etc) |
| 21 | +### 2. Raw RGBA Format |
| 22 | +Some OVG files contain raw RGBA pixel data without compression: |
| 23 | +- **Direct RGBA**: 4 bytes per pixel (R, G, B, A) |
| 24 | +- **No compression**: Pixel data stored sequentially |
| 25 | +- **Square dimensions**: Often forms perfect or near-perfect squares |
20 | 26 |
|
21 |
| -* To convert an `_ovg.bin` file to PNG, edit and run `python3 ovg_to_png.py` |
22 |
| -* To convert any PNG to an `_ovg.bin` compatible file, edit and run `python3 png_to_ovg.py` |
| 27 | +The converter automatically detects which format is used and processes accordingly. |
23 | 28 |
|
24 |
| -- Credit to `Niklas_1414` for pushing me to examine these files. Further credits in the python file. |
| 29 | +### RLE Command Block Format (Format 1 only) |
| 30 | +``` |
| 31 | +Bit 7 (MSB): Compression flag (1 = compressed, 0 = uncompressed) |
| 32 | +Bits 6-0: Pixel count - 1 (0-127 representing 1-128 pixels) |
| 33 | +``` |
25 | 34 |
|
26 |
| -Cheers! |
| 35 | +### RLE Compression Types (Format 1 only) |
| 36 | +- **Compressed (MSB = 1)**: Next 4 bytes (RGBA) repeated N times |
| 37 | +- **Uncompressed (MSB = 0)**: Next N×4 bytes are individual RGBA pixels |
| 38 | + |
| 39 | +### Format Auto-Detection |
| 40 | +The converter automatically detects the format by analyzing: |
| 41 | +- **File size**: Must be divisible by 4 for raw RGBA |
| 42 | +- **Dimensions**: Raw RGBA often forms perfect squares |
| 43 | +- **Alpha patterns**: Common alpha values (0, 255) indicate raw RGBA |
| 44 | + |
| 45 | +## Tools Included |
| 46 | + |
| 47 | +### 1. `ovg_to_png.py` - OVG to PNG Decoder |
| 48 | +Converts OVG files to PNG format with transparency preservation. |
| 49 | + |
| 50 | +### 2. `png_to_ovg.py` - PNG to OVG Encoder |
| 51 | +Converts PNG files back to OVG format with RLE compression. |
| 52 | + |
| 53 | +## Requirements |
| 54 | + |
| 55 | +```bash |
| 56 | +pip3 install --user Pillow |
| 57 | +``` |
| 58 | + |
| 59 | +## Usage |
| 60 | + |
| 61 | +### Decoding OVG to PNG |
| 62 | + |
| 63 | +#### Basic Conversion |
| 64 | +```bash |
| 65 | +python3 ovg_to_png.py input.bin [output.png] |
| 66 | +``` |
| 67 | + |
| 68 | +Examples: |
| 69 | +```bash |
| 70 | +# Convert with auto-detected dimensions and format |
| 71 | +python3 ovg_to_png.py opt/gresfiles/img_off_clock_face_ovg.bin |
| 72 | + |
| 73 | +# Convert with custom output name |
| 74 | +python3 ovg_to_png.py opt/gresfiles/img_off_clock_face_ovg.bin my_clock_face.png |
| 75 | + |
| 76 | +# The converter will automatically detect if the file is RLE-compressed or raw RGBA |
| 77 | +# Example output: |
| 78 | +# Detected format: raw_rgba |
| 79 | +# Raw RGBA data contains 1521 pixels |
| 80 | +# Auto-detected dimensions: 39x39 |
| 81 | +``` |
| 82 | + |
| 83 | +#### Specify Exact Dimensions |
| 84 | +```bash |
| 85 | +python3 ovg_to_png.py input.bin output.png --width 286 --height 286 |
| 86 | +python3 ovg_to_png.py input.bin --width 200 # Height calculated automatically |
| 87 | +``` |
| 88 | + |
| 89 | +#### Interactive Size Discovery |
| 90 | +```bash |
| 91 | +# Use size discovery to find correct dimensions |
| 92 | +python3 ovg_to_png.py input.bin --discover |
| 93 | + |
| 94 | +# Customize discovery range |
| 95 | +python3 ovg_to_png.py input.bin --discover --width-min 50 --width-max 300 --width-step 5 |
| 96 | +``` |
| 97 | + |
| 98 | +The discovery mode will: |
| 99 | +- Generate test images at different sizes |
| 100 | +- Display each size and wait for your input |
| 101 | +- Let you jump to specific widths |
| 102 | +- Help you find the correct dimensions visually |
| 103 | + |
| 104 | +#### Available Options |
| 105 | +- `-w, --width WIDTH` - Specify image width |
| 106 | +- `--height HEIGHT` - Specify image height |
| 107 | +- `-d, --discover` - Interactive size discovery mode |
| 108 | +- `--width-min MIN` - Minimum width for discovery (default: 35) |
| 109 | +- `--width-max MAX` - Maximum width for discovery (default: 400) |
| 110 | +- `--width-step STEP` - Width step for discovery (default: 1) |
| 111 | + |
| 112 | +#### Usage Help |
| 113 | +```bash |
| 114 | +python3 ovg_to_png.py |
| 115 | +``` |
| 116 | + |
| 117 | +#### Output Files |
| 118 | +- **Clock Face**: 286×286 pixels (perfect square from 81,796 pixels) |
| 119 | +- **Clock Shadow**: 400×400 pixels |
| 120 | +- **Clock Spotlight**: 619×619 pixels |
| 121 | +- **Clock Hands**: 286×286 pixels each |
| 122 | + |
| 123 | +### Encoding PNG to OVG |
| 124 | + |
| 125 | +#### Convert Specific File |
| 126 | +```bash |
| 127 | +python3 png_to_ovg.py input.png [output.bin] |
| 128 | +``` |
| 129 | + |
| 130 | +Examples: |
| 131 | +```bash |
| 132 | +# Convert with custom output name |
| 133 | +python3 png_to_ovg.py my_custom_clock.png img_off_clock_face_ovg_new.bin |
| 134 | + |
| 135 | +# Convert with auto-generated output name (replaces .png with .bin) |
| 136 | +python3 png_to_ovg.py clock_face_decoded.png |
| 137 | +# Creates: clock_face_decoded.bin |
| 138 | +``` |
| 139 | + |
| 140 | +#### Usage Help |
| 141 | +```bash |
| 142 | +python3 png_to_ovg.py |
| 143 | +``` |
| 144 | + |
| 145 | +Shows usage information and examples. |
| 146 | + |
| 147 | +#### Test Roundtrip Conversion |
| 148 | +```bash |
| 149 | +# Test with default file |
| 150 | +python3 png_to_ovg.py test |
| 151 | + |
| 152 | +# Test with specific file |
| 153 | +python3 png_to_ovg.py test opt/gresfiles/img_off_clock_face_ovg.bin |
| 154 | +``` |
| 155 | + |
| 156 | +This will: |
| 157 | +1. Decode the original OVG to PNG |
| 158 | +2. Encode the PNG back to OVG |
| 159 | +3. Compare file sizes and report compression efficiency |
| 160 | +4. Clean up temporary files automatically |
| 161 | + |
| 162 | +## Complete Workflow for Clock Customization |
| 163 | + |
| 164 | +### Step 1: Extract Original Images |
| 165 | +```bash |
| 166 | +# Extract specific files with custom names |
| 167 | +python3 ovg_to_png.py opt/gresfiles/img_off_clock_face_ovg.bin my_clock_face.png |
| 168 | +python3 ovg_to_png.py opt/gresfiles/img_off_clock_shadow_ovg.bin my_shadow.png |
| 169 | +python3 ovg_to_png.py opt/gresfiles/img_off_clock_spotlight_ovg.bin my_spotlight.png |
| 170 | + |
| 171 | +# Or extract with auto-generated names |
| 172 | +python3 ovg_to_png.py opt/gresfiles/img_off_clock_face_ovg.bin |
| 173 | +# Creates: img_off_clock_face_ovg_decoded.png |
| 174 | +``` |
| 175 | + |
| 176 | +### Step 2: Edit Images |
| 177 | +- Open your extracted PNG files in your image editor |
| 178 | +- Modify the clock face design as desired |
| 179 | +- Edit other components (shadow, spotlight, hands) if needed |
| 180 | +- Save as PNG with transparency preserved |
| 181 | + |
| 182 | +### Step 3: Convert Back to OVG |
| 183 | +```bash |
| 184 | +# Convert specific files with custom output names |
| 185 | +python3 png_to_ovg.py my_clock_face.png img_off_clock_face_ovg_new.bin |
| 186 | +python3 png_to_ovg.py my_shadow.png img_off_clock_shadow_ovg_new.bin |
| 187 | + |
| 188 | +# Or convert with auto-generated names |
| 189 | +python3 png_to_ovg.py img_off_clock_face_ovg_decoded.png |
| 190 | +# Creates: img_off_clock_face_ovg_decoded.bin |
| 191 | +``` |
| 192 | + |
| 193 | +### Step 4: Replace in Firmware |
| 194 | +- Backup original files first! |
| 195 | +- Replace original `.bin` files with corresponding `*_new.bin` files |
| 196 | +- Update firmware with modified files |
| 197 | + |
| 198 | +## Key Files |
| 199 | + |
| 200 | +### Input Files (Original OVG) |
| 201 | +- `opt/gresfiles/img_off_clock_face_ovg.bin` - Main clock face |
| 202 | +- `opt/gresfiles/img_off_clock_shadow_ovg.bin` - Clock shadow |
| 203 | +- `opt/gresfiles/img_off_clock_spotlight_ovg.bin` - Clock spotlight |
| 204 | +- `opt/gresfiles/img_off_clock_hour_XX_ovg.bin` - Hour hand positions |
| 205 | +- `opt/gresfiles/img_off_clock_minute_XX_ovg.bin` - Minute hand positions |
| 206 | +- `opt/gresfiles/img_off_clock_second_XX_ovg.bin` - Second hand positions |
| 207 | + |
| 208 | +### Output Files (Editable PNG) |
| 209 | +- `clock_face_decoded.png` - Main clock face (286×286) |
| 210 | +- `img_off_clock_*_decoded.png` - Individual components |
| 211 | + |
| 212 | +### Generated Files (New OVG) |
| 213 | +- `img_off_clock_face_ovg_new.bin` - Modified clock face |
| 214 | +- `img_off_clock_*_ovg_new.bin` - Modified components |
| 215 | + |
| 216 | +## Technical Details |
| 217 | + |
| 218 | +### Compression Performance |
| 219 | +Typical compression ratios achieved: |
| 220 | +- **Clock Face**: ~2.8:1 compression |
| 221 | +- **Clock Shadow**: ~10.8:1 compression |
| 222 | +- **Clock Hands**: ~33-41:1 compression |
| 223 | +- **Spotlight**: ~11.3:1 compression |
| 224 | + |
| 225 | +### Image Specifications |
| 226 | +- **Format**: RGBA (32-bit with alpha channel) |
| 227 | +- **Typical Size**: 286×286 pixels for main components |
| 228 | +- **Color Depth**: 8 bits per channel (R, G, B, A) |
| 229 | +- **Byte Order**: Standard RGBA pixel ordering |
| 230 | + |
| 231 | +## Troubleshooting |
| 232 | + |
| 233 | +### PIL/Pillow Not Found |
| 234 | +```bash |
| 235 | +pip3 install --user Pillow |
| 236 | +``` |
| 237 | + |
| 238 | +### File Not Found Errors |
| 239 | +Ensure the `opt/gresfiles/` directory exists with original OVG files. |
| 240 | + |
| 241 | +### Size Mismatch |
| 242 | +The converter automatically calculates optimal dimensions and detects file format. If dimensions appear incorrect: |
| 243 | + |
| 244 | +1. **Check format detection**: The converter will show "Detected format: raw_rgba" or "Detected format: rle_ovg" |
| 245 | +2. **Raw RGBA files**: Should auto-detect to perfect or near-perfect squares |
| 246 | +3. **RLE OVG files**: May require manual dimension specification with `--width` and `--height` |
| 247 | +4. **Use discovery mode**: `--discover` to find correct dimensions visually |
| 248 | + |
| 249 | +### Compression Issues |
| 250 | +If the generated OVG file is significantly larger than the original: |
| 251 | +1. Check for unnecessary transparency in your PNG |
| 252 | +2. Ensure solid color areas are truly solid (no noise/gradients) |
| 253 | +3. The RLE compression works best with areas of repeated pixels |
| 254 | + |
| 255 | +## Development Notes |
| 256 | + |
| 257 | +### Discovery Process |
| 258 | +1. Initial attempts tried standard image formats (BMP, RGB, etc.) |
| 259 | +2. Analyzed byte patterns showing 0xFF padding and RLE-like structures |
| 260 | +3. Found reference to NXP AN4339 PDF describing similar RLE format |
| 261 | +4. Reverse-engineered the exact command block structure |
| 262 | +5. Implemented both decoder and encoder with roundtrip testing |
| 263 | + |
| 264 | +### Format Insights |
| 265 | +- Files start with 0xFF padding that should be skipped |
| 266 | +- Command blocks use 7-bit pixel counts (1-128 pixels per command) |
| 267 | +- RLE compression is very effective for clock graphics with large solid areas |
| 268 | +- Alpha channel is preserved and properly handled |
| 269 | + |
| 270 | +## References |
| 271 | + |
| 272 | +- [NXP AN4339 Application Note](https://www.nxp.com.cn/docs/en/application-note/AN4339.pdf) - Describes similar RLE routine |
| 273 | +- [Reverse Engineering Stack Exchange](https://reverseengineering.stackexchange.com/questions/27688/open-unknown-image-format-probably-a-raw-image) - Initial format identification |
| 274 | + |
| 275 | +## License |
| 276 | + |
| 277 | +This project is provided as-is for educational and personal use. Always backup original firmware before making modifications. |
0 commit comments