esp32/sdcard

Example of the component embedblocks/jpeg-roi-decoder v0.3.0
# jpeg_roi_decoder — SD Card Example

Decodes a JPEG from an SD card and writes the raw RGB565 output to a file on the same card.
Demonstrates the high-level `jpeg_decoder_decode_view` API with a streaming `file_read_cb`
source and row-by-row chunk output — no full-frame buffer required.

---

## What it does

1. Mounts the SD card
2. Opens `testimg.jpg` from the SD card root
3. Creates a `jpeg_reader_t` backed by a `file_read_cb` — the decoder streams bytes directly from the file with no intermediate copy
4. Decodes to fit a 320×240 LCD viewport (centered, auto scale)
5. Streams each decoded row directly to `rgb565.raw` via `fwrite`
6. Closes both files when done — inside `on_done`, which is the only safe point

No full-frame buffer is needed — the decoder writes one row at a time to disk.

---

## Hardware required

| Component | Notes |
|---|---|
| ESP32 | Any variant |
| SD card module | SPI mode |
| SD card | FAT32 formatted |

---

## SD card contents

Place these files on the SD card before running:

```
/sdcard/testimg.jpg     ← input JPEG (any size)
```

After running, the SD card will contain:

```
/sdcard/rgb565.raw      ← raw RGB565 output, lcd_w × lcd_h × 2 bytes
```

---

## Configuration

Edit the defines at the top of `main.c`:

```c
#define LCD_W  320    /* output viewport width  in pixels */
#define LCD_H  240    /* output viewport height in pixels */
```

The decoder centers the JPEG in the viewport and picks the best scale automatically.
To override scale:

```c
view.scale = JPEG_SCALE_1_2;   /* 1/1, 1/2, 1/4, 1/8 */
```

To pan from center (in LCD pixels):

```c
view.pan_x =  50;   /* shift viewport 50px right */
view.pan_y = -30;   /* shift viewport 30px up    */
```

---

## Build and flash

```bash
idf.py set-target esp32
idf.py build flash monitor
```

---

## Expected output

```
I (399) JD_CORE: ROI(scaled): left=0 top=0 right=319 bottom=239
I (439) JPEG_UART: Decoded byte count 640
I (449) JPEG_UART: Decoded byte count 640
...                                    ← 240 lines, one per row
I (2100) JPEG_UART: Streaming finished
```

Total bytes written: `320 × 240 × 2 = 153 600 bytes`

---

## Verifying the output on PC

Use Python to convert `rgb565.raw` to a viewable PNG:

```python
import numpy as np
import cv2

W, H = 320, 240
raw  = np.fromfile("rgb565.raw", dtype=np.uint16).reshape((H, W))
r = ((raw >> 11) & 0x1F) << 3
g = ((raw >>  5) & 0x3F) << 2
b = ( raw        & 0x1F) << 3
cv2.imwrite("output.png", np.dstack((b, g, r)).astype(np.uint8))
print("Saved output.png")
```

---

## Important notes

**File lifetime — do not close files in `app_main`.** `jpeg_decoder_decode_view` returns
immediately after queuing the job. The worker task is still streaming bytes from `fin` via
`fread`. Calling `fclose(fin)` in `app_main` after `decode_view` returns causes a spinlock
crash. Both `fin` and `fout` are closed inside `on_done`, which fires only after the last
`fread` and `fwrite` have completed.

```c
/* ✅ correct — on_done owns both files */
static void on_done(const jpeg_done_event_t *evt) {
    decode_files_t *f = evt->user_data;
    fclose(f->fout);
    fclose(f->fin);
}

/* ❌ crash — worker still reading fin */
jpeg_decoder_decode_view(...);
fclose(fin);
```

**`fwrite` return value** — `fwrite(ptr, size, 1, fp)` returns the number of items written
(1 on success, not bytes). The chunk callback checks `ret != 1` to detect write errors.

**Logging during decode** — `ESP_LOGI` is kept inside `on_chunk` for demonstration purposes.
In a production streaming application where output goes to UART, remove all logging from
callbacks to avoid corrupting the binary stream.

**SD card SPI pins** — configure in `sd_mount_init()` to match your hardware. Default pins
follow the ESP32 SPI2 (HSPI) mapping.

---

## File structure

```
examples/sdcard/
├── main/
│   ├── CMakeLists.txt
│   └── main.c
├── CMakeLists.txt
└── README.md
```

---

## License

MIT License — see root LICENSE file.

To create a project from this example, run:

idf.py create-project-from-example "embedblocks/jpeg-roi-decoder=0.3.0:esp32/sdcard"

or download archive (~159.18 KB)