This repository contains tools for converting OVG (proprietary image format) files to PNG and back, specifically designed for car stereo firmware modification.
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.
This project was spun up out of a desire to skin more of the interface than ust the bootlogo. Much credit goes to @cr3ative for sharing his initial discoveries publicly on GitHub in the RCD 330 Image Utilities repo.
The converter supports two different OVG file formats:
The traditional OVG format uses RLE (Run-Length Encoding) compression:
- Command Block: 1 byte indicating compression type and pixel count
- Pixel Data: RGBA data (4 bytes per pixel)
- RLE Compression: Efficient encoding for repeated pixels
Some OVG files contain raw RGBA pixel data without compression:
- Direct RGBA: 4 bytes per pixel (R, G, B, A)
- No compression: Pixel data stored sequentially
- Square dimensions: Often forms perfect or near-perfect squares
The converter automatically detects which format is used and processes accordingly.
Bit 7 (MSB): Compression flag (1 = compressed, 0 = uncompressed)
Bits 6-0: Pixel count - 1 (0-127 representing 1-128 pixels)
- Compressed (MSB = 1): Next 4 bytes (RGBA) repeated N times
- Uncompressed (MSB = 0): Next N×4 bytes are individual RGBA pixels
The converter automatically detects the format by analyzing:
- File size: Must be divisible by 4 for raw RGBA
- Dimensions: Raw RGBA often forms perfect squares
- Alpha patterns: Common alpha values (0, 255) indicate raw RGBA
Converts OVG files to PNG format with transparency preservation.
Converts PNG files back to OVG format with RLE compression.
pip3 install --user Pillow
python3 ovg_to_png.py input.bin [output.png]
Examples:
# Convert with auto-detected dimensions and format
python3 ovg_to_png.py opt/gresfiles/img_off_clock_face_ovg.bin
# Convert with custom output name
python3 ovg_to_png.py opt/gresfiles/img_off_clock_face_ovg.bin my_clock_face.png
# The converter will automatically detect if the file is RLE-compressed or raw RGBA
# Example output:
# Detected format: raw_rgba
# Raw RGBA data contains 1521 pixels
# Auto-detected dimensions: 39x39
python3 ovg_to_png.py input.bin output.png --width 286 --height 286
python3 ovg_to_png.py input.bin --width 200 # Height calculated automatically
# Use size discovery to find correct dimensions
python3 ovg_to_png.py input.bin --discover
# Customize discovery range
python3 ovg_to_png.py input.bin --discover --width-min 50 --width-max 300 --width-step 5
The discovery mode will:
- Generate test images at different sizes
- Display each size and wait for your input
- Let you jump to specific widths
- Help you find the correct dimensions visually
-w, --width WIDTH
- Specify image width--height HEIGHT
- Specify image height-d, --discover
- Interactive size discovery mode--width-min MIN
- Minimum width for discovery (default: 35)--width-max MAX
- Maximum width for discovery (default: 400)--width-step STEP
- Width step for discovery (default: 1)
python3 ovg_to_png.py
- Clock Face: 286×286 pixels (perfect square from 81,796 pixels)
- Clock Shadow: 400×400 pixels
- Clock Spotlight: 619×619 pixels
- Clock Hands: 286×286 pixels each
python3 png_to_ovg.py input.png [output.bin]
Examples:
# Convert with custom output name
python3 png_to_ovg.py my_custom_clock.png img_off_clock_face_ovg_new.bin
# Convert with auto-generated output name (replaces .png with .bin)
python3 png_to_ovg.py clock_face_decoded.png
# Creates: clock_face_decoded.bin
python3 png_to_ovg.py
Shows usage information and examples.
# Test with default file
python3 png_to_ovg.py test
# Test with specific file
python3 png_to_ovg.py test opt/gresfiles/img_off_clock_face_ovg.bin
This will:
- Decode the original OVG to PNG
- Encode the PNG back to OVG
- Compare file sizes and report compression efficiency
- Clean up temporary files automatically
# Extract specific files with custom names
python3 ovg_to_png.py opt/gresfiles/img_off_clock_face_ovg.bin my_clock_face.png
python3 ovg_to_png.py opt/gresfiles/img_off_clock_shadow_ovg.bin my_shadow.png
python3 ovg_to_png.py opt/gresfiles/img_off_clock_spotlight_ovg.bin my_spotlight.png
# Or extract with auto-generated names
python3 ovg_to_png.py opt/gresfiles/img_off_clock_face_ovg.bin
# Creates: img_off_clock_face_ovg_decoded.png
- Open your extracted PNG files in your image editor
- Modify the clock face design as desired
- Edit other components (shadow, spotlight, hands) if needed
- Save as PNG with transparency preserved
# Convert specific files with custom output names
python3 png_to_ovg.py my_clock_face.png img_off_clock_face_ovg_new.bin
python3 png_to_ovg.py my_shadow.png img_off_clock_shadow_ovg_new.bin
# Or convert with auto-generated names
python3 png_to_ovg.py img_off_clock_face_ovg_decoded.png
# Creates: img_off_clock_face_ovg_decoded.bin
- Backup original files first!
- Replace original
.bin
files with corresponding*_new.bin
files - Update firmware with modified files
opt/gresfiles/img_off_clock_face_ovg.bin
- Main clock faceopt/gresfiles/img_off_clock_shadow_ovg.bin
- Clock shadowopt/gresfiles/img_off_clock_spotlight_ovg.bin
- Clock spotlightopt/gresfiles/img_off_clock_hour_XX_ovg.bin
- Hour hand positionsopt/gresfiles/img_off_clock_minute_XX_ovg.bin
- Minute hand positionsopt/gresfiles/img_off_clock_second_XX_ovg.bin
- Second hand positions
clock_face_decoded.png
- Main clock face (286×286)img_off_clock_*_decoded.png
- Individual components
img_off_clock_face_ovg_new.bin
- Modified clock faceimg_off_clock_*_ovg_new.bin
- Modified components
Typical compression ratios achieved:
- Clock Face: ~2.8:1 compression
- Clock Shadow: ~10.8:1 compression
- Clock Hands: ~33-41:1 compression
- Spotlight: ~11.3:1 compression
- Format: RGBA (32-bit with alpha channel)
- Typical Size: 286×286 pixels for main components
- Color Depth: 8 bits per channel (R, G, B, A)
- Byte Order: Standard RGBA pixel ordering
pip3 install --user Pillow
Ensure the opt/gresfiles/
directory exists with original OVG files.
The converter automatically calculates optimal dimensions and detects file format. If dimensions appear incorrect:
- Check format detection: The converter will show "Detected format: raw_rgba" or "Detected format: rle_ovg"
- Raw RGBA files: Should auto-detect to perfect or near-perfect squares
- RLE OVG files: May require manual dimension specification with
--width
and--height
- Use discovery mode:
--discover
to find correct dimensions visually
If the generated OVG file is significantly larger than the original:
- Check for unnecessary transparency in your PNG
- Ensure solid color areas are truly solid (no noise/gradients)
- The RLE compression works best with areas of repeated pixels
- Initial attempts tried standard image formats (BMP, RGB, etc.)
- Analyzed byte patterns showing 0xFF padding and RLE-like structures
- Found reference to NXP AN4339 PDF describing similar RLE format
- Reverse-engineered the exact command block structure
- Implemented both decoder and encoder with roundtrip testing
- Files start with 0xFF padding that should be skipped
- Command blocks use 7-bit pixel counts (1-128 pixels per command)
- RLE compression is very effective for clock graphics with large solid areas
- Alpha channel is preserved and properly handled
- NXP AN4339 Application Note - Describes similar RLE routine
- Reverse Engineering Stack Exchange - Initial format identification
This project is provided as-is for educational and personal use. Always backup original firmware before making modifications.