ソースを参照

ov2640: support sharpness control

Joo Aun Saw 2 年 前
コミット
69f1a99b55

+ 79 - 44
code/components/jomjol_controlcamera/ClassControllCamera.cpp

@@ -31,6 +31,8 @@
 #include "driver/ledc.h"
 #include "MainFlowControl.h"
 
+#include "ov2640_sharpness.h"
+
 #if (ESP_IDF_VERSION_MAJOR >= 5)
 #include "soc/periph_defs.h"
 #include "esp_private/periph_ctrl.h"
@@ -53,9 +55,9 @@ static const char* _STREAM_CONTENT_TYPE = "multipart/x-mixed-replace;boundary="
 static const char* _STREAM_BOUNDARY = "\r\n--" PART_BOUNDARY "\r\n";
 static const char* _STREAM_PART = "Content-Type: image/jpeg\r\nContent-Length: %u\r\n\r\n";
 
-// OV Camera SDE Indirect Register Access
-#define OV_IRA_BPADDR               0x7C
-#define OV_IRA_BPDATA               0x7D
+// OV2640 Camera SDE Indirect Register Access
+#define OV2640_IRA_BPADDR               0x7C
+#define OV2640_IRA_BPDATA               0x7D
 
 
 static camera_config_t camera_config = {
@@ -170,66 +172,93 @@ static size_t jpg_encode_stream(void * arg, size_t index, const void* data, size
 }
 
 
-bool CCamera::SetBrightnessContrastSaturation(int _brightness, int _contrast, int _saturation, int _autoExposureLevel, bool _grayscale, bool _negative, bool _aec2)
+bool CCamera::SetBrightnessContrastSaturation(int _brightness, int _contrast, int _saturation, int _autoExposureLevel, bool _grayscale, bool _negative, bool _aec2, int _sharpnessLevel)
 {
     _brightness = min(2, max(-2, _brightness));
     _contrast = min(2, max(-2, _contrast));
     _saturation = min(2, max(-2, _saturation));
     _autoExposureLevel = min(2, max(-2, _autoExposureLevel));
+    bool _autoSharpness = false;
+    if (_sharpnessLevel <= -4)
+        _autoSharpness = true;
+    _sharpnessLevel = min(3, max(-3, _sharpnessLevel));
 
     sensor_t * s = esp_camera_sensor_get();
     if (s) {
+        // camera gives precedence to negative over grayscale, so it's easier to do negative ourselves.
+        // if (_negative) {
+        //     s->set_special_effect(s, 1); // 0 - no effect, 1 - negative, 2 - grayscale, 3 - reddish, 4 - greenish, 5 - blue, 6 - retro
+        // }
+        if (_grayscale) {
+            s->set_special_effect(s, 2); // 0 - no effect, 1 - negative, 2 - grayscale, 3 - reddish, 4 - greenish, 5 - blue, 6 - retro
+        }
+
         // auto exposure controls
         s->set_aec2(s, _aec2 ? 1 : 0);
         s->set_ae_level(s, _autoExposureLevel); // -2 to 2
         s->set_gainceiling(s, GAINCEILING_2X); // GAINCEILING_2X 4X 8X 16X 32X 64X 128X
 
         // post processing
+        if (_autoSharpness) {
+            s->set_sharpness(s, 0); // auto-sharpness is not officially supported, default to 0
+        }
         s->set_saturation(s, _saturation);
         s->set_contrast(s, _contrast);
         s->set_brightness(s, _brightness);
 
-        /* Workaround - bug in cam library - enable bits are set without using bitwise OR logic -> only latest enable setting is used */
-        /* Library version: https://github.com/espressif/esp32-camera/commit/5c8349f4cf169c8a61283e0da9b8cff10994d3f3 */
-        /* Reference: https://esp32.com/viewtopic.php?f=19&t=14376#p93178 */
-        /* The memory structure is as follows for 
-        byte_0 = enable_bits
-            byte_0->bit0 = enable saturation and hue --> OK
-            byte_0->bit1 = enable saturation --> OK
-            byte_0->bit2 = enable brightness and contrast --> OK
-            byte_0->bit3 = enable green -> blue spitial effect (Antique and blunish and greenish and readdish and b&w) enable
-            byte_0->bit4 = anable gray -> read spitial effect (Antique and blunish and greenish and readdish and b&w) enable
-            byte_0->bit5 = remove (UV) in YUV color system
-            byte_0->bit6 = enable negative
-            byte_0->bit7 = remove (Y) in YUV color system
-        byte_1 = saturation1 0-255 --> ?
-        byte_2 = hue 0-255 --> OK
-        byte_3 = saturation2 0-255 --> OK
-        byte_4 = reenter saturation2 in documents --> ?
-        byte_5 = spital effect green -> blue 0-255 --> ?
-        byte_6 = spital effect gray -> read 0-255 --> ?
-        byte_7 = contrast lower byte 0-255 --> OK
-        byte_8 = contrast higher byte 0-255 --> OK
-        byte_9 = brightness 0-255 --> OK
-        byte_10= if byte_10==4 contrast effective --> ?
-        */
-
-        //s->set_reg(s, 0x7C, 0xFF, 2); // Optional feature - hue setting: Select byte 2 in register 0x7C to set hue value
-        //s->set_reg(s, 0x7D, 0xFF, 0); // Optional feature - hue setting: Hue value 0 - 255
-        int indirectReg0 = 0x07; // Set bit 0, 1, 2 to enable saturation, contrast, brightness and hue control
-        if (_grayscale) {
-            indirectReg0 |= 0x18;
-        }
-        if (_negative) {
-            indirectReg0 |= 0x40;
+        camera_sensor_info_t *sensor_info = esp_camera_sensor_get_info(&(s->id));
+        if (sensor_info != NULL) {
+            if (sensor_info->model == CAMERA_OV2640) {
+                if (_autoSharpness) {
+                    ov2640_enable_auto_sharpness(s);
+                } else {
+                    ov2640_set_sharpness(s, _sharpnessLevel);
+                }
+
+                /* Workaround - bug in cam library - enable bits are set without using bitwise OR logic -> only latest enable setting is used */
+                /* Library version: https://github.com/espressif/esp32-camera/commit/5c8349f4cf169c8a61283e0da9b8cff10994d3f3 */
+                /* Reference: https://esp32.com/viewtopic.php?f=19&t=14376#p93178 */
+                /* The memory structure is as follows for 
+                byte_0 = enable_bits
+                    byte_0->bit0 = enable saturation and hue --> OK
+                    byte_0->bit1 = enable saturation --> OK
+                    byte_0->bit2 = enable brightness and contrast --> OK
+                    byte_0->bit3 = enable green -> blue spitial effect (Antique and blunish and greenish and readdish and b&w) enable
+                    byte_0->bit4 = anable gray -> read spitial effect (Antique and blunish and greenish and readdish and b&w) enable
+                    byte_0->bit5 = remove (UV) in YUV color system
+                    byte_0->bit6 = enable negative
+                    byte_0->bit7 = remove (Y) in YUV color system
+                byte_1 = saturation1 0-255 --> ?
+                byte_2 = hue 0-255 --> OK
+                byte_3 = saturation2 0-255 --> OK
+                byte_4 = reenter saturation2 in documents --> ?
+                byte_5 = spital effect green -> blue 0-255 --> ?
+                byte_6 = spital effect gray -> read 0-255 --> ?
+                byte_7 = contrast lower byte 0-255 --> OK
+                byte_8 = contrast higher byte 0-255 --> OK
+                byte_9 = brightness 0-255 --> OK
+                byte_10= if byte_10==4 contrast effective --> ?
+                */
+
+                //s->set_reg(s, 0x7C, 0xFF, 2); // Optional feature - hue setting: Select byte 2 in register 0x7C to set hue value
+                //s->set_reg(s, 0x7D, 0xFF, 0); // Optional feature - hue setting: Hue value 0 - 255
+                int indirectReg0 = 0x07; // Set bit 0, 1, 2 to enable saturation, contrast, brightness and hue control
+                if (_grayscale) {
+                    indirectReg0 |= 0x18;
+                }
+                // camera gives precedence to negative over grayscale, so it's easier to do negative ourselves.
+                // if (_negative) {
+                //     indirectReg0 |= 0x40;
+                // }
+                // Indirect register access
+                s->set_reg(s, 0xFF, 0x01, 0); // Select DSP bank
+                s->set_reg(s, OV2640_IRA_BPADDR, 0xFF, 0x00); // Address 0x00
+                s->set_reg(s, OV2640_IRA_BPDATA, 0xFF, indirectReg0);
+                s->set_reg(s, OV2640_IRA_BPADDR, 0xFF, 0x05); // Address 0x05
+                s->set_reg(s, OV2640_IRA_BPDATA, 0xFF, 0x80);
+                s->set_reg(s, OV2640_IRA_BPDATA, 0xFF, 0x80);
+            }
         }
-        // Indirect register access
-        s->set_reg(s, 0xFF, 0x01, 0); // Select DSP bank
-        s->set_reg(s, OV_IRA_BPADDR, 0xFF, 0x00); // Address 0x00
-        s->set_reg(s, OV_IRA_BPDATA, 0xFF, indirectReg0);
-        s->set_reg(s, OV_IRA_BPADDR, 0xFF, 0x05); // Address 0x05
-        s->set_reg(s, OV_IRA_BPDATA, 0xFF, 0x80);
-        s->set_reg(s, OV_IRA_BPDATA, 0xFF, 0x80);
     }
     else {
         LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "SetBrightnessContrastSaturation: Failed to get control structure");
@@ -245,6 +274,8 @@ bool CCamera::SetBrightnessContrastSaturation(int _brightness, int _contrast, in
     imageGrayscale = _grayscale;
     imageNegative = _negative;
     imageAec2 = _aec2;
+    imageAutoSharpness = _autoSharpness;
+    imageSharpnessLevel = _sharpnessLevel;
 
     ESP_LOGD(TAG, "brightness %d, contrast: %d, saturation %d, autoExposureLevel %d, grayscale %d", brightness, contrast, saturation, autoExposureLevel, (int)imageGrayscale);
 
@@ -471,6 +502,10 @@ esp_err_t CCamera::CaptureToBasisImage(CImageBasis *_Image, int delay)
         return ESP_OK;
     }
 
+    if (imageNegative) {
+        _zwImage->Negative();
+    }
+
     stbi_uc* p_target;
     stbi_uc* p_source;    
     int channels = 3;

+ 3 - 1
code/components/jomjol_controlcamera/ClassControllCamera.h

@@ -42,6 +42,8 @@ class CCamera {
         int imageZoomOffsetY = 0;
         bool imageNegative = false;
         bool imageAec2 = false;
+        bool imageAutoSharpness = false;
+        int imageSharpnessLevel = 0;
     #ifdef GRAYSCALE_AS_DEFAULT
         bool imageGrayscale = true;
     #else
@@ -56,7 +58,7 @@ class CCamera {
         esp_err_t CaptureToHTTP(httpd_req_t *req, int delay = 0);
         esp_err_t CaptureToStream(httpd_req_t *req, bool FlashlightOn);
         void SetQualitySize(int qual, framesize_t resol, bool zoomEnabled, int zoomMode, int zoomOffsetX, int zoomOffsetY);
-        bool SetBrightnessContrastSaturation(int _brightness, int _contrast, int _saturation, int _autoExposureLevel, bool _grayscale, bool _negative, bool _aec2);
+        bool SetBrightnessContrastSaturation(int _brightness, int _contrast, int _saturation, int _autoExposureLevel, bool _grayscale, bool _negative, bool _aec2, int _sharpnessLevel);
         void SetZoom(bool zoomEnabled, int zoomMode, int zoomOffsetX, int zoomOffsetY);
         void GetCameraParameter(httpd_req_t *req, int &qual, framesize_t &resol, bool &zoomEnabled, int &zoomMode, int &zoomOffsetX, int &zoomOffsetY);
         void SetLEDIntensity(float _intrel);

+ 124 - 0
code/components/jomjol_controlcamera/ov2640_sharpness.cpp

@@ -0,0 +1,124 @@
+#include <stdint.h>
+#include "esp_camera.h"
+#include "ov2640_sharpness.h"
+
+
+#define OV2640_MAXLEVEL_SHARPNESS 6
+
+const static uint8_t OV2640_SHARPNESS_AUTO[]=
+{
+    0xFF, 0x00, 0xff,
+    0x92, 0x01, 0xff,
+    0x93, 0x20, 0x20,
+    0x00, 0x00, 0x00
+};
+
+const static uint8_t OV2640_SHARPNESS_MANUAL[]=
+{
+    0xFF, 0x00, 0xff,
+    0x92, 0x01, 0xff,
+    0x93, 0x00, 0x20,
+    0x00, 0x00, 0x00
+};
+
+const static uint8_t OV2640_SHARPNESS_LEVEL0[]=
+{
+    0xFF, 0x00, 0xff,
+    0x92, 0x01, 0xff,
+    0x93, 0xc0, 0x1f,
+    0x00, 0x00, 0x00
+};
+const static uint8_t OV2640_SHARPNESS_LEVEL1[]=
+{
+    0xFF, 0x00, 0xff,
+    0x92, 0x01, 0xff,
+    0x93, 0xc1, 0x1f,
+    0x00, 0x00, 0x00
+};
+const static uint8_t OV2640_SHARPNESS_LEVEL2[]=
+{
+    0xFF, 0x00, 0xff,
+    0x92, 0x01, 0xff,
+    0x93, 0xc2, 0x1f,
+    0x00, 0x00, 0x00
+};
+const static uint8_t OV2640_SHARPNESS_LEVEL3[]=
+{
+    0xFF, 0x00, 0xff,
+    0x92, 0x01, 0xff,
+    0x93, 0xc4, 0x1f,
+    0x00, 0x00, 0x00
+};
+const static uint8_t OV2640_SHARPNESS_LEVEL4[]=
+{
+    0xFF, 0x00, 0xff,
+    0x92, 0x01, 0xff,
+    0x93, 0xc8, 0x1f,
+    0x00, 0x00, 0x00
+};
+const static uint8_t OV2640_SHARPNESS_LEVEL5[]=
+{
+    0xFF, 0x00, 0xff,
+    0x92, 0x01, 0xff,
+    0x93, 0xd0, 0x1f,
+    0x00, 0x00, 0x00
+};
+const static uint8_t OV2640_SHARPNESS_LEVEL6[]=
+{
+    0xFF, 0x00, 0xff,
+    0x92, 0x01, 0xff,
+    0x93, 0xdf, 0x1f,
+    0x00, 0x00, 0x00
+};
+
+const static uint8_t *OV2640_SETTING_SHARPNESS[]=
+{
+    OV2640_SHARPNESS_LEVEL0, // -3 sharpness
+    OV2640_SHARPNESS_LEVEL1,
+    OV2640_SHARPNESS_LEVEL2,
+    OV2640_SHARPNESS_LEVEL3,
+    OV2640_SHARPNESS_LEVEL4,
+    OV2640_SHARPNESS_LEVEL5,
+    OV2640_SHARPNESS_LEVEL6  // +3 sharpness
+};
+
+static int table_mask_write(sensor_t *sensor, const uint8_t* ptab)
+{
+    uint8_t address;
+    uint8_t value;
+    uint8_t orgval;
+    uint8_t mask;
+    const uint8_t *pdata = ptab;
+
+    if (pdata == NULL)
+        return -1;
+
+    while (1)
+    {   
+        address = *pdata++;
+        value = *pdata++;
+        mask = *pdata++;
+        if ((address == 0) && (value == 0) && (mask == 0))
+            break;
+        sensor->set_reg(sensor, address, mask, value);
+    }   
+
+    return 0;
+}
+
+int ov2640_enable_auto_sharpness(sensor_t *sensor)
+{
+    table_mask_write(sensor, OV2640_SHARPNESS_AUTO);
+    return 0;
+}
+
+
+int ov2640_set_sharpness(sensor_t *sensor, int sharpness)
+{
+    if ((sharpness < -3) || (sharpness > OV2640_MAXLEVEL_SHARPNESS - 3))
+        return -1;
+
+    table_mask_write(sensor, OV2640_SHARPNESS_MANUAL);
+    table_mask_write(sensor, OV2640_SETTING_SHARPNESS[sharpness + 3]);
+    return 0;
+}

+ 11 - 0
code/components/jomjol_controlcamera/ov2640_sharpness.h

@@ -0,0 +1,11 @@
+#pragma once
+
+#ifndef OV2640_SHARPNESS_H
+#define OV2640_SHARPNESS_H
+
+#include "esp_camera.h"
+
+int ov2640_enable_auto_sharpness(sensor_t *sensor);
+int ov2640_set_sharpness(sensor_t *sensor, int sharpness); // -3 to +3, -4 for auto-sharpness
+
+#endif

+ 7 - 1
code/components/jomjol_flowcontroll/ClassFlowTakeImage.cpp

@@ -84,6 +84,7 @@ bool ClassFlowTakeImage::ReadParameter(FILE* pfile, string& aktparamgraph)
     int _brightness = -100;
     int _contrast = -100;
     int _saturation = -100;
+    int _sharpness = 0;
     int _autoExposureLevel = 0;
 
     if (aktparamgraph.size() == 0)
@@ -178,6 +179,11 @@ bool ClassFlowTakeImage::ReadParameter(FILE* pfile, string& aktparamgraph)
             _saturation = stoi(splitted[1]);
         }
 
+        if ((toUpper(splitted[0]) == "SHARPNESS") && (splitted.size() > 1))
+        {
+            _sharpness = stoi(splitted[1]);
+        }
+
         if ((toUpper(splitted[0]) == "FIXEDEXPOSURE") && (splitted.size() > 1))
         {
             if (toUpper(splitted[1]) == "TRUE")
@@ -199,7 +205,7 @@ bool ClassFlowTakeImage::ReadParameter(FILE* pfile, string& aktparamgraph)
         }
     }
 
-    Camera.SetBrightnessContrastSaturation(_brightness, _contrast, _saturation, _autoExposureLevel, ImageGrayscale, ImageNegative, ImageAec2);
+    Camera.SetBrightnessContrastSaturation(_brightness, _contrast, _saturation, _autoExposureLevel, ImageGrayscale, ImageNegative, ImageAec2, _sharpness);
     Camera.SetQualitySize(ImageQuality, ImageSize, ZoomEnabled, ZoomMode, zoomOffsetX, zoomOffsetY);
 
     image_width = Camera.image_width;

+ 6 - 1
code/components/jomjol_flowcontroll/MainFlowControl.cpp

@@ -703,6 +703,7 @@ esp_err_t handler_editflow(httpd_req_t *req)
         bool zoom = false;
         bool negative = false;
         bool aec2 = false;
+        int sharpnessLevel = 0;
     #ifdef GRAYSCALE_AS_DEFAULT
         bool grayscale = true;
     #else
@@ -732,6 +733,10 @@ esp_err_t handler_editflow(httpd_req_t *req)
             std::string _ae = std::string(_valuechar);
             aelevel = stoi(_ae);
         }
+        if (httpd_query_key_value(_query, "sh", _valuechar, 30) == ESP_OK) {
+            std::string _sh = std::string(_valuechar);
+            sharpnessLevel = stoi(_sh);
+        }
         if (httpd_query_key_value(_query, "gs", _valuechar, 30) == ESP_OK) {
             std::string _gr = std::string(_valuechar);
             if (stoi(_gr) != 0)
@@ -776,7 +781,7 @@ esp_err_t handler_editflow(httpd_req_t *req)
 //        ESP_LOGD(TAG, "Parameter host: %s", _host.c_str());
 //        string zwzw = "Do " + _task + " start\n"; ESP_LOGD(TAG, zwzw.c_str());
         Camera.SetZoom(zoom, zoommode, zoomoffsetx, zoomoffsety);
-        Camera.SetBrightnessContrastSaturation(bri, con, sat, aelevel, grayscale, negative, aec2);
+        Camera.SetBrightnessContrastSaturation(bri, con, sat, aelevel, grayscale, negative, aec2, sharpnessLevel);
         Camera.SetLEDIntensity(intens);
         ESP_LOGD(TAG, "test_take - vor TakeImage");
         std::string zw = flowctrl.doSingleStep("[TakeImage]", _host);

+ 14 - 0
code/components/jomjol_image_proc/CImageBasis.cpp

@@ -636,6 +636,20 @@ CImageBasis::CImageBasis(string _name, uint8_t* _rgb_image, int _channels, int _
 }
 
 
+void CImageBasis::Negative(void)
+{
+    RGBImageLock();
+
+    for (int i = 0; i < width * height * channels; i += channels) {
+        for (int c = 0; c < channels; c++) {
+            rgb_image[i+c] = 255 - rgb_image[i+c];
+        }
+    }
+
+    RGBImageRelease();
+}
+
+
 void CImageBasis::Contrast(float _contrast)  //input range [-100..100]
 {
     stbi_uc* p_source;

+ 1 - 0
code/components/jomjol_image_proc/CImageBasis.h

@@ -56,6 +56,7 @@ class CImageBasis
         void drawEllipse(int x1, int y1, int radx, int rady, int r, int g, int b, int thickness = 1);
 
         void setPixelColor(int x, int y, int r, int g, int b);
+        void Negative(void);
         void Contrast(float _contrast);
         bool ImageOkay();
         bool CopyFromMemory(uint8_t* _source, int _size);

+ 31 - 3
sd-card/html/edit_reference.html

@@ -184,7 +184,7 @@
         </tr>
         <tr>
             <td><label for="negative" id="labelnegative">Negative:</label></td>
-            <td><input type="checkbox" id="negative" name="negative" value="0"></td>
+            <td><input type="checkbox" id="negative" name="negative" value="0" onchange="drawRotated()"></td>
             <td>
                 <class id="TakeImage_AutoExposureLevel_text" style="color:black;">Auto exposure:</class>
             </td>
@@ -193,6 +193,17 @@
                 <output id="TakeImage_AutoExposureLevel_value1_output" style="vertical-align:middle; min-width:15px; padding-right:5px; text-align:right; float:left">0</output>
             </td>
         </tr>
+        <tr>
+            <td></td>
+            <td></td>
+            <td>
+                <class id="TakeImage_Sharpness_text" style="color:black;">Sharpness:</class>
+            </td>
+            <td>
+                <input style="clear: both; width: 80%;vertical-align:middle" type="range" id="TakeImage_Sharpness_value1" size="13" value=0  min="-4" max="3" oninput="this.nextElementSibling.value = this.value">
+                <output id="TakeImage_Sharpness_value1_output" style="vertical-align:middle; min-width:15px; padding-right:5px; text-align:right; float:left">0</output>
+            </td>
+        </tr>
     </table>
     <table>
         <colgroup>
@@ -257,11 +268,13 @@
                 _brightness = document.getElementById("TakeImage_Brightness_value1").value;
                 _contrast = document.getElementById("TakeImage_Contrast_value1").value;
                 _saturation = document.getElementById("TakeImage_Saturation_value1").value;
+                _sharpness = document.getElementById("TakeImage_Sharpness_value1").value;
                 _ae = document.getElementById("TakeImage_AutoExposureLevel_value1").value;
                 url = getDomainname() + "/editflow?task=test_take&bri=" + _brightness;
-                url = url + "&con=" + _contrast + "&sat=" + _saturation + "&int=" + _intensity + "&ae=" + _ae + "&gs=" + _grayscale + "&ne=" + _negative + "&a2=" + _aec2;
+                url = url + "&con=" + _contrast + "&sat=" + _saturation + "&sh=" + _sharpness + "&int=" + _intensity + "&gs=" + _grayscale + "&ne=" + _negative + "&z=" + _zoom;
                 if (_zoom != '0')
-                    url = url + "&z=" + _zoom + "&zm=" + _zm + "&x=" + _x + "&y=" + _y;
+                    url = url + "&zm=" + _zm + "&x=" + _x + "&y=" + _y;
+                url = url + "&ae=" + _ae + "&a2=" + _aec2;
             }
             else
             {
@@ -320,6 +333,7 @@
                 document.getElementById("TakeImage_Brightness_value1").disabled = false;
                 document.getElementById("TakeImage_Contrast_value1").disabled = false;
                 document.getElementById("TakeImage_Saturation_value1").disabled = false;
+                document.getElementById("TakeImage_Sharpness_value1").disabled = false;
                 document.getElementById("TakeImage_LEDIntensity_value1").disabled = false;
             }
             else
@@ -374,6 +388,7 @@
             document.getElementById("TakeImage_Brightness_value1").disabled = true;
             document.getElementById("TakeImage_Saturation_value1").disabled = true;
             document.getElementById("TakeImage_Contrast_value1").disabled = true;
+            document.getElementById("TakeImage_Sharpness_value1").disabled = true;
             document.getElementById("TakeImage_LEDIntensity_value1").disabled = true;
             document.getElementById("mirror").disabled = false;
             document.getElementById("flip").disabled = false;
@@ -450,6 +465,7 @@
                     ReadParameter(param, "TakeImage", "Brightness", false);
                     ReadParameter(param, "TakeImage", "Contrast", false);
                     ReadParameter(param, "TakeImage", "Saturation", false);
+                    ReadParameter(param, "TakeImage", "Sharpness", false);
                     ReadParameter(param, "TakeImage", "LEDIntensity", false);
                     ReadParameter(param, "TakeImage", "AutoExposureLevel", false);
                 }
@@ -523,6 +539,7 @@
             param["TakeImage"]["Brightness"]["enabled"] = true;
             param["TakeImage"]["Contrast"]["enabled"] = true;
             param["TakeImage"]["Saturation"]["enabled"] = true;
+            param["TakeImage"]["Sharpness"]["enabled"] = true;
             
             param["TakeImage"]["Grayscale"]["enabled"] = true;
             param["TakeImage"]["Negative"]["enabled"] = true;
@@ -593,6 +610,11 @@
                 param["TakeImage"]["Saturation"]["found"] = true;
                 param["TakeImage"]["Saturation"]["value1"] = "0";
             }
+            if (!param["TakeImage"]["Sharpness"]["found"])
+            {
+                param["TakeImage"]["Sharpness"]["found"] = true;
+                param["TakeImage"]["Sharpness"]["value1"] = "0";
+            }
 
             UpdateInput();
             showReference(param); 
@@ -604,6 +626,7 @@
             WriteParameter(param, category, "TakeImage", "Brightness", false, true);
             WriteParameter(param, category, "TakeImage", "Contrast", false, true);
             WriteParameter(param, category, "TakeImage", "Saturation", false, true);
+            WriteParameter(param, category, "TakeImage", "Sharpness", false, true);
             WriteParameter(param, category, "TakeImage", "LEDIntensity", false);
             if (param["TakeImage"]["Grayscale"].value1 == "true") {
                 document.getElementById("grayscale").checked = true;
@@ -724,6 +747,11 @@
             context.clearRect(0,0,canvas.width,canvas.height);
             context.save();
 
+            negative = document.getElementById("negative").checked;
+            if (negative) {
+                context.filter = 'invert(1)';
+            }
+
             if (isActReference)
             {
                 context.drawImage(imageObj,0,0);

+ 1 - 0
sd-card/html/readconfigparam.js

@@ -117,6 +117,7 @@ function ParseConfig() {
      ParamAddValue(param, catname, "Brightness");
      ParamAddValue(param, catname, "Contrast");
      ParamAddValue(param, catname, "Saturation");
+     ParamAddValue(param, catname, "Sharpness");
      ParamAddValue(param, catname, "LEDIntensity");
      ParamAddValue(param, catname, "ImageQuality");
      ParamAddValue(param, catname, "ImageSize");