Skip to content

Commit 13f17d8

Browse files
[todo] jpegdec
1 parent 3cbb89c commit 13f17d8

File tree

6 files changed

+407
-1
lines changed

6 files changed

+407
-1
lines changed

src/eloquent_esp32cam/camera/pixformat.h

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,16 @@ namespace Eloquent {
2222
}
2323

2424
/**
25-
*
25+
* Alias for grayscale
2626
*/
2727
void gray() {
28+
grayscale();
29+
}
30+
31+
/**
32+
*
33+
*/
34+
void grayscale() {
2835
format = PIXFORMAT_GRAYSCALE;
2936
}
3037

@@ -83,6 +90,14 @@ namespace Eloquent {
8390
inline bool is(pixformat_t fmt) {
8491
return format == fmt;
8592
}
93+
94+
/**
95+
* Get bytes per pixel
96+
* @return
97+
*/
98+
inline uint8_t bpp() {
99+
return isGray() ? 1 : (isRGB888() ? 3 : 2);
100+
}
86101
};
87102
}
88103
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
#ifndef ELOQUENTESP32CAM_JPEG_CALLBACKS
2+
#define ELOQUENTESP32CAM_JPEG_CALLBACKS
3+
4+
#include <functional>
5+
6+
namespace Eloquent {
7+
namespace Esp32cam {
8+
namespace JPEG {
9+
/**
10+
* Callbacks for JPEGDECWrapper
11+
*/
12+
template<typename Callback, uint8_t numCallbacks>
13+
class Callbacks {
14+
public:
15+
16+
/**
17+
* Constructor
18+
*/
19+
Callbacks() : _i(0) {
20+
21+
}
22+
23+
/**
24+
* Add callback
25+
* @param callback
26+
* @return
27+
*/
28+
bool add(Callback callback) {
29+
if (_i == numCallbacks)
30+
return false;
31+
32+
_callbacks[_i++] = callback;
33+
return true;
34+
}
35+
36+
/**
37+
* Loop over callbacks
38+
* @param fn
39+
*/
40+
template<typename ForEach>
41+
void forEach(ForEach fn) {
42+
for (uint8_t i = 0; i < _i; i++)
43+
fn(_callbacks[i]);
44+
}
45+
46+
protected:
47+
uint8_t _i;
48+
Callback _callbacks[numCallbacks];
49+
};
50+
}
51+
}
52+
}
53+
54+
#endif
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#include <Arduino.h>
2+
#include <JPEGDEC.h>
3+
#include "JPEGDECWrapper.h"
4+
5+
using Eloquent::Esp32cam::JPEG::JPEGDECWrapper;
6+
7+
/**
8+
*
9+
* @param mcu
10+
* @return
11+
*/
12+
int __handleMCU__(jpeg_draw_tag *mcu) {
13+
JPEGDECWrapper *jpeg = (JPEGDECWrapper*) mcu->pUser;
14+
const auto crop = jpeg->crop;
15+
const uint8_t bpp = mcu->iBpp / 8;
16+
17+
for (size_t i = 0; i < mcu->iHeight; i++) {
18+
const size_t inputOffset = i * mcu->iWidth * bpp;
19+
20+
if (!jpeg->addMCU(mcu->x, mcu->y + i, bpp, ((uint8_t*) mcu->pPixels) + inputOffset, mcu->iWidthUsed))
21+
return 0;
22+
}
23+
24+
return 1;
25+
}
Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
#ifdef __JPEGDEC__
2+
#ifndef ELOQUENTESP32CAM_JPEGDECWRAPPER_H
3+
#define ELOQUENTESP32CAM_JPEGDECWRAPPER_H
4+
5+
#include <JPEGDEC.h>
6+
#include "../mem.h"
7+
#include "../camera/camera.h"
8+
#include "../extra/exception.h"
9+
#include "../extra/time/benchmark.h"
10+
11+
using eloq::camera;
12+
using Eloquent::Error::Exception;
13+
using Eloquent::Extra::Time::Benchmark;
14+
15+
/**
16+
* Sadly we need to declare a global function
17+
* to handle decoding
18+
*/
19+
int __handleMCU__(jpeg_draw_tag *mcu);
20+
21+
namespace Eloquent {
22+
namespace Esp32cam {
23+
namespace JPEG {
24+
class JPEGDECWrapper {
25+
public:
26+
uint8_t *pixels;
27+
uint16_t outputWidth;
28+
uint16_t outputHeight;
29+
JPEGDEC jpeg;
30+
Exception exception;
31+
Benchmark benchmark;
32+
struct {
33+
int16_t x;
34+
int16_t y;
35+
uint16_t width;
36+
uint16_t height;
37+
} crop;
38+
39+
/**
40+
* Constructor
41+
*/
42+
JPEGDECWrapper() :
43+
pixels(NULL),
44+
exception("JPEGDEC"),
45+
_allocatedSize(0),
46+
_options(0) {
47+
crop.x = 0;
48+
crop.y = 0;
49+
crop.width = 0;
50+
crop.height = 0;
51+
}
52+
53+
/**
54+
* Todo: not working
55+
*/
56+
void onlyLuma() {
57+
_options |= JPEG_LUMA_ONLY;
58+
}
59+
60+
/**
61+
* Decode at 1/2 resolution
62+
*/
63+
inline void half() {
64+
_options |= JPEG_SCALE_HALF;
65+
}
66+
67+
/**
68+
* Decode at 1/4 resolution
69+
*/
70+
inline void quarter() {
71+
_options |= JPEG_SCALE_QUARTER;
72+
}
73+
74+
/**
75+
* Decode at 1/8 resolution
76+
*/
77+
inline void eighth() {
78+
_options |= JPEG_SCALE_EIGHTH;
79+
}
80+
81+
/**
82+
* Set divisor
83+
*/
84+
void divide(uint8_t divisor) {
85+
switch (divisor) {
86+
case 8:
87+
eighth();
88+
return;
89+
case 4:
90+
quarter();
91+
return;
92+
case 2:
93+
half();
94+
return;
95+
}
96+
}
97+
98+
/**
99+
*
100+
*/
101+
Exception& decode() {
102+
if (!camera.hasFrame())
103+
return exception.set("No frame available");
104+
105+
if (!jpeg.openRAM(camera.frame->buf, camera.frame->len, __handleMCU__))
106+
return exception.set("Can't init decoder");
107+
108+
const uint16_t inputWidth = camera.frame->width / divisor();
109+
const uint16_t inputHeight = camera.frame->height / divisor();
110+
111+
outputWidth = crop.width ? crop.width : inputWidth;
112+
outputHeight = crop.height ? crop.height : inputHeight;
113+
alloc(outputWidth * outputHeight * bpp());
114+
jpeg.setUserPointer((void*) this);
115+
116+
// allow for negative crop x/y
117+
while (crop.x < 0)
118+
crop.x += inputWidth;
119+
120+
while (crop.y < 0)
121+
crop.y += inputHeight;
122+
123+
if (pixels == NULL)
124+
return exception.set("Can't allocate decode buffer");
125+
126+
benchmark.benchmark([this]() {
127+
if (!jpeg.decode(0, 0, _options)) {
128+
exception.set("Decode failed");
129+
}
130+
else {
131+
exception.clear();
132+
}
133+
134+
jpeg.close();
135+
});
136+
137+
return exception;
138+
}
139+
140+
/**
141+
* Get dimension divisor
142+
*/
143+
uint8_t divisor() {
144+
return _options & JPEG_SCALE_EIGHTH ? 8 :
145+
(_options & JPEG_SCALE_QUARTER ? 4 :
146+
(_options & JPEG_SCALE_HALF ? 2 : 1));
147+
}
148+
149+
/**
150+
* Get number of decoded pixels
151+
*/
152+
inline size_t count() {
153+
return outputWidth * outputHeight;
154+
}
155+
156+
/**
157+
* Get number of bytes
158+
*/
159+
inline size_t size() {
160+
return count() * bpp();
161+
}
162+
163+
/**
164+
* Get bytes per pixel (grayscale = 1, rgb = 2)
165+
*/
166+
inline uint8_t bpp() {
167+
return _options & JPEG_LUMA_ONLY ? 1 : 2;
168+
}
169+
170+
/**
171+
* Get pixels as uint16_t* buffer
172+
*/
173+
uint16_t* u16() {
174+
return (uint16_t*) pixels;
175+
}
176+
177+
/**
178+
* Swap byte order
179+
*/
180+
void swapBytes() {
181+
const size_t numBytes = size();
182+
183+
for (size_t i = 0; i < numBytes; i += 2) {
184+
const uint8_t a = pixels[i];
185+
const uint8_t b = pixels[i + 1];
186+
pixels[i] = b;
187+
pixels[i + 1] = a;
188+
}
189+
}
190+
191+
/**
192+
* For internal use only.
193+
*/
194+
bool addMCU(uint16_t x, uint16_t y, uint8_t bpp, uint8_t *mcuPixels, uint16_t width) {
195+
// easy case: no crop
196+
if (crop.x == 0 && crop.y == 0 && crop.width == 0 && crop.height == 0) {
197+
// todo: ephimeral decoding
198+
199+
const size_t outputOffset = y * outputWidth + x;
200+
201+
memcpy(pixels + outputOffset * bpp, mcuPixels, width * bpp);
202+
return true;
203+
}
204+
205+
// only get pixels inside crop area
206+
// early stopping
207+
if (crop.height > 0 && y >= crop.y + crop.height)
208+
return false;
209+
210+
if (crop.y > 0 && y < crop.y)
211+
return true;
212+
213+
if (x + width < crop.x)
214+
return true;
215+
216+
if (crop.x > x) {
217+
const uint16_t dx = crop.x - x;
218+
mcuPixels += dx * bpp;
219+
width -= dx;
220+
x = crop.x;
221+
}
222+
223+
if (crop.width > 0 && crop.width < width)
224+
width = crop.width;
225+
226+
// todo: ephimeral decoding
227+
228+
const size_t outputOffset = (y - crop.y) * outputWidth + (x - crop.x);
229+
230+
memcpy(pixels + outputOffset * bpp, mcuPixels, width * bpp);
231+
return true;
232+
}
233+
234+
protected:
235+
size_t _allocatedSize;
236+
int _options;
237+
238+
/**
239+
* Allocate memory for decoded pixels
240+
*/
241+
void alloc(size_t size) {
242+
if (pixels == NULL || _allocatedSize == 0) {
243+
ESP_LOGD("JPEGDEG", "Allocating %d bytes for decoded pixels", size);
244+
pixels = eloq::alloc<uint8_t>(size);
245+
_allocatedSize = size;
246+
}
247+
else if (pixels != NULL && size != _allocatedSize) {
248+
ESP_LOGD("JPEGDEG", "(Re)Allocating %d bytes for decoded pixels", size);
249+
pixels = eloq::realloc<uint8_t>(pixels, size);
250+
_allocatedSize = size;
251+
}
252+
}
253+
};
254+
}
255+
}
256+
}
257+
258+
namespace eloq {
259+
static Eloquent::Esp32cam::JPEG::JPEGDECWrapper jpegdec;
260+
}
261+
262+
#endif //ELOQUENTESP32CAM_JPEGDECWRAPPER_H
263+
#endif //__JPEGDEC__

src/eloquent_esp32cam/jpegdec.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#pragma once
2+
3+
#include "./jpeg/JPEGDECWrapper.h"

0 commit comments

Comments
 (0)