Chroma Subsampling Image Compression Pipeline (Scala & Chisel)
This repository implements a hardware-accelerated image compression pipeline in Scala and Chisel3, targeting FPGA/ASIC integration. The pipeline’s primary goal is to take a stream of RGB pixels, convert them to YCbCr, apply configurable chroma subsampling (e.g., 4:4:4 → 4:2:2 or 4:2:0), optionally perform further spatial downsampling and color quantization, then reconstruct the data back into packetized RGB bundles. All processing is done locally (no off-chip or host-side support), and subsampling/quantization parameters are parameterizable at runtime or generate-time.
-
RGB -> YCbCr Conversion
- Module:
RGB2YCbCr - Description:
- Converts each incoming 24-bit RGB pixel into three separate channels: Y (luma), Cb, and Cr (chroma).
- Uses the standard fixed-point equations:
Y = clamp( 77·R + 150·G + 29·B + 128 ) >> 8 Cb = clamp( -43·R - 85·G + 128·B + 128 ) >> 8 Cr = clamp( 128·R - 107·G - 21·B + 128 ) >> 8 - All multipliers and shifts are implemented in pure integer arithmetic; results are clamped to the 0–255 range.
- Module:
-
Adaptive Chroma Subsampling
- Module:
ChromaSubsampler - Description:
- Supports parameterizable subsampling ratios (e.g., 4:4:4 → 4:2:2, 4:2:0, etc.).
- Locally downsamples the Cb/Cr channels on a per-block or per-row basis (configurable at generate-time).
- Module:
-
Spatial Downsampling
- Module:
SpatialDownSampler - Description:
- Further reduces resolution of Y (and optionally Cb/Cr) channels via a configurable 2D kernel (e.g., average pooling).
- Kernel size (e.g., 2×2, 4×4) and stride can be adjusted through parameters.
- Fully implemented and validated through ScalaTest/ChiselTest.
- Module:
-
Color Quantization
- Module:
ColorQuantizer - Description:
- Applies bit-width reduction (e.g., 8 bits → 4 bits per component) or palette indexing to Y, Cb, and Cr.
- Intended to reduce memory footprint and downstream bandwidth.
- Module:
-
Pixel Bundling (& Packetization)
- Module:
PixelBundle - Description:
- Reassembles processed Y/Cb/Cr (or re-converted RGB) into a packetized bundle suitable for DMA or on-chip memory write-back.
- Packs multiple pixels (e.g., 4 or 8 at a time) into a wider bus (e.g., 64 bits or 128 bits) to maximize throughput.
- Module:
-
YCbCr → RGB Reconstruction
- Module:
YCbCrUtils(inYCbCr2RGB.scala) - Description:
- Inverts JPEG-style YCbCr back to RGB after processing (e.g., for visualization or verification).
- Uses the standard inverse formulas:
R = clamp((298·Y + 409·(Cr−128) + 128) >> 8) G = clamp((298·Y − 100·(Cb−128) − 208·(Cr−128) + 128) >> 8) B = clamp((298·Y + 516·(Cb−128) + 128) >> 8) - Ensures each channel (R, G, B) is clamped to the 0–255 range.
- Module:
-
Top-Level Integration
- Module:
ImageCompressorTop - Description:
- Orchestrates the full processing pipeline end to end:
- Read input RGB pixel stream (from memory or test bench).
- Convert RGB → YCbCr using
RGB2YCbCr. - Optionally subsample Cb/Cr via
ChromaSubsampler. - Optionally spatially downsample Y/Cb/Cr via
SpatialDownSampler. - Optionally quantize Y/Cb/Cr via
ColorQuantizer. - Re-packetize processed channels via
PixelBundle. - Reconstruct YCbCr → RGB (for visualization/verification) using
YCbCrUtils(inYCbCr2RGB.scala).
- Produces a compressed, processed pixel stream and, if desired, a reconstructed RGB image for comparison.
- All parameters (subsampling ratio, spatial kernel size, quantization levels, and pipeline order) are generate-time constants passed via the Scala/Chisel generator.
- Orchestrates the full processing pipeline end to end:
- Module:
-
Software-Reference Image Processor Model
- Module:
ImageProcessorModel(inImageProcessorModel.scala) - Description:
- Provides utilities to read/write PNG or JPEG images using the Scrimage library.
getImageParams(...)creates anImageProcessorParamsstructure (width, height, numPixelsPerCycle, default chroma parameters).getImagePixels(...)extracts a 2D ScalaSeq[Seq[Seq[Int]]](height×width×RGB) to serve as a software reference.writeImage(...)functions allow writing intermediate stage outputs (YCbCr, subsampled, quantized pixel arrays) back to disk as PNG.
- Module:
All generator entry points live in src/main/scala/top/ImageCompressorTopApp.scala. This object instantiates ImageCompressorTop with your chosen parameters. By default, it will emit a Chisel FIRRTL file and optionally run a small simulation that reads one of the test_images/ PNG files and prints out the compressed, subsampled pixel stream in the console.
To run the generator:
# From repository root
$ sbt "Test / runMain jpeg.ImageCompressionApp"Available command-line parameters (all optional, with defaults):
| Parameter | Description | Default / Example |
|---|---|---|
--input=<path> |
Input image file (inputPath), e.g.: test_images/in128x128.png. Internally, val inputPath = "<path>" and val imageName = new File(inputPath).getName.takeWhile(_!='.'). |
"test_images/in128x128.png" |
--chromaA=<1 | 2 | 4> |
Chroma param_a (horizontal sampling) (in “J:a:b”). 4 → 4:4:x (no horizontal Cb/Cr subsampling); 2 → 4:2:x; 1 → 4:1:x. |
4 |
--chromaB=<0 | param_a> |
Chroma param_b (vertical sampling) (in “J:a:b”). If param_b == param_a: no vertical subsampling (e.g., 4:4:4, 4:2:2, 4:1:1). If param_b == 0: subsample by 2 (e.g., 4:2:0). |
4 |
--quantY=<1–8> |
Quantization bits for Y channel (luma). Valid range: 1–8. | 8 |
--quantCb=<1–8> |
Quantization bits for Cb channel (chroma-blue). Valid range: 1–8. | 8 |
--quantCr=<1–8> |
Quantization bits for Cr channel (chroma-red). Valid range: 1–8. | 8 |
--spatial=<1 | 2 | 4 | 8> |
Spatial downsampling factor. 1 → no downsampling; 2 → 2×2 pooling; 4 → 4×4 pooling; 8 → 8×8 pooling. |
1 |
--op1=<ProcessingStep.Type> |
Pipeline step 1. Valid values: SpatialSampling, ColorQuantization, ChromaSubsampling. |
SpatialSampling |
--op2=<ProcessingStep.Type> |
Pipeline step 2. Valid values: SpatialSampling, ColorQuantization, ChromaSubsampling. |
ColorQuantization |
--op3=<ProcessingStep.Type> |
Pipeline step 3. Valid values: SpatialSampling, ColorQuantization, ChromaSubsampling. |
ChromaSubsampling |
Note: Internally, these map to: val selectedChromaParamA: Int = val selectedChromaParamB: Int = val yTargetQuantBits: Int = val cbTargetQuantBits: Int = val crTargetQuantBits: Int = val selectedSpatialFactor: Int = val op1_choice: ProcessingStep.Type = ProcessingStep. val op2_choice: ProcessingStep.Type = ProcessingStep. val op3_choice: ProcessingStep.Type = ProcessingStep.
Run the Test
sbt testTesting should run for Chroma Subsampling, Color Quantization, RGB2YCB, and Spatial Downsampling. Test images should produce for different chroma subsampling, color quantizer, spatial downsampling parameters.
Chroma-Subsampling-Image-Compressor/
├── modules/
│ ├── RGB2YCbCr [✓] Complete
│ ├── ChromaSubsampler [✓] Complete
│ ├── SpatialDownSampler [✓] Complete
│ ├── ColorQuantizer [✓] Complete
│ └── PixelBundle [✓] Complete
├── top/
│ ├── ImageCompressorTop [✓] Complete
│ │ └── Current: Wires submodules.
│ └── ImageCompressorTopApp [✓] Complete
│ └── CLI & generator for FIRRTL/Verilog + optional PNG dumps.
└── test/
├── RGB2YCbCrTester [✓] Passing
├── SpatialDownSamplerTester [✓] Passing
├── ChromaSubsamplerTester [✓] Passing
└── ColorQuantizerTester [✓] Passing
- Scala 2.13+
- sbt 1.5+
- Chisel 3.5.x (or later)
- ChiselTest (for simulation)
- Scrimage 4.1.1 (for image and color functions)