Skip to content

Commit 4ffff0b

Browse files
authored
Merge pull request #10 from CodyTolene/ct/feature-3-add-toggle-able-dithering-styles
Add toggle-able dithering styles by pressing OK button.
2 parents 753cc99 + 43ffa6c commit 4ffff0b

File tree

5 files changed

+111
-53
lines changed

5 files changed

+111
-53
lines changed

src-fap/scenes/camera_suite_scene_menu.c

+3-3
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,14 @@ void camera_suite_scene_menu_on_enter(void* context) {
2121

2222
submenu_add_item(
2323
app->submenu,
24-
"Open Camera", // Style: Atkinson
24+
"Open Camera",
2525
SubmenuIndexSceneStyle1,
2626
camera_suite_scene_menu_submenu_callback,
2727
app);
28-
// TODO: Uncomment when style 2 is implemented
28+
// Staged view for the future.
2929
// submenu_add_item(
3030
// app->submenu,
31-
// "Style: Floyd-Steinberg",
31+
// "Test",
3232
// SubmenuIndexSceneStyle2,
3333
// camera_suite_scene_menu_submenu_callback,
3434
// app);

src-fap/views/camera_suite_view_guide.c

+2-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ void camera_suite_view_guide_draw(Canvas* canvas, CameraSuiteViewGuideModel* mod
3636
canvas_draw_str_aligned(canvas, 0, 22, AlignLeft, AlignTop, "Right = Toggle Dithering");
3737
canvas_draw_str_aligned(canvas, 0, 32, AlignLeft, AlignTop, "Up = Contrast Up");
3838
canvas_draw_str_aligned(canvas, 0, 42, AlignLeft, AlignTop, "Down = Contrast Down");
39-
canvas_draw_str_aligned(canvas, 0, 52, AlignLeft, AlignTop, "Center = Take Picture (TODO)");
39+
// TODO: Possibly update to take picture instead.
40+
canvas_draw_str_aligned(canvas, 0, 52, AlignLeft, AlignTop, "Center = Toggle Dither Type");
4041
}
4142

4243
static void camera_suite_view_guide_model_init(CameraSuiteViewGuideModel* const model) {

src-fap/views/camera_suite_view_style_1.c

+45-1
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,15 @@
44
#include <input/input.h>
55
#include <gui/elements.h>
66
#include <dolphin/dolphin.h>
7+
#include "../helpers/camera_suite_haptic.h"
8+
#include "../helpers/camera_suite_speaker.h"
9+
#include "../helpers/camera_suite_led.h"
710

811
static CameraSuiteViewStyle1* current_instance = NULL;
12+
// Dithering type:
13+
// 0 = Floyd Steinberg (default)
14+
// 1 = Atkinson
15+
static int current_dithering = 0;
916

1017
struct CameraSuiteViewStyle1 {
1118
CameraSuiteViewStyle1Callback callback;
@@ -110,6 +117,22 @@ static bool camera_suite_view_style_1_input(InputEvent* event, void* context) {
110117
furi_assert(context);
111118
CameraSuiteViewStyle1* instance = context;
112119
if(event->type == InputTypeRelease) {
120+
switch(event->key) {
121+
default: // Stop all sounds, reset the LED.
122+
with_view_model(
123+
instance->view,
124+
UartDumpModel * model,
125+
{
126+
UNUSED(model);
127+
camera_suite_play_bad_bump(instance->context);
128+
camera_suite_stop_all_sound(instance->context);
129+
camera_suite_led_set_rgb(instance->context, 0, 0, 0);
130+
},
131+
true);
132+
break;
133+
}
134+
// Send `data` to the ESP32-CAM
135+
} else if(event->type == InputTypePress) {
113136
uint8_t data[1];
114137
switch(event->key) {
115138
case InputKeyBack:
@@ -133,6 +156,9 @@ static bool camera_suite_view_style_1_input(InputEvent* event, void* context) {
133156
UartDumpModel * model,
134157
{
135158
UNUSED(model);
159+
camera_suite_play_happy_bump(instance->context);
160+
camera_suite_play_input_sound(instance->context);
161+
camera_suite_led_set_rgb(instance->context, 0, 0, 255);
136162
instance->callback(CameraSuiteCustomEventSceneStyle1Left, instance->context);
137163
},
138164
true);
@@ -145,6 +171,9 @@ static bool camera_suite_view_style_1_input(InputEvent* event, void* context) {
145171
UartDumpModel * model,
146172
{
147173
UNUSED(model);
174+
camera_suite_play_happy_bump(instance->context);
175+
camera_suite_play_input_sound(instance->context);
176+
camera_suite_led_set_rgb(instance->context, 0, 0, 255);
148177
instance->callback(CameraSuiteCustomEventSceneStyle1Right, instance->context);
149178
},
150179
true);
@@ -157,6 +186,9 @@ static bool camera_suite_view_style_1_input(InputEvent* event, void* context) {
157186
UartDumpModel * model,
158187
{
159188
UNUSED(model);
189+
camera_suite_play_happy_bump(instance->context);
190+
camera_suite_play_input_sound(instance->context);
191+
camera_suite_led_set_rgb(instance->context, 0, 0, 255);
160192
instance->callback(CameraSuiteCustomEventSceneStyle1Up, instance->context);
161193
},
162194
true);
@@ -169,17 +201,29 @@ static bool camera_suite_view_style_1_input(InputEvent* event, void* context) {
169201
UartDumpModel * model,
170202
{
171203
UNUSED(model);
204+
camera_suite_play_happy_bump(instance->context);
205+
camera_suite_play_input_sound(instance->context);
206+
camera_suite_led_set_rgb(instance->context, 0, 0, 255);
172207
instance->callback(CameraSuiteCustomEventSceneStyle1Down, instance->context);
173208
},
174209
true);
175210
break;
176211
case InputKeyOk:
177-
// TODO: Take picture.
212+
if(current_dithering == 0) {
213+
data[0] = 'd'; // Update to Floyd Steinberg dithering.
214+
current_dithering = 1;
215+
} else {
216+
data[0] = 'D'; // Update to Atkinson dithering.
217+
current_dithering = 0;
218+
}
178219
with_view_model(
179220
instance->view,
180221
UartDumpModel * model,
181222
{
182223
UNUSED(model);
224+
camera_suite_play_happy_bump(instance->context);
225+
camera_suite_play_input_sound(instance->context);
226+
camera_suite_led_set_rgb(instance->context, 0, 0, 255);
183227
instance->callback(CameraSuiteCustomEventSceneStyle1Ok, instance->context);
184228
},
185229
true);

src-fap/views/camera_suite_view_style_1.h

-25
Original file line numberDiff line numberDiff line change
@@ -25,31 +25,6 @@
2525
#define RING_BUFFER_LENGTH (ROW_BUFFER_LENGTH + 3) // ROW_BUFFER_LENGTH + Header => 16 + 3 = 19
2626
#define LAST_ROW_INDEX (FRAME_BUFFER_LENGTH - ROW_BUFFER_LENGTH) // 1024 - 16 = 1008
2727

28-
// const uint8_t _I_DolphinCommon_56x48_0[] = {
29-
// 0x01, 0x00, 0xdf, 0x00, 0x00, 0x1f, 0xfe, 0x0e, 0x05, 0x3f, 0x04, 0x06, 0x78, 0x06, 0x30, 0x20,
30-
// 0xf8, 0x00, 0xc6, 0x12, 0x1c, 0x04, 0x0c, 0x0a, 0x38, 0x08, 0x08, 0x0c, 0x60, 0xc0, 0x21, 0xe0,
31-
// 0x04, 0x0a, 0x18, 0x02, 0x1b, 0x00, 0x18, 0xa3, 0x00, 0x21, 0x90, 0x01, 0x8a, 0x20, 0x02, 0x19,
32-
// 0x80, 0x18, 0x80, 0x64, 0x09, 0x20, 0x89, 0x81, 0x8c, 0x3e, 0x41, 0xe2, 0x80, 0x50, 0x00, 0x43,
33-
// 0x08, 0x01, 0x0c, 0xfc, 0x68, 0x40, 0x61, 0xc0, 0x50, 0x30, 0x00, 0x63, 0xa0, 0x7f, 0x80, 0xc4,
34-
// 0x41, 0x19, 0x07, 0xff, 0x02, 0x06, 0x18, 0x24, 0x03, 0x41, 0xf3, 0x2b, 0x10, 0x19, 0x38, 0x10,
35-
// 0x30, 0x31, 0x7f, 0xe0, 0x34, 0x08, 0x30, 0x19, 0x60, 0x80, 0x65, 0x86, 0x0a, 0x4c, 0x0c, 0x30,
36-
// 0x81, 0xb9, 0x41, 0xa0, 0x54, 0x08, 0xc7, 0xe2, 0x06, 0x8a, 0x18, 0x25, 0x02, 0x21, 0x0f, 0x19,
37-
// 0x88, 0xd8, 0x6e, 0x1b, 0x01, 0xd1, 0x1b, 0x86, 0x39, 0x66, 0x3a, 0xa4, 0x1a, 0x50, 0x06, 0x48,
38-
// 0x18, 0x18, 0xd0, 0x03, 0x01, 0x41, 0x98, 0xcc, 0x60, 0x39, 0x01, 0x49, 0x2d, 0x06, 0x03, 0x50,
39-
// 0xf8, 0x40, 0x3e, 0x02, 0xc1, 0x82, 0x86, 0xc7, 0xfe, 0x0f, 0x28, 0x2c, 0x91, 0xd2, 0x90, 0x9a,
40-
// 0x18, 0x19, 0x3e, 0x6d, 0x73, 0x12, 0x16, 0x00, 0x32, 0x49, 0x72, 0xc0, 0x7e, 0x5d, 0x44, 0xba,
41-
// 0x2c, 0x08, 0xa4, 0xc8, 0x82, 0x06, 0x17, 0xe0, 0x81, 0x90, 0x2a, 0x40, 0x61, 0xe1, 0xa2, 0x44,
42-
// 0x0c, 0x76, 0x2b, 0xe8, 0x89, 0x26, 0x43, 0x83, 0x31, 0x8c, 0x78, 0x0c, 0xb0, 0x48, 0x10, 0x1a,
43-
// 0xe0, 0x00, 0x63,
44-
// };
45-
// const uint8_t* const _I_DolphinCommon_56x48[] = {_I_DolphinCommon_56x48_0};
46-
// const Icon I_DolphinCommon_56x48 = {
47-
// .width = 56,
48-
// .height = 48,
49-
// .frame_count = 1,
50-
// .frame_rate = 0,
51-
// .frames = _I_DolphinCommon_56x48};
52-
5328
typedef struct UartDumpModel UartDumpModel;
5429

5530
struct UartDumpModel {

src-firmware/esp32_cam_uart_stream/esp32_cam_uart_stream.ino

+61-23
Original file line numberDiff line numberDiff line change
@@ -26,18 +26,22 @@ camera_config_t config;
2626
void handleSerialInput();
2727
void initializeCamera();
2828
void processImage(camera_fb_t* fb);
29-
void ditherImage(camera_fb_t* fb);
29+
void ditherImage(camera_fb_t* fb, int dt);
3030
bool isDarkBit(uint8_t bit);
3131

3232
// Serial input flags
3333
bool disableDithering = false;
3434
bool invert = false;
3535
bool rotated = false;
3636
bool stopStream = false;
37+
// Dithering type:
38+
// 0 = Floyd Steinberg (default)
39+
// 1 = Atkinson
40+
int dtType = 0;
41+
3742

3843
void setup() {
3944
Serial.begin(230400);
40-
4145
initializeCamera();
4246
}
4347

@@ -48,6 +52,7 @@ void loop() {
4852
return;
4953
}
5054

55+
// Frame buffer.
5156
camera_fb_t* fb = esp_camera_fb_get();
5257

5358
if (!fb) {
@@ -85,11 +90,11 @@ void handleSerialInput() {
8590
case 'c': // Remove contrast.
8691
cameraSensor->set_contrast(cameraSensor, cameraSensor->status.contrast - 1);
8792
break;
88-
case 'D': // Enable dithering.
89-
disableDithering = false;
93+
case 'D': // Use Floyd Steinberg dithering.
94+
dtType = 0;
9095
break;
91-
case 'd': // Disable dithering.
92-
disableDithering = true;
96+
case 'd': // Use Atkinson dithering.
97+
dtType = 1;
9398
break;
9499
case 'M': // Toggle Mirror
95100
cameraSensor->set_hmirror(cameraSensor, !cameraSensor->status.hmirror);
@@ -149,7 +154,7 @@ void initializeCamera() {
149154

150155
void processImage(camera_fb_t* frameBuffer) {
151156
if (!disableDithering) {
152-
ditherImage(frameBuffer);
157+
ditherImage(frameBuffer, dtType);
153158
}
154159

155160
uint8_t flipper_y = 0;
@@ -186,22 +191,55 @@ void processImage(camera_fb_t* frameBuffer) {
186191
}
187192

188193
// Dither image.
189-
void ditherImage(camera_fb_t* fb) {
190-
for (int y = 0; y < fb->height - 1; ++y) {
191-
for (int x = 1; x < fb->width - 1; ++x) {
192-
int current = y * fb->width + x;
193-
// Convert to black or white
194-
uint8_t oldpixel = fb->buf[current];
195-
uint8_t newpixel = oldpixel >= 128 ? 255 : 0;
196-
fb->buf[current] = newpixel;
197-
// Compute quantization error
198-
int quant_error = oldpixel - newpixel;
199-
// Propagate the error
200-
fb->buf[current + 1] += quant_error * 7 / 16;
201-
fb->buf[(y + 1) * fb->width + x - 1] += quant_error * 3 / 16;
202-
fb->buf[(y + 1) * fb->width + x] += quant_error * 5 / 16;
203-
fb->buf[(y + 1) * fb->width + x + 1] += quant_error / 16;
204-
}
194+
// @param fb Frame buffer
195+
// @param dt Dithering type:
196+
// 0 = Floyd Steinberg (default)
197+
// 1 = Atkinson
198+
void ditherImage(camera_fb_t* fb, int dt) {
199+
switch (dt) {
200+
default:
201+
case 0: // Floyd Steinberg dithering
202+
for (int y = 0; y < fb->height - 1; ++y) {
203+
for (int x = 1; x < fb->width - 1; ++x) {
204+
int current = y * fb->width + x;
205+
// Convert to black or white
206+
uint8_t oldpixel = fb->buf[current];
207+
uint8_t newpixel = oldpixel >= 128 ? 255 : 0;
208+
fb->buf[current] = newpixel;
209+
// Compute quantization error
210+
int quant_error = oldpixel - newpixel;
211+
// Propagate the error
212+
fb->buf[current + 1] += quant_error * 7 / 16;
213+
fb->buf[(y + 1) * fb->width + x - 1] += quant_error * 3 / 16;
214+
fb->buf[(y + 1) * fb->width + x] += quant_error * 5 / 16;
215+
fb->buf[(y + 1) * fb->width + x + 1] += quant_error / 16;
216+
}
217+
}
218+
break;
219+
case 1: // Atkinson dithering
220+
for (int y = 0; y < fb->height; ++y) {
221+
for (int x = 0; x < fb->width; ++x) {
222+
int current = y * fb->width + x;
223+
uint8_t oldpixel = fb->buf[current];
224+
uint8_t newpixel = oldpixel >= 128 ? 255 : 0;
225+
fb->buf[current] = newpixel;
226+
int quant_error = oldpixel - newpixel;
227+
228+
if (x + 1 < fb->width)
229+
fb->buf[current + 1] += quant_error * 1 / 8;
230+
if (x + 2 < fb->width)
231+
fb->buf[current + 2] += quant_error * 1 / 8;
232+
if (x > 0 && y + 1 < fb->height)
233+
fb->buf[(y + 1) * fb->width + x - 1] += quant_error * 1 / 8;
234+
if (y + 1 < fb->height)
235+
fb->buf[(y + 1) * fb->width + x] += quant_error * 1 / 8;
236+
if (y + 1 < fb->height && x + 1 < fb->width)
237+
fb->buf[(y + 1) * fb->width + x + 1] += quant_error * 1 / 8;
238+
if (y + 2 < fb->height)
239+
fb->buf[(y + 2) * fb->width + x] += quant_error * 1 / 8;
240+
}
241+
}
242+
break;
205243
}
206244
}
207245

0 commit comments

Comments
 (0)