Explorar o código

Add toggle-able dithering styles by pressing the OK button. Update guide to reflect.

Cody Tolene %!s(int64=2) %!d(string=hai) anos
pai
achega
43ffa6c16f

+ 3 - 3
src-fap/scenes/camera_suite_scene_menu.c

@@ -21,14 +21,14 @@ void camera_suite_scene_menu_on_enter(void* context) {
 
     submenu_add_item(
         app->submenu,
-        "Open Camera", // Style: Atkinson
+        "Open Camera",
         SubmenuIndexSceneStyle1,
         camera_suite_scene_menu_submenu_callback,
         app);
-    // TODO: Uncomment when style 2 is implemented
+    // Staged view for the future.
     // submenu_add_item(
     //     app->submenu,
-    //     "Style: Floyd-Steinberg",
+    //     "Test",
     //     SubmenuIndexSceneStyle2,
     //     camera_suite_scene_menu_submenu_callback,
     //     app);

+ 2 - 1
src-fap/views/camera_suite_view_guide.c

@@ -36,7 +36,8 @@ void camera_suite_view_guide_draw(Canvas* canvas, CameraSuiteViewGuideModel* mod
     canvas_draw_str_aligned(canvas, 0, 22, AlignLeft, AlignTop, "Right = Toggle Dithering");
     canvas_draw_str_aligned(canvas, 0, 32, AlignLeft, AlignTop, "Up = Contrast Up");
     canvas_draw_str_aligned(canvas, 0, 42, AlignLeft, AlignTop, "Down = Contrast Down");
-    canvas_draw_str_aligned(canvas, 0, 52, AlignLeft, AlignTop, "Center = Take Picture (TODO)");
+    // TODO: Possibly update to take picture instead.
+    canvas_draw_str_aligned(canvas, 0, 52, AlignLeft, AlignTop, "Center = Toggle Dither Type");
 }
 
 static void camera_suite_view_guide_model_init(CameraSuiteViewGuideModel* const model) {

+ 45 - 1
src-fap/views/camera_suite_view_style_1.c

@@ -4,8 +4,15 @@
 #include <input/input.h>
 #include <gui/elements.h>
 #include <dolphin/dolphin.h>
+#include "../helpers/camera_suite_haptic.h"
+#include "../helpers/camera_suite_speaker.h"
+#include "../helpers/camera_suite_led.h"
 
 static CameraSuiteViewStyle1* current_instance = NULL;
+// Dithering type:
+//    0 = Floyd Steinberg (default)
+//    1 = Atkinson
+static int current_dithering = 0;
 
 struct CameraSuiteViewStyle1 {
     CameraSuiteViewStyle1Callback callback;
@@ -110,6 +117,22 @@ static bool camera_suite_view_style_1_input(InputEvent* event, void* context) {
     furi_assert(context);
     CameraSuiteViewStyle1* instance = context;
     if(event->type == InputTypeRelease) {
+        switch(event->key) {
+        default: // Stop all sounds, reset the LED.
+            with_view_model(
+                instance->view,
+                UartDumpModel * model,
+                {
+                    UNUSED(model);
+                    camera_suite_play_bad_bump(instance->context);
+                    camera_suite_stop_all_sound(instance->context);
+                    camera_suite_led_set_rgb(instance->context, 0, 0, 0);
+                },
+                true);
+            break;
+        }
+        // Send `data` to the ESP32-CAM
+    } else if(event->type == InputTypePress) {
         uint8_t data[1];
         switch(event->key) {
         case InputKeyBack:
@@ -133,6 +156,9 @@ static bool camera_suite_view_style_1_input(InputEvent* event, void* context) {
                 UartDumpModel * model,
                 {
                     UNUSED(model);
+                    camera_suite_play_happy_bump(instance->context);
+                    camera_suite_play_input_sound(instance->context);
+                    camera_suite_led_set_rgb(instance->context, 0, 0, 255);
                     instance->callback(CameraSuiteCustomEventSceneStyle1Left, instance->context);
                 },
                 true);
@@ -145,6 +171,9 @@ static bool camera_suite_view_style_1_input(InputEvent* event, void* context) {
                 UartDumpModel * model,
                 {
                     UNUSED(model);
+                    camera_suite_play_happy_bump(instance->context);
+                    camera_suite_play_input_sound(instance->context);
+                    camera_suite_led_set_rgb(instance->context, 0, 0, 255);
                     instance->callback(CameraSuiteCustomEventSceneStyle1Right, instance->context);
                 },
                 true);
@@ -157,6 +186,9 @@ static bool camera_suite_view_style_1_input(InputEvent* event, void* context) {
                 UartDumpModel * model,
                 {
                     UNUSED(model);
+                    camera_suite_play_happy_bump(instance->context);
+                    camera_suite_play_input_sound(instance->context);
+                    camera_suite_led_set_rgb(instance->context, 0, 0, 255);
                     instance->callback(CameraSuiteCustomEventSceneStyle1Up, instance->context);
                 },
                 true);
@@ -169,17 +201,29 @@ static bool camera_suite_view_style_1_input(InputEvent* event, void* context) {
                 UartDumpModel * model,
                 {
                     UNUSED(model);
+                    camera_suite_play_happy_bump(instance->context);
+                    camera_suite_play_input_sound(instance->context);
+                    camera_suite_led_set_rgb(instance->context, 0, 0, 255);
                     instance->callback(CameraSuiteCustomEventSceneStyle1Down, instance->context);
                 },
                 true);
             break;
         case InputKeyOk:
-            // TODO: Take picture.
+            if(current_dithering == 0) {
+                data[0] = 'd'; // Update to Floyd Steinberg dithering.
+                current_dithering = 1;
+            } else {
+                data[0] = 'D'; // Update to Atkinson dithering.
+                current_dithering = 0;
+            }
             with_view_model(
                 instance->view,
                 UartDumpModel * model,
                 {
                     UNUSED(model);
+                    camera_suite_play_happy_bump(instance->context);
+                    camera_suite_play_input_sound(instance->context);
+                    camera_suite_led_set_rgb(instance->context, 0, 0, 255);
                     instance->callback(CameraSuiteCustomEventSceneStyle1Ok, instance->context);
                 },
                 true);

+ 0 - 25
src-fap/views/camera_suite_view_style_1.h

@@ -25,31 +25,6 @@
 #define RING_BUFFER_LENGTH (ROW_BUFFER_LENGTH + 3) // ROW_BUFFER_LENGTH + Header => 16 + 3 = 19
 #define LAST_ROW_INDEX (FRAME_BUFFER_LENGTH - ROW_BUFFER_LENGTH) // 1024 - 16 = 1008
 
-// const uint8_t _I_DolphinCommon_56x48_0[] = {
-//     0x01, 0x00, 0xdf, 0x00, 0x00, 0x1f, 0xfe, 0x0e, 0x05, 0x3f, 0x04, 0x06, 0x78, 0x06, 0x30, 0x20,
-//     0xf8, 0x00, 0xc6, 0x12, 0x1c, 0x04, 0x0c, 0x0a, 0x38, 0x08, 0x08, 0x0c, 0x60, 0xc0, 0x21, 0xe0,
-//     0x04, 0x0a, 0x18, 0x02, 0x1b, 0x00, 0x18, 0xa3, 0x00, 0x21, 0x90, 0x01, 0x8a, 0x20, 0x02, 0x19,
-//     0x80, 0x18, 0x80, 0x64, 0x09, 0x20, 0x89, 0x81, 0x8c, 0x3e, 0x41, 0xe2, 0x80, 0x50, 0x00, 0x43,
-//     0x08, 0x01, 0x0c, 0xfc, 0x68, 0x40, 0x61, 0xc0, 0x50, 0x30, 0x00, 0x63, 0xa0, 0x7f, 0x80, 0xc4,
-//     0x41, 0x19, 0x07, 0xff, 0x02, 0x06, 0x18, 0x24, 0x03, 0x41, 0xf3, 0x2b, 0x10, 0x19, 0x38, 0x10,
-//     0x30, 0x31, 0x7f, 0xe0, 0x34, 0x08, 0x30, 0x19, 0x60, 0x80, 0x65, 0x86, 0x0a, 0x4c, 0x0c, 0x30,
-//     0x81, 0xb9, 0x41, 0xa0, 0x54, 0x08, 0xc7, 0xe2, 0x06, 0x8a, 0x18, 0x25, 0x02, 0x21, 0x0f, 0x19,
-//     0x88, 0xd8, 0x6e, 0x1b, 0x01, 0xd1, 0x1b, 0x86, 0x39, 0x66, 0x3a, 0xa4, 0x1a, 0x50, 0x06, 0x48,
-//     0x18, 0x18, 0xd0, 0x03, 0x01, 0x41, 0x98, 0xcc, 0x60, 0x39, 0x01, 0x49, 0x2d, 0x06, 0x03, 0x50,
-//     0xf8, 0x40, 0x3e, 0x02, 0xc1, 0x82, 0x86, 0xc7, 0xfe, 0x0f, 0x28, 0x2c, 0x91, 0xd2, 0x90, 0x9a,
-//     0x18, 0x19, 0x3e, 0x6d, 0x73, 0x12, 0x16, 0x00, 0x32, 0x49, 0x72, 0xc0, 0x7e, 0x5d, 0x44, 0xba,
-//     0x2c, 0x08, 0xa4, 0xc8, 0x82, 0x06, 0x17, 0xe0, 0x81, 0x90, 0x2a, 0x40, 0x61, 0xe1, 0xa2, 0x44,
-//     0x0c, 0x76, 0x2b, 0xe8, 0x89, 0x26, 0x43, 0x83, 0x31, 0x8c, 0x78, 0x0c, 0xb0, 0x48, 0x10, 0x1a,
-//     0xe0, 0x00, 0x63,
-// };
-// const uint8_t* const _I_DolphinCommon_56x48[] = {_I_DolphinCommon_56x48_0};
-// const Icon I_DolphinCommon_56x48 = {
-//     .width = 56,
-//     .height = 48,
-//     .frame_count = 1,
-//     .frame_rate = 0,
-//     .frames = _I_DolphinCommon_56x48};
-
 typedef struct UartDumpModel UartDumpModel;
 
 struct UartDumpModel {

+ 61 - 23
src-firmware/esp32_cam_uart_stream/esp32_cam_uart_stream.ino

@@ -26,7 +26,7 @@ camera_config_t config;
 void handleSerialInput();
 void initializeCamera();
 void processImage(camera_fb_t* fb);
-void ditherImage(camera_fb_t* fb);
+void ditherImage(camera_fb_t* fb, int dt);
 bool isDarkBit(uint8_t bit);
 
 // Serial input flags
@@ -34,10 +34,14 @@ bool disableDithering = false;
 bool invert = false;
 bool rotated = false;
 bool stopStream = false;
+// Dithering type:
+//    0 = Floyd Steinberg (default)
+//    1 = Atkinson
+int dtType = 0;
+
 
 void setup() {
   Serial.begin(230400);
-
   initializeCamera();
 }
 
@@ -48,6 +52,7 @@ void loop() {
     return;
   }
 
+  // Frame buffer.
   camera_fb_t* fb = esp_camera_fb_get();
 
   if (!fb) {
@@ -85,11 +90,11 @@ void handleSerialInput() {
       case 'c': // Remove contrast.
         cameraSensor->set_contrast(cameraSensor, cameraSensor->status.contrast - 1);
         break;
-      case 'D': // Enable dithering.
-        disableDithering = false;
+      case 'D': // Use Floyd Steinberg dithering.
+        dtType = 0;
         break;
-      case 'd': // Disable dithering.
-        disableDithering = true;
+      case 'd': // Use Atkinson dithering.
+        dtType = 1;
         break;
       case 'M': // Toggle Mirror
         cameraSensor->set_hmirror(cameraSensor, !cameraSensor->status.hmirror);
@@ -149,7 +154,7 @@ void initializeCamera() {
 
 void processImage(camera_fb_t* frameBuffer) {
   if (!disableDithering) {
-    ditherImage(frameBuffer);
+    ditherImage(frameBuffer, dtType);
   }
 
   uint8_t flipper_y = 0;
@@ -186,22 +191,55 @@ void processImage(camera_fb_t* frameBuffer) {
 }
 
 // Dither image.
-void ditherImage(camera_fb_t* fb) {
-  for (int y = 0; y < fb->height - 1; ++y) {
-    for (int x = 1; x < fb->width - 1; ++x) {
-      int current = y * fb->width + x;
-      // Convert to black or white
-      uint8_t oldpixel = fb->buf[current];
-      uint8_t newpixel = oldpixel >= 128 ? 255 : 0;
-      fb->buf[current] = newpixel;
-      // Compute quantization error
-      int quant_error = oldpixel - newpixel;
-      // Propagate the error
-      fb->buf[current + 1] += quant_error * 7 / 16;
-      fb->buf[(y + 1) * fb->width + x - 1] += quant_error * 3 / 16;
-      fb->buf[(y + 1) * fb->width + x] += quant_error * 5 / 16;
-      fb->buf[(y + 1) * fb->width + x + 1] += quant_error / 16;
-    }
+// @param fb Frame buffer
+// @param dt Dithering type:
+//    0 = Floyd Steinberg (default)
+//    1 = Atkinson
+void ditherImage(camera_fb_t* fb, int dt) {
+  switch (dt) {
+    default:
+    case 0: // Floyd Steinberg dithering
+      for (int y = 0; y < fb->height - 1; ++y) {
+        for (int x = 1; x < fb->width - 1; ++x) {
+          int current = y * fb->width + x;
+          // Convert to black or white
+          uint8_t oldpixel = fb->buf[current];
+          uint8_t newpixel = oldpixel >= 128 ? 255 : 0;
+          fb->buf[current] = newpixel;
+          // Compute quantization error
+          int quant_error = oldpixel - newpixel;
+          // Propagate the error
+          fb->buf[current + 1] += quant_error * 7 / 16;
+          fb->buf[(y + 1) * fb->width + x - 1] += quant_error * 3 / 16;
+          fb->buf[(y + 1) * fb->width + x] += quant_error * 5 / 16;
+          fb->buf[(y + 1) * fb->width + x + 1] += quant_error / 16;
+        }
+      }
+      break;
+    case 1: // Atkinson dithering
+      for (int y = 0; y < fb->height; ++y) {
+        for (int x = 0; x < fb->width; ++x) {
+          int current = y * fb->width + x;
+          uint8_t oldpixel = fb->buf[current];
+          uint8_t newpixel = oldpixel >= 128 ? 255 : 0;
+          fb->buf[current] = newpixel;
+          int quant_error = oldpixel - newpixel;
+
+          if (x + 1 < fb->width)
+            fb->buf[current + 1] += quant_error * 1 / 8;
+          if (x + 2 < fb->width)
+            fb->buf[current + 2] += quant_error * 1 / 8;
+          if (x > 0 && y + 1 < fb->height)
+            fb->buf[(y + 1) * fb->width + x - 1] += quant_error * 1 / 8;
+          if (y + 1 < fb->height)
+            fb->buf[(y + 1) * fb->width + x] += quant_error * 1 / 8;
+          if (y + 1 < fb->height && x + 1 < fb->width)
+            fb->buf[(y + 1) * fb->width + x + 1] += quant_error * 1 / 8;
+          if (y + 2 < fb->height)
+            fb->buf[(y + 2) * fb->width + x] += quant_error * 1 / 8;
+        }
+      }
+      break;
   }
 }