TinyGPU
Loading...
Searching...
No Matches
JPEGParser.h
Go to the documentation of this file.
1#pragma once
2
3#include <stddef.h>
4#include <stdint.h>
5
6#include <TinyJPEGDecoder.h> // https://github.com/pschatzmann/TinyJPEG
7
9#include "TinyGPU/Surface/ISurface.h"
10
11namespace tinygpu {
12
13/**
14 * @brief Decodes baseline JPEG images into a TinyGPU ISurface, using
15 * pschatzmann/TinyJPEG (https://github.com/pschatzmann/TinyJPEG) as the
16 * underlying decoder.
17 *
18 * TinyJPEG is a separate, optional Arduino/CMake library - install it
19 * alongside TinyGPU and `#include <TinyGPU/IO/JPEGParser.h>` explicitly
20 * (this header is not pulled in by TinyGPU.h) to opt in to JPEG support.
21 *
22 * TinyJPEG streams decoded pixels one MCU block at a time rather than
23 * requiring memory for the whole decoded frame, so decode() resizes the
24 * target surface once (from the JPEG's own header) and then just fans
25 * each block out to setPixel() as it arrives - no intermediate frame
26 * buffer beyond the target surface itself.
27 */
28template <typename RGB_T = RGB565>
30 public:
31 explicit JPEGParser(ISurface<RGB_T>& target) : target_(target) {
32 // TinyJPEG's default output is packed RGB565 in conventional (native)
33 // bit order; RGB565::RGB565(uint16_t) expects the byte-swapped wire
34 // order this library stores instead (see RGB565.h) - setSwapBytes(true)
35 // makes the decoder do that swap for us so onBlock() can construct
36 // RGB_T directly from each output pixel. Correct for every panel this
37 // has been verified against so far (including TinyGPU's own desktop
38 // SDL backend) - see setSwapBytes() below if a specific panel needs
39 // the opposite.
40 decoder_.setSwapBytes(true);
41 decoder_.setCallback(&JPEGParser::onBlock);
42 }
43
44 /// Overrides the byte-swap behavior set in the constructor (see its
45 /// comment) - some panels/wiring need the opposite of the true default
46 /// that's otherwise correct.
47 void setSwapBytes(bool swap) { decoder_.setSwapBytes(swap); }
48
49 /// Sets the output reduction factor: 1 (full size), 2, 4, or 8.
50 void setScale(uint8_t scale) { decoder_.setJpgScale(scale); }
51
52 /// Decodes a JPEG held entirely in memory into the target surface at (0, 0).
53 /// Resizes the target surface to the JPEG's own dimensions first.
54 bool decode(const uint8_t* data, size_t size) {
55 uint16_t w = 0, h = 0;
56 if (decoder_.getJpgSize(&w, &h, data, size) != JDR_OK) {
57 return setError("Could not read JPEG header");
58 }
59 if (!target_.resize(w, h)) {
60 return setError("Could not resize target surface");
61 }
62 decoder_.setUserData(this);
63 return checkResult(decoder_.drawJpg(0, 0, data, size));
64 }
65
66 /// Decodes a JPEG read from any file-like object (Arduino's fs::File,
67 /// SD's File, or anything else exposing available()/read()/position()/
68 /// seek() - see TinyJPEG's docs/decoding.md) into the target surface at
69 /// (0, 0). Resizes the target surface to the JPEG's own dimensions
70 /// first. `file` is read from its current position and left wherever
71 /// the decode stopped; this method rewinds to that starting position
72 /// only between reading the header and decoding the pixels.
73 template <typename FileT>
74 bool decode(FileT& file) {
75 const size_t startPos = file.position();
76 uint16_t w = 0, h = 0;
77 if (decoder_.getJpgSize(&w, &h, file) != JDR_OK) {
78 return setError("Could not read JPEG header");
79 }
80 if (!target_.resize(w, h)) {
81 return setError("Could not resize target surface");
82 }
83 file.seek(startPos);
84 decoder_.setUserData(this);
85 return checkResult(decoder_.drawJpg(0, 0, file));
86 }
87
88 /// Returns the latest error message, if any.
89 const char* errorMessage() const { return errorMessage_; }
90
91 protected:
92 ISurface<RGB_T>& target_;
94 const char* errorMessage_ = nullptr;
95
96 bool setError(const char* message) {
97 errorMessage_ = message;
98 return false;
99 }
100
101 bool checkResult(JRESULT result) {
102 if (result == JDR_OK) {
103 errorMessage_ = nullptr;
104 return true;
105 }
106 return setError("JPEG decode failed");
107 }
108
109 static bool onBlock(tinyjpeg::TinyJPEGDecoder& decoder, int16_t x, int16_t y,
110 uint16_t w, uint16_t h, uint16_t* data) {
111 auto* self = static_cast<JPEGParser*>(decoder.getUserData());
112 for (uint16_t row = 0; row < h; ++row) {
113 const size_t py = static_cast<size_t>(y) + row;
114 for (uint16_t col = 0; col < w; ++col) {
115 const size_t px = static_cast<size_t>(x) + col;
116 // Trailing MCU blocks commonly overhang the image's right/bottom
117 // edge (JPEG blocks are 8x8/16x16-aligned; the image itself
118 // usually isn't) - drop whatever falls outside the target.
119 if (self->target_.contains(px, py)) {
120 self->target_.setPixel(px, py, RGB_T(data[(row * w) + col]));
121 }
122 }
123 }
124 return true;
125 }
126};
127
128} // namespace tinygpu
Decodes baseline JPEG images into a TinyGPU ISurface, using pschatzmann/TinyJPEG (https://github....
Definition: JPEGParser.h:29
static bool onBlock(tinyjpeg::TinyJPEGDecoder &decoder, int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t *data)
Definition: JPEGParser.h:109
bool decode(FileT &file)
Definition: JPEGParser.h:74
void setScale(uint8_t scale)
Sets the output reduction factor: 1 (full size), 2, 4, or 8.
Definition: JPEGParser.h:50
bool setError(const char *message)
Definition: JPEGParser.h:96
bool decode(const uint8_t *data, size_t size)
Definition: JPEGParser.h:54
JPEGParser(ISurface< RGB_T > &target)
Definition: JPEGParser.h:31
const char * errorMessage_
Definition: JPEGParser.h:94
ISurface< RGB_T > & target_
Definition: JPEGParser.h:92
bool checkResult(JRESULT result)
Definition: JPEGParser.h:101
const char * errorMessage() const
Returns the latest error message, if any.
Definition: JPEGParser.h:89
void setSwapBytes(bool swap)
Definition: JPEGParser.h:47
tinyjpeg::TinyJPEGDecoder decoder_
Definition: JPEGParser.h:93
RGB color stored in 16-bit RGB565 format, byte-swapped from the conventional bit layout.
Definition: RGB565.h:24
Definition: DSIBusESP32.h:19