added photo EXIF metadata
parent
3d7d283b6d
commit
527f2a53ae
|
|
@ -252,7 +252,7 @@ const char page_config_html[] PROGMEM = R"rawliteral(
|
||||||
<option value="4">1024x768</option>
|
<option value="4">1024x768</option>
|
||||||
<option value="5">1280x1024</option>
|
<option value="5">1280x1024</option>
|
||||||
<option value="6">1600x1200</option>
|
<option value="6">1600x1200</option>
|
||||||
</select>
|
</select> <span class="pc1">pixels</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr><td style="height: 1px;"></td><td style="height: 1px;"></td></tr>
|
<tr><td style="height: 1px;"></td><td style="height: 1px;"></td></tr>
|
||||||
|
|
@ -260,6 +260,16 @@ const char page_config_html[] PROGMEM = R"rawliteral(
|
||||||
<tr><td class="pc1">Contrast</td><td class="pc2">Low <input type="range" class="slider" name="contrast" id=contrastid min="-2" max="2" step="1" onchange="changeValue(this.value, 'set_int?contrast=', 'config')"> High</td></tr>
|
<tr><td class="pc1">Contrast</td><td class="pc2">Low <input type="range" class="slider" name="contrast" id=contrastid min="-2" max="2" step="1" onchange="changeValue(this.value, 'set_int?contrast=', 'config')"> High</td></tr>
|
||||||
<tr><td class="pc1">Saturation</td><td class="pc2">Low <input type="range" class="slider" name="saturation" id=saturationid min="-2" max="2" step="1" onchange="changeValue(this.value, 'set_int?saturation=', 'config')"> High</td></tr>
|
<tr><td class="pc1">Saturation</td><td class="pc2">Low <input type="range" class="slider" name="saturation" id=saturationid min="-2" max="2" step="1" onchange="changeValue(this.value, 'set_int?saturation=', 'config')"> High</td></tr>
|
||||||
<tr><td style="height: 1px;"></td><td style="height: 1px;"></td></tr>
|
<tr><td style="height: 1px;"></td><td style="height: 1px;"></td></tr>
|
||||||
|
<tr>
|
||||||
|
<td class="pc1">Image rotation</td><td><label for="image_rotation"></label>
|
||||||
|
<select class="select" id="image_rotationid" name="image_rotation" onchange="changeValue(this.value, 'set_int?image_rotation=', 'config')">
|
||||||
|
<option value="1">0°</option>
|
||||||
|
<option value="6">90°</option>
|
||||||
|
<option value="3">180°</option>
|
||||||
|
<option value="8">270°</option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
<tr><td class="pc1">Horizontal mirror</td><td class="pc2"><label class="switch"><input type="checkbox" name="hmirror" id=hmirrorid onchange="changeValue(this.checked, 'set_bool?hmirror=', 'config')"><span class="checkbox_slider round"></span></label> <span id="status_hmirror"></span></td></tr>
|
<tr><td class="pc1">Horizontal mirror</td><td class="pc2"><label class="switch"><input type="checkbox" name="hmirror" id=hmirrorid onchange="changeValue(this.checked, 'set_bool?hmirror=', 'config')"><span class="checkbox_slider round"></span></label> <span id="status_hmirror"></span></td></tr>
|
||||||
<tr><td class="pc1">Vertical flip</td><td class="pc2"><label class="switch"><input type="checkbox" name="vflip" id=vflipid onchange="changeValue(this.checked, 'set_bool?vflip=', 'config')"><span class="checkbox_slider round"></span></label> <span id="status_vflip"></span></td></tr>
|
<tr><td class="pc1">Vertical flip</td><td class="pc2"><label class="switch"><input type="checkbox" name="vflip" id=vflipid onchange="changeValue(this.checked, 'set_bool?vflip=', 'config')"><span class="checkbox_slider round"></span></label> <span id="status_vflip"></span></td></tr>
|
||||||
<tr><td style="height: 1px;"></td><td style="height: 1px;"></td></tr>
|
<tr><td style="height: 1px;"></td><td style="height: 1px;"></td></tr>
|
||||||
|
|
@ -921,6 +931,7 @@ function get_data(val) {
|
||||||
document.getElementById('brightnessid').value = obj.brightness;
|
document.getElementById('brightnessid').value = obj.brightness;
|
||||||
document.getElementById('contrastid').value = obj.contrast;
|
document.getElementById('contrastid').value = obj.contrast;
|
||||||
document.getElementById('saturationid').value = obj.saturation;
|
document.getElementById('saturationid').value = obj.saturation;
|
||||||
|
document.getElementById('image_rotationid').value = obj.image_rotation;
|
||||||
document.getElementById('hmirrorid').checked = obj.hmirror;
|
document.getElementById('hmirrorid').checked = obj.hmirror;
|
||||||
document.getElementById('vflipid').checked = obj.vflip;
|
document.getElementById('vflipid').checked = obj.vflip;
|
||||||
document.getElementById('lencid').checked = obj.lensc;
|
document.getElementById('lencid').checked = obj.lensc;
|
||||||
|
|
|
||||||
|
|
@ -22,9 +22,14 @@ Camera SystemCamera(&SystemConfig, &SystemLog, FLASH_GPIO_NUM);
|
||||||
Camera::Camera(Configuration* i_conf, Logs* i_log, uint8_t i_FlashPin) {
|
Camera::Camera(Configuration* i_conf, Logs* i_log, uint8_t i_FlashPin) {
|
||||||
config = i_conf;
|
config = i_conf;
|
||||||
log = i_log;
|
log = i_log;
|
||||||
|
|
||||||
CameraFlashPin = i_FlashPin;
|
CameraFlashPin = i_FlashPin;
|
||||||
StreamOnOff = false;
|
StreamOnOff = false;
|
||||||
frameBufferSemaphore = xSemaphoreCreateMutex();
|
frameBufferSemaphore = xSemaphoreCreateMutex();
|
||||||
|
|
||||||
|
PhotoExifData.header = NULL;
|
||||||
|
PhotoExifData.len = 0;
|
||||||
|
PhotoExifData.offset = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -101,6 +106,7 @@ void Camera::InitCameraModule() {
|
||||||
|
|
||||||
/* Camera init */
|
/* Camera init */
|
||||||
err = esp_camera_init(&CameraConfig);
|
err = esp_camera_init(&CameraConfig);
|
||||||
|
|
||||||
if (err != ESP_OK) {
|
if (err != ESP_OK) {
|
||||||
log->AddEvent(LogLevel_Warning, "Camera init failed. Error: " + String(err, HEX));
|
log->AddEvent(LogLevel_Warning, "Camera init failed. Error: " + String(err, HEX));
|
||||||
log->AddEvent(LogLevel_Warning, F("Reset ESP32-cam!"));
|
log->AddEvent(LogLevel_Warning, F("Reset ESP32-cam!"));
|
||||||
|
|
@ -138,6 +144,7 @@ void Camera::LoadCameraCfgFromEeprom() {
|
||||||
exposure_ctrl = config->LoadExposureCtrl();
|
exposure_ctrl = config->LoadExposureCtrl();
|
||||||
CameraFlashEnable = config->LoadCameraFlashEnable();
|
CameraFlashEnable = config->LoadCameraFlashEnable();
|
||||||
CameraFlashTime = config->LoadCameraFlashTime();
|
CameraFlashTime = config->LoadCameraFlashTime();
|
||||||
|
imageExifRotation = config->LoadCameraImageExifRotation();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -250,6 +257,7 @@ void Camera::ReinitCameraModule() {
|
||||||
if (err != ESP_OK) {
|
if (err != ESP_OK) {
|
||||||
log->AddEvent(LogLevel_Warning, "Camera error deinit camera module. Error: " + String(err, HEX));
|
log->AddEvent(LogLevel_Warning, "Camera error deinit camera module. Error: " + String(err, HEX));
|
||||||
}
|
}
|
||||||
|
delay(100);
|
||||||
InitCameraModule();
|
InitCameraModule();
|
||||||
ApplyCameraCfg();
|
ApplyCameraCfg();
|
||||||
}
|
}
|
||||||
|
|
@ -266,6 +274,7 @@ void Camera::CapturePhoto() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CameraCaptureSuccess = false;
|
||||||
/* check flash, and enable FLASH LED */
|
/* check flash, and enable FLASH LED */
|
||||||
if (true == CameraFlashEnable) {
|
if (true == CameraFlashEnable) {
|
||||||
ledcWrite(FLASH_PWM_CHANNEL, FLASH_ON_STATUS);
|
ledcWrite(FLASH_PWM_CHANNEL, FLASH_ON_STATUS);
|
||||||
|
|
@ -277,11 +286,14 @@ void Camera::CapturePhoto() {
|
||||||
if (FrameBuffer) {
|
if (FrameBuffer) {
|
||||||
esp_camera_fb_return(FrameBuffer);
|
esp_camera_fb_return(FrameBuffer);
|
||||||
} else {
|
} else {
|
||||||
|
esp_camera_fb_return(FrameBuffer);
|
||||||
log->AddEvent(LogLevel_Error, F("Camera capture failed training photo"));
|
log->AddEvent(LogLevel_Error, F("Camera capture failed training photo"));
|
||||||
|
//ReinitCameraModule();
|
||||||
}
|
}
|
||||||
|
|
||||||
int attempts = 0;
|
int attempts = 0;
|
||||||
const int maxAttempts = 5;
|
const int maxAttempts = 5;
|
||||||
|
PhotoExifData.header = NULL;
|
||||||
do {
|
do {
|
||||||
log->AddEvent(LogLevel_Info, F("Taking photo..."));
|
log->AddEvent(LogLevel_Info, F("Taking photo..."));
|
||||||
|
|
||||||
|
|
@ -303,6 +315,11 @@ void Camera::CapturePhoto() {
|
||||||
FrameBuffer->len = 0;
|
FrameBuffer->len = 0;
|
||||||
} else {
|
} else {
|
||||||
log->AddEvent(LogLevel_Info, "Photo OK! " + String(ControlFlag, HEX));
|
log->AddEvent(LogLevel_Info, "Photo OK! " + String(ControlFlag, HEX));
|
||||||
|
|
||||||
|
update_exif_from_cfg(imageExifRotation);
|
||||||
|
get_exif_header(FrameBuffer, &PhotoExifData.header, &PhotoExifData.len);
|
||||||
|
PhotoExifData.offset = get_jpeg_data_offset(FrameBuffer);
|
||||||
|
CameraCaptureSuccess = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
attempts++;
|
attempts++;
|
||||||
|
|
@ -318,6 +335,33 @@ void Camera::CapturePhoto() {
|
||||||
ledcWrite(FLASH_PWM_CHANNEL, FLASH_OFF_STATUS);
|
ledcWrite(FLASH_PWM_CHANNEL, FLASH_OFF_STATUS);
|
||||||
}
|
}
|
||||||
xSemaphoreGive(frameBufferSemaphore);
|
xSemaphoreGive(frameBufferSemaphore);
|
||||||
|
/*
|
||||||
|
// Save picture
|
||||||
|
File file = SD_MMC.open("/photo.jpg", FILE_WRITE);
|
||||||
|
|
||||||
|
if (file) {
|
||||||
|
size_t ret = 0;
|
||||||
|
if (PhotoExifData.header != NULL) {
|
||||||
|
ret = file.write(PhotoExifData.header, PhotoExifData.len);
|
||||||
|
if (ret != PhotoExifData.len) {
|
||||||
|
Serial.println("Failed\nError while writing header to file");
|
||||||
|
PhotoExifData.offset = 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
PhotoExifData.offset = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = file.write(&FrameBuffer->buf[PhotoExifData.offset], FrameBuffer->len - PhotoExifData.offset);
|
||||||
|
if (ret != FrameBuffer->len - PhotoExifData.offset) {
|
||||||
|
Serial.println("Failed\nError while writing to file");
|
||||||
|
} else {
|
||||||
|
Serial.printf("Saved as %s\n", "photo.jpg");
|
||||||
|
}
|
||||||
|
file.close();
|
||||||
|
} else {
|
||||||
|
Serial.printf("Failed\nCould not open file: %s\n", "photo.jpg");
|
||||||
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -372,6 +416,10 @@ bool Camera::GetStreamStatus() {
|
||||||
return StreamOnOff;
|
return StreamOnOff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Camera::GetCameraCaptureSuccess() {
|
||||||
|
return CameraCaptureSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@brief Set Frame Size
|
@brief Set Frame Size
|
||||||
@param uint16_t - frame size
|
@param uint16_t - frame size
|
||||||
|
|
@ -440,6 +488,15 @@ camera_fb_t* Camera::GetPhotoFb() {
|
||||||
return FrameBuffer;
|
return FrameBuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief Get Photo Exif Data
|
||||||
|
@param none
|
||||||
|
@return PhotoExifData_t* - photo exif data
|
||||||
|
*/
|
||||||
|
PhotoExifData_t* Camera::GetPhotoExifData() {
|
||||||
|
return &PhotoExifData;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@brief Copy Photo
|
@brief Copy Photo
|
||||||
@param camera_fb_t* - pointer to camera_fb_t
|
@param camera_fb_t* - pointer to camera_fb_t
|
||||||
|
|
@ -741,6 +798,11 @@ void Camera::SetCameraFlashTime(uint16_t i_data) {
|
||||||
CameraFlashTime = i_data;
|
CameraFlashTime = i_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Camera::SetCameraImageRotation(uint8_t i_data) {
|
||||||
|
config->SaveCameraImageExifRotation(i_data);
|
||||||
|
imageExifRotation = i_data;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@brief Get Photo Quality
|
@brief Get Photo Quality
|
||||||
@param none
|
@param none
|
||||||
|
|
@ -1030,4 +1092,8 @@ uint16_t Camera::GetCameraFlashTime() {
|
||||||
return CameraFlashTime;
|
return CameraFlashTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint8_t Camera::GetCameraImageRotation() {
|
||||||
|
return imageExifRotation;
|
||||||
|
}
|
||||||
|
|
||||||
/* EOF */
|
/* EOF */
|
||||||
|
|
@ -19,12 +19,22 @@
|
||||||
#include "soc/rtc_cntl_reg.h"
|
#include "soc/rtc_cntl_reg.h"
|
||||||
#include "cfg.h"
|
#include "cfg.h"
|
||||||
|
|
||||||
|
#include "exif.h"
|
||||||
|
#include "FS.h"
|
||||||
|
#include "SD_MMC.h"
|
||||||
|
|
||||||
#include "Camera_cfg.h"
|
#include "Camera_cfg.h"
|
||||||
#include "Arduino.h"
|
#include "Arduino.h"
|
||||||
#include "mcu_cfg.h"
|
#include "mcu_cfg.h"
|
||||||
#include "var.h"
|
#include "var.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
|
|
||||||
|
struct PhotoExifData_t {
|
||||||
|
const uint8_t *header;
|
||||||
|
size_t len;
|
||||||
|
size_t offset;
|
||||||
|
};
|
||||||
|
|
||||||
class Camera {
|
class Camera {
|
||||||
private:
|
private:
|
||||||
uint8_t PhotoQuality; ///< photo quality
|
uint8_t PhotoQuality; ///< photo quality
|
||||||
|
|
@ -51,6 +61,9 @@ private:
|
||||||
uint16_t CameraFlashTime; ///< camera fash duration time
|
uint16_t CameraFlashTime; ///< camera fash duration time
|
||||||
uint8_t CameraFlashPin; ///< GPIO pin for LED
|
uint8_t CameraFlashPin; ///< GPIO pin for LED
|
||||||
framesize_t TFrameSize; ///< framesize_t type for camera module
|
framesize_t TFrameSize; ///< framesize_t type for camera module
|
||||||
|
uint8_t imageExifRotation; ///< image rotation. 0 degree: value 1, 90 degree: value 6, 180 degree: value 3, 270 degree: value 8
|
||||||
|
|
||||||
|
bool CameraCaptureSuccess; ///< camera capture success
|
||||||
|
|
||||||
/* OV2640 camera module pinout and cfg*/
|
/* OV2640 camera module pinout and cfg*/
|
||||||
camera_config_t CameraConfig; ///< camera configuration
|
camera_config_t CameraConfig; ///< camera configuration
|
||||||
|
|
@ -60,6 +73,7 @@ private:
|
||||||
SemaphoreHandle_t frameBufferSemaphore; ///< semaphore for frame buffer
|
SemaphoreHandle_t frameBufferSemaphore; ///< semaphore for frame buffer
|
||||||
float StreamAverageFps; ///< stream average fps
|
float StreamAverageFps; ///< stream average fps
|
||||||
uint16_t StreamAverageSize; ///< stream average size
|
uint16_t StreamAverageSize; ///< stream average size
|
||||||
|
PhotoExifData_t PhotoExifData; ///< photo exif data
|
||||||
|
|
||||||
Configuration *config; ///< pointer to Configuration object
|
Configuration *config; ///< pointer to Configuration object
|
||||||
Logs *log; ///< pointer to Logs object
|
Logs *log; ///< pointer to Logs object
|
||||||
|
|
@ -78,6 +92,7 @@ public:
|
||||||
void CaptureReturnFrameBuffer();
|
void CaptureReturnFrameBuffer();
|
||||||
void SetStreamStatus(bool);
|
void SetStreamStatus(bool);
|
||||||
bool GetStreamStatus();
|
bool GetStreamStatus();
|
||||||
|
bool GetCameraCaptureSuccess();
|
||||||
|
|
||||||
void StreamSetFrameSize(uint16_t);
|
void StreamSetFrameSize(uint16_t);
|
||||||
void StreamSetFrameFps(float);
|
void StreamSetFrameFps(float);
|
||||||
|
|
@ -92,6 +107,7 @@ public:
|
||||||
int GetPhotoSize();
|
int GetPhotoSize();
|
||||||
String GetPhoto();
|
String GetPhoto();
|
||||||
camera_fb_t *GetPhotoFb();
|
camera_fb_t *GetPhotoFb();
|
||||||
|
PhotoExifData_t * GetPhotoExifData();
|
||||||
framesize_t TransformFrameSizeDataType(uint8_t);
|
framesize_t TransformFrameSizeDataType(uint8_t);
|
||||||
|
|
||||||
void SetFlashStatus(bool);
|
void SetFlashStatus(bool);
|
||||||
|
|
@ -119,6 +135,7 @@ public:
|
||||||
void SetExposureCtrl(bool);
|
void SetExposureCtrl(bool);
|
||||||
void SetCameraFlashEnable(bool);
|
void SetCameraFlashEnable(bool);
|
||||||
void SetCameraFlashTime(uint16_t);
|
void SetCameraFlashTime(uint16_t);
|
||||||
|
void SetCameraImageRotation(uint8_t);
|
||||||
|
|
||||||
uint8_t GetPhotoQuality();
|
uint8_t GetPhotoQuality();
|
||||||
uint8_t GetFrameSize();
|
uint8_t GetFrameSize();
|
||||||
|
|
@ -144,6 +161,7 @@ public:
|
||||||
bool GetExposureCtrl();
|
bool GetExposureCtrl();
|
||||||
bool GetCameraFlashEnable();
|
bool GetCameraFlashEnable();
|
||||||
uint16_t GetCameraFlashTime();
|
uint16_t GetCameraFlashTime();
|
||||||
|
uint8_t GetCameraImageRotation();
|
||||||
};
|
};
|
||||||
|
|
||||||
extern Camera SystemCamera; ///< Camera object
|
extern Camera SystemCamera; ///< Camera object
|
||||||
|
|
|
||||||
|
|
@ -166,6 +166,7 @@ void Configuration::DefaultCfg() {
|
||||||
SaveNetworkMask(FACTORY_CFG_NETWORK_STATIC_MASK);
|
SaveNetworkMask(FACTORY_CFG_NETWORK_STATIC_MASK);
|
||||||
SaveNetworkGateway(FACTORY_CFG_NETWORK_STATIC_GATEWAY);
|
SaveNetworkGateway(FACTORY_CFG_NETWORK_STATIC_GATEWAY);
|
||||||
SaveNetworkDns(FACTORY_CFG_NETWORK_STATIC_DNS);
|
SaveNetworkDns(FACTORY_CFG_NETWORK_STATIC_DNS);
|
||||||
|
SaveCameraImageExifRotation(FACTORY_CFG_IMAGE_EXIF_ROTATION);
|
||||||
Log->AddEvent(LogLevel_Warning, F("+++++++++++++++++++++++++++"));
|
Log->AddEvent(LogLevel_Warning, F("+++++++++++++++++++++++++++"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -823,6 +824,16 @@ void Configuration::SaveNetworkDns(String i_data) {
|
||||||
SaveIpAddress(EEPROM_ADDR_NETWORK_STATIC_DNS_START, i_data);
|
SaveIpAddress(EEPROM_ADDR_NETWORK_STATIC_DNS_START, i_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@info Save camera image rotation
|
||||||
|
@param uint8_t - value
|
||||||
|
@return none
|
||||||
|
*/
|
||||||
|
void Configuration::SaveCameraImageExifRotation(uint8_t i_data) {
|
||||||
|
Log->AddEvent(LogLevel_Verbose, "Save camera image exif rotation: " + String(i_data));
|
||||||
|
SaveUint8(EEPROM_ADDR_IMAGE_ROTATION_START, i_data);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@info load refresh interval from eeprom
|
@info load refresh interval from eeprom
|
||||||
@param none
|
@param none
|
||||||
|
|
@ -1300,4 +1311,17 @@ String Configuration::LoadNetworkDns() {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint8_t Configuration::LoadCameraImageExifRotation() {
|
||||||
|
uint8_t ret = EEPROM.read(EEPROM_ADDR_IMAGE_ROTATION_START);
|
||||||
|
|
||||||
|
/* check if value is 255. When value is 255, then set default value */
|
||||||
|
if (ret == 255) {
|
||||||
|
ret = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log->AddEvent(LogLevel_Info, "Camera image rotation: " + String(ret));
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
/* EOF */
|
/* EOF */
|
||||||
|
|
@ -71,6 +71,7 @@ public:
|
||||||
void SaveNetworkMask(String);
|
void SaveNetworkMask(String);
|
||||||
void SaveNetworkGateway(String);
|
void SaveNetworkGateway(String);
|
||||||
void SaveNetworkDns(String);
|
void SaveNetworkDns(String);
|
||||||
|
void SaveCameraImageExifRotation(uint8_t);
|
||||||
|
|
||||||
uint8_t LoadRefreshInterval();
|
uint8_t LoadRefreshInterval();
|
||||||
String LoadToken();
|
String LoadToken();
|
||||||
|
|
@ -111,6 +112,7 @@ public:
|
||||||
String LoadNetworkMask();
|
String LoadNetworkMask();
|
||||||
String LoadNetworkGateway();
|
String LoadNetworkGateway();
|
||||||
String LoadNetworkDns();
|
String LoadNetworkDns();
|
||||||
|
uint8_t LoadCameraImageExifRotation();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Logs *Log; ///< Pointer to Logs object
|
Logs *Log; ///< Pointer to Logs object
|
||||||
|
|
|
||||||
|
|
@ -122,9 +122,22 @@ bool PrusaConnect::SendDataToBackend(String *i_data, int i_data_length, String i
|
||||||
if (SendPhoto == i_data_type) {
|
if (SendPhoto == i_data_type) {
|
||||||
log->AddEvent(LogLevel_Verbose, F("Sendig photo"));
|
log->AddEvent(LogLevel_Verbose, F("Sendig photo"));
|
||||||
|
|
||||||
/* sending photo */
|
/* get photo buffer */
|
||||||
|
bool SendWithExif = false;
|
||||||
uint8_t *fbBuf = camera->GetPhotoFb()->buf;
|
uint8_t *fbBuf = camera->GetPhotoFb()->buf;
|
||||||
size_t fbLen = camera->GetPhotoFb()->len;
|
size_t fbLen = camera->GetPhotoFb()->len;
|
||||||
|
|
||||||
|
/* sending exif data */
|
||||||
|
if (camera->GetPhotoExifData()->header != NULL) {
|
||||||
|
SendWithExif = true;
|
||||||
|
sendet_data += client.write(camera->GetPhotoExifData()->header, camera->GetPhotoExifData()->len);
|
||||||
|
fbBuf += camera->GetPhotoExifData()->offset;
|
||||||
|
fbLen -= camera->GetPhotoExifData()->offset;
|
||||||
|
} else {
|
||||||
|
SendWithExif = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* sending photo */
|
||||||
for (size_t i=0; i < fbLen; i += PHOTO_FRAGMENT_SIZE) {
|
for (size_t i=0; i < fbLen; i += PHOTO_FRAGMENT_SIZE) {
|
||||||
if ((i + PHOTO_FRAGMENT_SIZE) < fbLen) {
|
if ((i + PHOTO_FRAGMENT_SIZE) < fbLen) {
|
||||||
sendet_data += client.write(fbBuf, PHOTO_FRAGMENT_SIZE);
|
sendet_data += client.write(fbBuf, PHOTO_FRAGMENT_SIZE);
|
||||||
|
|
@ -138,6 +151,13 @@ bool PrusaConnect::SendDataToBackend(String *i_data, int i_data_length, String i
|
||||||
client.println("\r\n");
|
client.println("\r\n");
|
||||||
client.flush();
|
client.flush();
|
||||||
|
|
||||||
|
/* log message */
|
||||||
|
if (SendWithExif) {
|
||||||
|
SystemLog.AddEvent(LogLevel_Verbose, F("Photo with EXIF data sent"));
|
||||||
|
} else {
|
||||||
|
SystemLog.AddEvent(LogLevel_Warning, F("Photo without EXIF data sent"));
|
||||||
|
}
|
||||||
|
|
||||||
/* sending device information */
|
/* sending device information */
|
||||||
} else if (SendInfo == i_data_type) {
|
} else if (SendInfo == i_data_type) {
|
||||||
log->AddEvent(LogLevel_Verbose, F("Sending info"));
|
log->AddEvent(LogLevel_Verbose, F("Sending info"));
|
||||||
|
|
@ -200,7 +220,14 @@ bool PrusaConnect::SendDataToBackend(String *i_data, int i_data_length, String i
|
||||||
void PrusaConnect::SendPhotoToBackend() {
|
void PrusaConnect::SendPhotoToBackend() {
|
||||||
log->AddEvent(LogLevel_Info, F("Start sending photo to prusaconnect"));
|
log->AddEvent(LogLevel_Info, F("Start sending photo to prusaconnect"));
|
||||||
String Photo = "";
|
String Photo = "";
|
||||||
SendDataToBackend(&Photo, camera->GetPhotoFb()->len, "image/jpg", "Photo", HOST_URL_CAM_PATH, SendPhoto);
|
size_t total_len = 0;
|
||||||
|
|
||||||
|
if (camera->GetPhotoExifData()->header != NULL) {
|
||||||
|
total_len = camera->GetPhotoExifData()->len + camera->GetPhotoFb()->len - camera->GetPhotoExifData()->offset;
|
||||||
|
} else {
|
||||||
|
total_len = camera->GetPhotoFb()->len;
|
||||||
|
}
|
||||||
|
SendDataToBackend(&Photo, total_len, "image/jpg", "Photo", HOST_URL_CAM_PATH, SendPhoto);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -247,7 +274,11 @@ void PrusaConnect::SendInfoToBackend() {
|
||||||
*/
|
*/
|
||||||
void PrusaConnect::TakePictureAndSendToBackend() {
|
void PrusaConnect::TakePictureAndSendToBackend() {
|
||||||
camera->CapturePhoto();
|
camera->CapturePhoto();
|
||||||
|
if (camera->GetCameraCaptureSuccess() == true) {
|
||||||
SendPhotoToBackend();
|
SendPhotoToBackend();
|
||||||
|
} else {
|
||||||
|
log->AddEvent(LogLevel_Error, F("Error capturing photo. Stop sending to backend!"));
|
||||||
|
}
|
||||||
camera->CaptureReturnFrameBuffer();
|
camera->CaptureReturnFrameBuffer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,452 @@
|
||||||
|
/**
|
||||||
|
* exif.c - Functions to generate Exif metadata
|
||||||
|
*
|
||||||
|
* Copyright (c) 2019, David Imhoff <dimhoff.devel@gmail.com>
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
* * Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* * Neither the name of the author nor the names of its contributors may
|
||||||
|
* be used to endorse or promote products derived from this software
|
||||||
|
* without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
|
||||||
|
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
||||||
|
* EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
|
||||||
|
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||||
|
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
||||||
|
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||||
|
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <time.h>
|
||||||
|
#include <sys/time.h>
|
||||||
|
|
||||||
|
#include "exif.h"
|
||||||
|
#include "exif_defines.h"
|
||||||
|
|
||||||
|
// TIFF header byte order
|
||||||
|
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||||
|
# define TIFF_BYTE_ORDER 0x4949
|
||||||
|
#else // __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||||
|
# define TIFF_BYTE_ORDER 0x4d4d
|
||||||
|
#endif // __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||||
|
|
||||||
|
// Reimplementation of htons() that can be used to initialize a struct member.
|
||||||
|
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||||
|
# define htons_macro(x) \
|
||||||
|
(((x) & 0x00ff) << 8 | \
|
||||||
|
((x) & 0xff00) >> 8)
|
||||||
|
#else // __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||||
|
# define htons_macro(x) (x)
|
||||||
|
#endif // __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type for storing Tiff Rational typed data
|
||||||
|
*/
|
||||||
|
#pragma pack(1)
|
||||||
|
typedef struct {
|
||||||
|
uint32_t num;
|
||||||
|
uint32_t denom;
|
||||||
|
} TiffRational;
|
||||||
|
#pragma pack()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type used for IFD entries
|
||||||
|
*
|
||||||
|
* This type is used to store a value within the Tiff IFD.
|
||||||
|
*/
|
||||||
|
#pragma pack(1)
|
||||||
|
typedef struct {
|
||||||
|
uint16_t tag; // Data tag
|
||||||
|
uint16_t type; // Data type
|
||||||
|
uint32_t length; // length of data
|
||||||
|
// Offset of data from start of TIFF data, or data it self if length <= 4.
|
||||||
|
// Always use the IFD_SET_*() macros to modify the value.
|
||||||
|
uint32_t value;
|
||||||
|
} IfdEntry;
|
||||||
|
#pragma pack()
|
||||||
|
|
||||||
|
// Some helper macros to initialize the IFD value's. The types shorter then
|
||||||
|
// 4-bytes need to be aligned such that they are directly after the previous
|
||||||
|
// field. These macros take care of this.
|
||||||
|
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||||
|
# define IFD_SET_BYTE(VAL) (VAL)
|
||||||
|
# define IFD_SET_SHORT(VAL) (VAL)
|
||||||
|
# define IFD_SET_LONG(VAL) (VAL)
|
||||||
|
# define IFD_SET_OFFSET(REF, VAR) offsetof(REF, VAR)
|
||||||
|
# define IFD_SET_UNDEF(V0, V1, V2, V3) \
|
||||||
|
(((V3) & 0xff) << 24 | \
|
||||||
|
((V2) & 0xff) << 16 | \
|
||||||
|
((V1) & 0xff) << 8 | \
|
||||||
|
((V0) & 0xff))
|
||||||
|
#else // __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||||
|
# define IFD_SET_BYTE(VAL) (((VAL) & 0xff) << 24)
|
||||||
|
# define IFD_SET_SHORT(VAL) (((VAL) & 0xffff) << 16)
|
||||||
|
# define IFD_SET_LONG(VAL) (VAL)
|
||||||
|
# define IFD_SET_OFFSET(REF, VAR) offsetof(REF, VAR)
|
||||||
|
# define IFD_SET_UNDEF(V0, V1, V2, V3) \
|
||||||
|
(((V0) & 0xff) << 24 | \
|
||||||
|
((V1) & 0xff) << 16 | \
|
||||||
|
((V2) & 0xff) << 8 | \
|
||||||
|
((V3) & 0xff))
|
||||||
|
#endif // __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||||
|
|
||||||
|
// Amount of entries in the 0th IFD
|
||||||
|
#ifdef WITH_GNSS
|
||||||
|
# define IFD0_ENTRY_CNT 11
|
||||||
|
#else
|
||||||
|
# define IFD0_ENTRY_CNT 12
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Amount of entries in the Exif private IFD
|
||||||
|
#define IFD_EXIF_ENTRY_CNT 6
|
||||||
|
|
||||||
|
// Amount of entries in the GPS private IFD
|
||||||
|
#define IFD_GPS_ENTRY_CNT 12
|
||||||
|
|
||||||
|
// GPS map datum, probably always 'WGS-84'
|
||||||
|
#define GPS_MAP_DATUM "WGS-84"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* New Jpeg/Exif header
|
||||||
|
*
|
||||||
|
* This defines the new JPEG/Exif header that is added to the images. To keep
|
||||||
|
* it simple, the structure of the header is completely static.
|
||||||
|
*/
|
||||||
|
#pragma pack(1)
|
||||||
|
struct JpegExifHdr {
|
||||||
|
uint16_t jpeg_soi; // htons(0xffd8)
|
||||||
|
uint16_t marker; // htons(0xffe1)
|
||||||
|
uint16_t len; // htons(length)
|
||||||
|
uint8_t exif_identifier[6]; // 'Exif\0\0'
|
||||||
|
|
||||||
|
struct TiffData {
|
||||||
|
struct {
|
||||||
|
uint16_t byte_order; // 'II' || 'MM'
|
||||||
|
uint16_t signature; // 0x002A
|
||||||
|
uint32_t ifd_offset; // 0x8
|
||||||
|
} tiff_hdr;
|
||||||
|
struct {
|
||||||
|
uint16_t cnt; // amount of entries
|
||||||
|
IfdEntry entries[IFD0_ENTRY_CNT];
|
||||||
|
uint32_t next_ifd; // Offset of next IFD, or 0x0 if last IFD
|
||||||
|
} ifd0;
|
||||||
|
struct {
|
||||||
|
TiffRational XYResolution;
|
||||||
|
char make[sizeof(CAMERA_MAKE)];
|
||||||
|
char model[sizeof(CAMERA_MODEL)];
|
||||||
|
char software[sizeof(CAMERA_SOFTWARE)];
|
||||||
|
char datetime[20];
|
||||||
|
} ifd0_data;
|
||||||
|
struct {
|
||||||
|
uint16_t cnt; // amount of entries
|
||||||
|
IfdEntry entries[IFD_EXIF_ENTRY_CNT];
|
||||||
|
uint32_t next_ifd; // Offset of next IFD, or 0x0 if last IFD
|
||||||
|
} ifd_exif;
|
||||||
|
#ifdef WITH_GNSS
|
||||||
|
struct {
|
||||||
|
uint16_t cnt; // amount of entries
|
||||||
|
IfdEntry entries[IFD_GPS_ENTRY_CNT];
|
||||||
|
uint32_t next_ifd; // Offset of next IFD, or 0x0 if last IFD
|
||||||
|
} ifd_gps;
|
||||||
|
struct {
|
||||||
|
TiffRational latitude[3];
|
||||||
|
TiffRational longitude[3];
|
||||||
|
TiffRational altitude;
|
||||||
|
TiffRational time_stamp[3];
|
||||||
|
char map_datum[sizeof(GPS_MAP_DATUM)];
|
||||||
|
uint8_t processing_method[8 + 3];
|
||||||
|
char date_stamp[11];
|
||||||
|
} ifd_gps_data;
|
||||||
|
#endif //WITH_GNSS
|
||||||
|
} tiff_data;
|
||||||
|
} exif_hdr = {
|
||||||
|
htons_macro(0xffd8),
|
||||||
|
htons_macro(0xffe1),
|
||||||
|
htons_macro(sizeof(JpegExifHdr) - offsetof(JpegExifHdr, len)),
|
||||||
|
{ 'E', 'x', 'i', 'f', 0, 0 },
|
||||||
|
{
|
||||||
|
.tiff_hdr = { TIFF_BYTE_ORDER, 0x002A, 0x8 },
|
||||||
|
.ifd0 = {
|
||||||
|
.cnt = IFD0_ENTRY_CNT,
|
||||||
|
.entries = {
|
||||||
|
{ TagTiffMake,
|
||||||
|
TiffTypeAscii,
|
||||||
|
sizeof(exif_hdr.tiff_data.ifd0_data.make),
|
||||||
|
IFD_SET_OFFSET(JpegExifHdr::TiffData, ifd0_data.make) },
|
||||||
|
{ TagTiffModel,
|
||||||
|
TiffTypeAscii,
|
||||||
|
sizeof(exif_hdr.tiff_data.ifd0_data.model),
|
||||||
|
IFD_SET_OFFSET(JpegExifHdr::TiffData, ifd0_data.model) },
|
||||||
|
#define TAG_IFD0_ORIENTATION_IDX 2
|
||||||
|
{ TagTiffOrientation,
|
||||||
|
TiffTypeShort, 1,
|
||||||
|
IFD_SET_SHORT(1) },
|
||||||
|
{ TagTiffXResolution,
|
||||||
|
TiffTypeRational, 1,
|
||||||
|
IFD_SET_OFFSET(JpegExifHdr::TiffData, ifd0_data) },
|
||||||
|
{ TagTiffYResolution,
|
||||||
|
TiffTypeRational, 1,
|
||||||
|
IFD_SET_OFFSET(JpegExifHdr::TiffData, ifd0_data) },
|
||||||
|
{ TagTiffResolutionUnit,
|
||||||
|
TiffTypeShort, 1,
|
||||||
|
IFD_SET_SHORT(0x0002) },
|
||||||
|
{ TagTiffSoftware,
|
||||||
|
TiffTypeAscii,
|
||||||
|
sizeof(exif_hdr.tiff_data.ifd0_data.software),
|
||||||
|
IFD_SET_OFFSET(JpegExifHdr::TiffData, ifd0_data.software) },
|
||||||
|
{ TagTiffDateTime,
|
||||||
|
TiffTypeAscii,
|
||||||
|
sizeof(exif_hdr.tiff_data.ifd0_data.datetime),
|
||||||
|
IFD_SET_OFFSET(JpegExifHdr::TiffData, ifd0_data.datetime) },
|
||||||
|
{ TagTiffYCbCrPositioning,
|
||||||
|
TiffTypeShort, 1,
|
||||||
|
IFD_SET_SHORT(0x0001) },
|
||||||
|
{ TagTiffExifIFD,
|
||||||
|
TiffTypeLong, 1,
|
||||||
|
IFD_SET_OFFSET(JpegExifHdr::TiffData, ifd_exif) },
|
||||||
|
#ifdef WITH_GNSS
|
||||||
|
{ TagTiffGPSIFD,
|
||||||
|
TiffTypeLong, 1,
|
||||||
|
IFD_SET_OFFSET(JpegExifHdr::TiffData, ifd_gps) },
|
||||||
|
#endif // WITH_GNSS
|
||||||
|
},
|
||||||
|
.next_ifd = 0
|
||||||
|
},
|
||||||
|
.ifd0_data = {
|
||||||
|
{ 72, 1 },
|
||||||
|
CAMERA_MAKE,
|
||||||
|
CAMERA_MODEL,
|
||||||
|
CAMERA_SOFTWARE,
|
||||||
|
" : : : : ",
|
||||||
|
},
|
||||||
|
.ifd_exif = {
|
||||||
|
.cnt = IFD_EXIF_ENTRY_CNT,
|
||||||
|
.entries = {
|
||||||
|
{ TagExifVersion,
|
||||||
|
TiffTypeUndef, 4,
|
||||||
|
IFD_SET_UNDEF(0x30, 0x32, 0x33, 0x30) },
|
||||||
|
{ TagExifComponentsConfiguration,
|
||||||
|
TiffTypeUndef, 4,
|
||||||
|
IFD_SET_UNDEF(0x01, 0x02, 0x03, 0x00) },
|
||||||
|
#define TAG_EXIF_SUBSEC_TIME_IDX 2
|
||||||
|
{ TagExifSubSecTime,
|
||||||
|
TiffTypeAscii, 4,
|
||||||
|
IFD_SET_UNDEF(0x20, 0x20, 0x20, 0x00) },
|
||||||
|
{ TagExifColorSpace,
|
||||||
|
TiffTypeShort, 1,
|
||||||
|
IFD_SET_SHORT(1) },
|
||||||
|
#define TAG_EXIF_PIXEL_X_DIMENSION_IDX 4
|
||||||
|
{ TagExifPixelXDimension,
|
||||||
|
TiffTypeShort, 1,
|
||||||
|
IFD_SET_SHORT(1600) },
|
||||||
|
#define TAG_EXIF_PIXEL_Y_DIMENSION_IDX (TAG_EXIF_PIXEL_X_DIMENSION_IDX + 1)
|
||||||
|
{ TagExifPixelYDimension,
|
||||||
|
TiffTypeShort, 1,
|
||||||
|
IFD_SET_SHORT(1200) },
|
||||||
|
},
|
||||||
|
.next_ifd = 0
|
||||||
|
},
|
||||||
|
#ifdef WITH_GNSS
|
||||||
|
.ifd_gps = {
|
||||||
|
.cnt = IFD_GPS_ENTRY_CNT,
|
||||||
|
.entries = {
|
||||||
|
{ TagGPSVersionID,
|
||||||
|
TiffTypeByte, 4,
|
||||||
|
IFD_SET_UNDEF(2, 3, 0, 0) },
|
||||||
|
#define TAG_GPS_LATITUDE_REF_IDX 1
|
||||||
|
{ TagGPSLatitudeRef,
|
||||||
|
TiffTypeAscii, 2,
|
||||||
|
IFD_SET_UNDEF(0x00, 0x00, 0x00, 0x00) },
|
||||||
|
{ TagGPSLatitude,
|
||||||
|
TiffTypeRational, 3,
|
||||||
|
IFD_SET_OFFSET(JpegExifHdr::TiffData, ifd_gps_data.latitude) },
|
||||||
|
#define TAG_GPS_LONGITUDE_REF_IDX 3
|
||||||
|
{ TagGPSLongitudeRef,
|
||||||
|
TiffTypeAscii, 2,
|
||||||
|
IFD_SET_UNDEF(0x00, 0x00, 0x00, 0x00) },
|
||||||
|
{ TagGPSLongitude,
|
||||||
|
TiffTypeRational, 3,
|
||||||
|
IFD_SET_OFFSET(JpegExifHdr::TiffData, ifd_gps_data.longitude) },
|
||||||
|
#define TAG_GPS_ALTITUDE_REF_IDX 5
|
||||||
|
{ TagGPSAltitudeRef,
|
||||||
|
TiffTypeByte, 1,
|
||||||
|
IFD_SET_UNDEF(0x00, 0x00, 0x00, 0x00) },
|
||||||
|
{ TagGPSAltitude,
|
||||||
|
TiffTypeRational, 1,
|
||||||
|
IFD_SET_OFFSET(JpegExifHdr::TiffData, ifd_gps_data.altitude) },
|
||||||
|
{ TagGPSTimeStamp,
|
||||||
|
TiffTypeRational, 3,
|
||||||
|
IFD_SET_OFFSET(JpegExifHdr::TiffData, ifd_gps_data.time_stamp) },
|
||||||
|
#define TAG_GPS_STATUS_IDX 8
|
||||||
|
{ TagGPSStatus,
|
||||||
|
TiffTypeAscii, 2,
|
||||||
|
IFD_SET_UNDEF('V', 0x00, 0x00, 0x00) },
|
||||||
|
/*TODO: currently not available from MicroNMEA
|
||||||
|
{ TagGPSMeasureMode,
|
||||||
|
TiffTypeAscii, 2,
|
||||||
|
IFD_SET_UNDEF('2', 0x00, 0x00, 0x00) },*/
|
||||||
|
{ TagGPSMapDatum,
|
||||||
|
TiffTypeAscii,
|
||||||
|
sizeof(exif_hdr.tiff_data.ifd_gps_data.map_datum),
|
||||||
|
IFD_SET_OFFSET(JpegExifHdr::TiffData, ifd_gps_data.map_datum) },
|
||||||
|
{ TagGPSProcessingMethod,
|
||||||
|
TiffTypeUndef,
|
||||||
|
sizeof(exif_hdr.tiff_data.ifd_gps_data.processing_method),
|
||||||
|
IFD_SET_OFFSET(JpegExifHdr::TiffData, ifd_gps_data.processing_method) },
|
||||||
|
{ TagGPSDateStamp,
|
||||||
|
TiffTypeAscii,
|
||||||
|
sizeof(exif_hdr.tiff_data.ifd_gps_data.date_stamp),
|
||||||
|
IFD_SET_OFFSET(JpegExifHdr::TiffData, ifd_gps_data.date_stamp) },
|
||||||
|
},
|
||||||
|
.next_ifd = 0
|
||||||
|
},
|
||||||
|
.ifd_gps_data = {
|
||||||
|
{ { 0, 1000*1000 }, { 0, 1 }, { 0, 1 } },
|
||||||
|
{ { 0, 1000*1000 }, { 0, 1 }, { 0, 1 } },
|
||||||
|
{ 0, 1000 },
|
||||||
|
{ { 0, 1 }, { 0, 1 }, { 0, 1 } },
|
||||||
|
GPS_MAP_DATUM,
|
||||||
|
{ 0x41, 0x53, 0x43, 0x49, 0x49, 0x00, 0x00, 0x00, 'G', 'P', 'S' },
|
||||||
|
" : : ",
|
||||||
|
}
|
||||||
|
#endif // WITH_GNSS
|
||||||
|
}
|
||||||
|
};
|
||||||
|
#pragma pack()
|
||||||
|
|
||||||
|
bool update_exif_from_cfg(const uint8_t c)
|
||||||
|
{
|
||||||
|
exif_hdr.tiff_data.ifd0.entries[TAG_IFD0_ORIENTATION_IDX].value = IFD_SET_SHORT(c);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef WITH_GNSS
|
||||||
|
void update_exif_gps(const MicroNMEA& nmea)
|
||||||
|
{
|
||||||
|
// Latitude
|
||||||
|
long lat = nmea.getLatitude();
|
||||||
|
if (lat < 0) {
|
||||||
|
exif_hdr.tiff_data.ifd_gps.entries[TAG_GPS_LATITUDE_REF_IDX].value = IFD_SET_BYTE('S');
|
||||||
|
lat *= -1;
|
||||||
|
} else {
|
||||||
|
exif_hdr.tiff_data.ifd_gps.entries[TAG_GPS_LATITUDE_REF_IDX].value = IFD_SET_BYTE('N');
|
||||||
|
}
|
||||||
|
exif_hdr.tiff_data.ifd_gps_data.latitude[0].num = lat;
|
||||||
|
|
||||||
|
// Longitude
|
||||||
|
long lon = nmea.getLongitude();
|
||||||
|
if (lon < 0) {
|
||||||
|
exif_hdr.tiff_data.ifd_gps.entries[TAG_GPS_LONGITUDE_REF_IDX].value = IFD_SET_BYTE('W');
|
||||||
|
lon *= -1;
|
||||||
|
} else {
|
||||||
|
exif_hdr.tiff_data.ifd_gps.entries[TAG_GPS_LONGITUDE_REF_IDX].value = IFD_SET_BYTE('E');
|
||||||
|
}
|
||||||
|
exif_hdr.tiff_data.ifd_gps_data.longitude[0].num = lon;
|
||||||
|
|
||||||
|
// Altitude
|
||||||
|
long alt;
|
||||||
|
if (nmea.getAltitude(alt)) {
|
||||||
|
if (alt < 0) {
|
||||||
|
exif_hdr.tiff_data.ifd_gps.entries[TAG_GPS_ALTITUDE_REF_IDX].value = IFD_SET_BYTE(1);
|
||||||
|
alt *= -1;
|
||||||
|
} else {
|
||||||
|
exif_hdr.tiff_data.ifd_gps.entries[TAG_GPS_ALTITUDE_REF_IDX].value = IFD_SET_BYTE(0);
|
||||||
|
}
|
||||||
|
exif_hdr.tiff_data.ifd_gps_data.altitude.num = alt;
|
||||||
|
} else {
|
||||||
|
exif_hdr.tiff_data.ifd_gps_data.altitude.num = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// time stamp
|
||||||
|
exif_hdr.tiff_data.ifd_gps_data.time_stamp[0].num = nmea.getHour();
|
||||||
|
exif_hdr.tiff_data.ifd_gps_data.time_stamp[1].num = nmea.getMinute();
|
||||||
|
exif_hdr.tiff_data.ifd_gps_data.time_stamp[2].num = nmea.getSecond();
|
||||||
|
|
||||||
|
// GPS Status
|
||||||
|
if (nmea.isValid()) {
|
||||||
|
exif_hdr.tiff_data.ifd_gps.entries[TAG_GPS_STATUS_IDX].value = IFD_SET_BYTE('A');
|
||||||
|
} else {
|
||||||
|
exif_hdr.tiff_data.ifd_gps.entries[TAG_GPS_STATUS_IDX].value = IFD_SET_BYTE('V');
|
||||||
|
}
|
||||||
|
|
||||||
|
// date stamp
|
||||||
|
snprintf(exif_hdr.tiff_data.ifd_gps_data.date_stamp,
|
||||||
|
sizeof(exif_hdr.tiff_data.ifd_gps_data.date_stamp),
|
||||||
|
"%04u:%02u:%02u",
|
||||||
|
nmea.getYear(),
|
||||||
|
nmea.getMonth(),
|
||||||
|
nmea.getDay());
|
||||||
|
}
|
||||||
|
#endif // WITH_GNSS
|
||||||
|
|
||||||
|
const uint8_t *get_exif_header(camera_fb_t *fb, const uint8_t **exif_buf, size_t *exif_len)
|
||||||
|
{
|
||||||
|
// TODO: pass config to function and use that to set some of the image
|
||||||
|
// taking conditions. Or do this only once, with a update config
|
||||||
|
// function????
|
||||||
|
|
||||||
|
// Get current time
|
||||||
|
struct timeval now_tv;
|
||||||
|
if (gettimeofday(&now_tv, NULL) != 0) {
|
||||||
|
now_tv.tv_sec = time(NULL);
|
||||||
|
now_tv.tv_usec = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set date time
|
||||||
|
struct tm timeinfo;
|
||||||
|
localtime_r(&now_tv.tv_sec, &timeinfo);
|
||||||
|
strftime(exif_hdr.tiff_data.ifd0_data.datetime,
|
||||||
|
sizeof(exif_hdr.tiff_data.ifd0_data.datetime),
|
||||||
|
"%Y:%m:%d %H:%M:%S", &timeinfo);
|
||||||
|
|
||||||
|
// Set sub-seconds time
|
||||||
|
snprintf( (char *) &(exif_hdr.tiff_data.ifd_exif.entries[TAG_EXIF_SUBSEC_TIME_IDX].value), 9, "%03ld", now_tv.tv_usec/1000);
|
||||||
|
|
||||||
|
// Update image dimensions
|
||||||
|
exif_hdr.tiff_data.ifd_exif.entries[TAG_EXIF_PIXEL_X_DIMENSION_IDX].value = IFD_SET_SHORT(fb->width);
|
||||||
|
exif_hdr.tiff_data.ifd_exif.entries[TAG_EXIF_PIXEL_Y_DIMENSION_IDX].value = IFD_SET_SHORT(fb->height);
|
||||||
|
|
||||||
|
*exif_len = sizeof(exif_hdr);
|
||||||
|
|
||||||
|
if (exif_buf != NULL) {
|
||||||
|
*exif_buf = (uint8_t *) &exif_hdr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (uint8_t *) &exif_hdr;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t get_jpeg_data_offset(camera_fb_t *fb)
|
||||||
|
{
|
||||||
|
if (fb->len < 6) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check JPEG SOI
|
||||||
|
if (fb->buf[0] != 0xff || fb->buf[1] != 0xd8) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
size_t data_offset = 2; // Offset to first JPEG segment after header
|
||||||
|
|
||||||
|
// Check if JFIF header
|
||||||
|
if (fb->buf[2] == 0xff && fb->buf[3] == 0xe0) {
|
||||||
|
uint16_t jfif_len = fb->buf[4] << 8 | fb->buf[5];
|
||||||
|
|
||||||
|
data_offset += 2 + jfif_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data_offset >= fb->len) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return data_offset;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,91 @@
|
||||||
|
/**
|
||||||
|
* exif.h - Functions to generate Exif metadata
|
||||||
|
*
|
||||||
|
* Copyright (c) 2019, David Imhoff <dimhoff.devel@gmail.com>
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
* * Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* * Neither the name of the author nor the names of its contributors may
|
||||||
|
* be used to endorse or promote products derived from this software
|
||||||
|
* without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
|
||||||
|
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
||||||
|
* EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
|
||||||
|
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||||
|
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
||||||
|
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||||
|
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
* Project URL: https://github.com/dimhoff/ESP32-CAM_Interval
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#ifndef __EXIF_H__
|
||||||
|
#define __EXIF_H__
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include "esp_camera.h"
|
||||||
|
#ifdef WITH_GNSS
|
||||||
|
# include <MicroNMEA.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "mcu_cfg.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the configurable EXIF header fields
|
||||||
|
*
|
||||||
|
* Update all field in the EXIF header that depend on the configuration. Needs
|
||||||
|
* to be called every time the configuration is changed.
|
||||||
|
*
|
||||||
|
* @returns True on success, else false
|
||||||
|
*/
|
||||||
|
bool update_exif_from_cfg(const uint8_t);
|
||||||
|
|
||||||
|
#ifdef WITH_GNSS
|
||||||
|
/**
|
||||||
|
* Update GPS data in EXIF header
|
||||||
|
*
|
||||||
|
* Should be called for every call to get_exif_header() if GNSS support is enabled.
|
||||||
|
*/
|
||||||
|
void update_exif_gps(const MicroNMEA& nmea);
|
||||||
|
#endif // WITH_GNSS
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Exif header
|
||||||
|
*
|
||||||
|
* Get a buffer with a new Exif header that can be prepended to a JPEG image.
|
||||||
|
* The JPEG does need to be stripped from its original header first, see
|
||||||
|
* get_jpeg_data_offset().
|
||||||
|
*
|
||||||
|
* The returned pointer point so a static buffer, and should not be altered.
|
||||||
|
* This function is not reentrant safe.
|
||||||
|
*
|
||||||
|
* @param fb Frame buffer of captured image. Encoding is expected to
|
||||||
|
* be JPEG
|
||||||
|
* @param exif_buf If not NULL, used to return pointer to Exif buffer in
|
||||||
|
* @param exif_buf Used to return the size of the Exif buffer
|
||||||
|
*
|
||||||
|
* @returns Pointer to Exif buffer, or NULL on error
|
||||||
|
*/
|
||||||
|
const uint8_t *get_exif_header(camera_fb_t *fb, const uint8_t **exif_buf, size_t *exif_len);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get offset of first none header byte in buffer
|
||||||
|
*
|
||||||
|
* Get the offset of the first none JPEG header byte in capture buffer. This
|
||||||
|
* can be used to strip the JPEG SOI and JFIF headers from an image.
|
||||||
|
*
|
||||||
|
* @returns offset of first non header byte, or 0 on error
|
||||||
|
*/
|
||||||
|
size_t get_jpeg_data_offset(camera_fb_t *fb);
|
||||||
|
|
||||||
|
#endif // __EXIF_H__
|
||||||
|
|
@ -0,0 +1,214 @@
|
||||||
|
/**
|
||||||
|
* exif_defines.h - Defines for constants from Exif v2.3 standard
|
||||||
|
*
|
||||||
|
* Written 2019, David Imhoff <dimhoff.devel@gmail.com>
|
||||||
|
*
|
||||||
|
* This is free and unencumbered software released into the public domain.
|
||||||
|
*
|
||||||
|
* Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||||
|
* distribute this software, either in source code form or as a compiled
|
||||||
|
* binary, for any purpose, commercial or non-commercial, and by any
|
||||||
|
* means.
|
||||||
|
*
|
||||||
|
* In jurisdictions that recognize copyright laws, the author or authors
|
||||||
|
* of this software dedicate any and all copyright interest in the
|
||||||
|
* software to the public domain. We make this dedication for the benefit
|
||||||
|
* of the public at large and to the detriment of our heirs and
|
||||||
|
* successors. We intend this dedication to be an overt act of
|
||||||
|
* relinquishment in perpetuity of all present and future rights to this
|
||||||
|
* software under copyright law.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||||
|
* IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||||
|
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||||
|
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
* OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
#ifndef __EXIF_DEFINES_H__
|
||||||
|
#define __EXIF_DEFINES_H__
|
||||||
|
|
||||||
|
// TIFF Rev. 6.0 Attribute Information Used in Exif
|
||||||
|
enum {
|
||||||
|
// A. Tags relating to image data structure
|
||||||
|
TagTiffImageWidth = 0x100, // Image width
|
||||||
|
TagTiffImageLength = 0x101, // Image height
|
||||||
|
TagTiffBitsPerSample = 0x102, // Number of bits per component
|
||||||
|
TagTiffCompression = 0x103, // Compression scheme
|
||||||
|
TagTiffPhotometricInterpretation = 0x106, // Pixel composition
|
||||||
|
TagTiffOrientation = 0x112, // Orientation of image
|
||||||
|
TagTiffSamplesPerPixel = 0x115, // Number of components
|
||||||
|
TagTiffPlanarConfiguration = 0x11C, // Image data arrangement
|
||||||
|
TagTiffYCbCrSubSampling = 0x212, // Subsampling ratio of Y to C
|
||||||
|
TagTiffYCbCrPositioning = 0x213, // Y and C positioning
|
||||||
|
TagTiffXResolution = 0x11A, // Image resolution in width direction
|
||||||
|
TagTiffYResolution = 0x11B, // Image resolution in height direction
|
||||||
|
TagTiffResolutionUnit = 0x128, // Unit of X and Y resolution
|
||||||
|
|
||||||
|
// B. Tags relating to recording offset
|
||||||
|
TagTiffStripOffsets = 0x111, // Image data location
|
||||||
|
TagTiffRowsPerStrip = 0x116, // Number of rows per strip
|
||||||
|
TagTiffStripByteCounts = 0x117, // Bytes per compressed strip
|
||||||
|
TagTiffJPEGInterchangeFormat = 0x201, // Offset to JPEG SOI
|
||||||
|
TagTiffJPEGInterchangeFormatLength = 0x202, // Bytes of JPEG data
|
||||||
|
|
||||||
|
// C. Tags relating to image data characteristics
|
||||||
|
TagTiffTransferFunction = 0x12D, // Transfer function
|
||||||
|
TagTiffWhitePoint = 0x13E, // White point chromaticity
|
||||||
|
TagTiffPrimaryChromaticities = 0x13F, // Chromaticities of primaries
|
||||||
|
TagTiffYCbCrCoefficients = 0x211, // Color space transformation matrix coefficients
|
||||||
|
TagTiffReferenceBlackWhite = 0x214, // Pair of black and white reference values
|
||||||
|
|
||||||
|
// D. Other tags
|
||||||
|
TagTiffDateTime = 0x132, // File change date and time
|
||||||
|
TagTiffImageDescription = 0x10E, // Image title
|
||||||
|
TagTiffMake = 0x10F, // Image input equipment manufacturer
|
||||||
|
TagTiffModel = 0x110, // Image input equipment model
|
||||||
|
TagTiffSoftware = 0x131, // Software used
|
||||||
|
TagTiffArtist = 0x13B, // Person who created the image
|
||||||
|
TagTiffCopyright = 0x8298, // Copyright holder
|
||||||
|
|
||||||
|
// Exif private IFD's
|
||||||
|
TagTiffExifIFD = 0x8769, // Exif IFD pointer
|
||||||
|
TagTiffGPSIFD = 0x8825, // Exif IFD pointer
|
||||||
|
TagTiffInteroperabilityIFD = 0xA005, // Exif IFD pointer
|
||||||
|
};
|
||||||
|
|
||||||
|
// Exif IFD Attribute Information
|
||||||
|
enum {
|
||||||
|
// Tags Relating to Version
|
||||||
|
TagExifVersion = 0x9000, // Exif version
|
||||||
|
TagExifFlashpixVersion = 0xA000, // Supported Flashpix version
|
||||||
|
|
||||||
|
// Tag Relating to Image Data Characteristics
|
||||||
|
TagExifColorSpace = 0xA001, // Color space information
|
||||||
|
TagExifGamma = 0xA500, // Gamma
|
||||||
|
|
||||||
|
// Tags Relating to Image Configuration
|
||||||
|
TagExifComponentsConfiguration = 0x9101, // Meaning of each component
|
||||||
|
TagExifCompressedBitsPerPixel = 0x9102, // Image compression mode
|
||||||
|
TagExifPixelXDimension = 0xA002, // Valid image width
|
||||||
|
TagExifPixelYDimension = 0xA003, // Valid image height
|
||||||
|
|
||||||
|
// Tags Relating to User Information
|
||||||
|
TagExifMakerNote = 0x927C, // Manufacturer notes
|
||||||
|
TagExifUserComment = 0x9286, // User comments
|
||||||
|
|
||||||
|
// Tag Relating to Related File Information
|
||||||
|
TagExifRelatedSoundFile = 0xA004, // Related audio file
|
||||||
|
|
||||||
|
// Tags Relating to Date and Time
|
||||||
|
TagExifDateTimeOriginal = 0x9003, // Date and time of original data generation
|
||||||
|
TagExifDateTimeDigitized = 0x9004, // Date and time of digital data generation
|
||||||
|
TagExifSubSecTime = 0x9290, // DateTime subseconds
|
||||||
|
TagExifSubSecTimeOriginal = 0x9291, // DateTimeOriginal subseconds
|
||||||
|
TagExifSubSecTimeDigitized = 0x9292, // DateTimeDigitized subseconds
|
||||||
|
|
||||||
|
// Tags Relating to Picture-Taking Conditions
|
||||||
|
TagExifExposureTime = 0x829A, // Exposure time
|
||||||
|
TagExifFNumber = 0x829D, // F number
|
||||||
|
TagExifExposureProgram = 0x8822, // Exposure program
|
||||||
|
TagExifSpectralSensitivity = 0x8824, // Spectral sensitivity
|
||||||
|
TagExifPhotographicSensitivity = 0x8827, // Photographic Sensitivity
|
||||||
|
TagExifOECF = 0x8828, // Optoelectric conversion factor
|
||||||
|
TagExifSensitivityType = 0x8830, // Sensitivity Type
|
||||||
|
TagExifStandardOutputSensitivity = 0x8831, // Standard Output Sensitivity
|
||||||
|
TagExifRecommendedExposureIndex = 0x8832, // Recommended ExposureIndex
|
||||||
|
TagExifISOSpeed = 0x8833, // ISO Speed
|
||||||
|
TagExifISOSpeedLatitudeyyy = 0x8834, // ISO Speed Latitude yyy
|
||||||
|
TagExifISOSpeedLatitudezzz = 0x8835, // ISO Speed Latitude zzz
|
||||||
|
TagExifShutterSpeedValue = 0x9201, // Shutter speed
|
||||||
|
TagExifApertureValue = 0x9202, // Aperture
|
||||||
|
TagExifBrightnessValue = 0x9203, // Brightness
|
||||||
|
TagExifExposureBiasValue = 0x9204, // Exposure bias
|
||||||
|
TagExifMaxApertureValue = 0x9205, // Maximum lens aperture
|
||||||
|
TagExifSubjectDistance = 0x9206, // Subject distance
|
||||||
|
TagExifMeteringMode = 0x9207, // Metering mode
|
||||||
|
TagExifLightSource = 0x9208, // Light source
|
||||||
|
TagExifFlash = 0x9209, // Flash
|
||||||
|
TagExifFocalLength = 0x920A, // Lens focal length
|
||||||
|
TagExifSubjectArea = 0x9214, // Subject area
|
||||||
|
TagExifFlashEnergy = 0xA20B, // Flash energy
|
||||||
|
TagExifSpatialFrequencyResponse = 0xA20C, // Spatial frequency response
|
||||||
|
TagExifFocalPlaneXResolution = 0xA20E, // Focal plane X resolution
|
||||||
|
TagExifFocalPlaneYResolution = 0xA20F, // Focal plane Y resolution
|
||||||
|
TagExifFocalPlaneResolutionUnit = 0xA210, // Focal plane resolution unit
|
||||||
|
TagExifSubjectLocation = 0xA214, // Subject location
|
||||||
|
TagExifExposureIndex = 0xA215, // Exposure index
|
||||||
|
TagExifSensingMethod = 0xA217, // Sensing method
|
||||||
|
TagExifFileSource = 0xA300, // File source
|
||||||
|
TagExifSceneType = 0xA301, // Scene type
|
||||||
|
TagExifCFAPattern = 0xA302, // CFA pattern
|
||||||
|
TagExifCustomRendered = 0xA401, // Custom image processing
|
||||||
|
TagExifExposureMode = 0xA402, // Exposure mode
|
||||||
|
TagExifWhiteBalance = 0xA403, // White balance
|
||||||
|
TagExifDigitalZoomRatio = 0xA404, // Digital zoom ratio
|
||||||
|
TagExifFocalLengthIn35mmFilm = 0xA405, // Focal length in 35 mm film
|
||||||
|
TagExifSceneCaptureType = 0xA406, // Scene capture type
|
||||||
|
TagExifGainControl = 0xA407, // Gain control
|
||||||
|
TagExifContrast = 0xA408, // Contrast
|
||||||
|
TagExifSaturation = 0xA409, // Saturation
|
||||||
|
TagExifSharpness = 0xA40A, // Sharpness
|
||||||
|
TagExifDeviceSettingDescription = 0xA40B, // Device settings description
|
||||||
|
TagExifSubjectDistanceRange = 0xA40C, // Subject distance range
|
||||||
|
|
||||||
|
// Other Tags
|
||||||
|
TagExifImageUniqueID = 0xA420, // Unique image ID
|
||||||
|
TagExifCameraOwnerName = 0xA430, // Camera Owner Name
|
||||||
|
TagExifBodySerialNumber = 0xA431, // Body Serial Number
|
||||||
|
TagExifLensSpecification = 0xA432, // Lens Specification
|
||||||
|
TagExifLensMake = 0xA433, // Lens Make
|
||||||
|
TagExifLensModel = 0xA434, // Lens Model
|
||||||
|
TagExifLensSerialNumber = 0xA435, // Lens Serial Number
|
||||||
|
};
|
||||||
|
|
||||||
|
// GPS Attribute Information
|
||||||
|
enum {
|
||||||
|
TagGPSVersionID = 0x00, // GPS tag version
|
||||||
|
TagGPSLatitudeRef = 0x01, // North or South Latitude
|
||||||
|
TagGPSLatitude = 0x02, // Latitude
|
||||||
|
TagGPSLongitudeRef = 0x03, // East or West Longitude
|
||||||
|
TagGPSLongitude = 0x04, // Longitude
|
||||||
|
TagGPSAltitudeRef = 0x05, // Altitude reference
|
||||||
|
TagGPSAltitude = 0x06, // Altitude
|
||||||
|
TagGPSTimeStamp = 0x07, // GPS time (atomic clock)
|
||||||
|
TagGPSSatellites = 0x08, // GPS satellites used for measurement
|
||||||
|
TagGPSStatus = 0x09, // GPS receiver status
|
||||||
|
TagGPSMeasureMode = 0x0A, // GPS measurement mode
|
||||||
|
TagGPSDOP = 0x0B, // Measurement precision
|
||||||
|
TagGPSSpeedRef = 0x0C, // Speed unit
|
||||||
|
TagGPSSpeed = 0x0D, // Speed of GPS receiver
|
||||||
|
TagGPSTrackRef = 0x0E, // Reference for direction of movement
|
||||||
|
TagGPSTrack = 0x0F, // Direction of movement
|
||||||
|
TagGPSImgDirectionRef = 0x10, // Reference for direction of image
|
||||||
|
TagGPSImgDirection = 0x11, // Direction of image
|
||||||
|
TagGPSMapDatum = 0x12, // Geodetic survey data used
|
||||||
|
TagGPSDestLatitudeRef = 0x13, // Reference for latitude of destination
|
||||||
|
TagGPSDestLatitude = 0x14, // Latitude of destination
|
||||||
|
TagGPSDestLongitudeRef = 0x15, // Reference for longitude of destination
|
||||||
|
TagGPSDestLongitude = 0x16, // Longitude of destination
|
||||||
|
TagGPSDestBearingRef = 0x17, // Reference for bearing of destination
|
||||||
|
TagGPSDestBearing = 0x18, // Bearing of destination
|
||||||
|
TagGPSDestDistanceRef = 0x19, // Reference for distance to destination
|
||||||
|
TagGPSDestDistance = 0x1A, // Distance to destination
|
||||||
|
TagGPSProcessingMethod = 0x1B, // Name of GPS processing method
|
||||||
|
TagGPSAreaInformation = 0x1C, // Name of GPS area
|
||||||
|
TagGPSDateStamp = 0x1D, // GPS date
|
||||||
|
TagGPSDifferential = 0x1E, // GPS differential correction
|
||||||
|
TagGPSHPositioningError = 0x1F, // Horizontal positioning error
|
||||||
|
};
|
||||||
|
|
||||||
|
// TIFF type fields values
|
||||||
|
enum {
|
||||||
|
TiffTypeByte = 1,
|
||||||
|
TiffTypeAscii = 2,
|
||||||
|
TiffTypeShort = 3,
|
||||||
|
TiffTypeLong = 4,
|
||||||
|
TiffTypeRational = 5,
|
||||||
|
TiffTypeUndef = 7,
|
||||||
|
TiffTypeSLong = 9,
|
||||||
|
TiffTypeSRational = 10,
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // __EXIF_DEFINES_H__
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
#define _MCU_CFG_H_
|
#define _MCU_CFG_H_
|
||||||
|
|
||||||
/* ---------------- BASIC MCU CFG --------------*/
|
/* ---------------- BASIC MCU CFG --------------*/
|
||||||
#define SW_VERSION "1.0.2-rc2" ///< SW version
|
#define SW_VERSION "1.0.2-rc3" ///< SW version
|
||||||
#define SW_BUILD __DATE__ " " __TIME__ ///< build number
|
#define SW_BUILD __DATE__ " " __TIME__ ///< build number
|
||||||
#define CONSOLE_VERBOSE_DEBUG false ///< enable/disable verbose debug log level for console
|
#define CONSOLE_VERBOSE_DEBUG false ///< enable/disable verbose debug log level for console
|
||||||
#define DEVICE_HOSTNAME "Prusa-ESP32cam" ///< device hostname
|
#define DEVICE_HOSTNAME "Prusa-ESP32cam" ///< device hostname
|
||||||
|
|
@ -99,6 +99,11 @@
|
||||||
#define NTP_GTM_OFFSET_SEC 0 ///< GMT offset in seconds. 0 = UTC. 3600 = GMT+1
|
#define NTP_GTM_OFFSET_SEC 0 ///< GMT offset in seconds. 0 = UTC. 3600 = GMT+1
|
||||||
#define NTP_DAYLIGHT_OFFSET_SEC 0 ///< daylight offset in seconds. 0 = no daylight saving time. 3600 = +1 hour
|
#define NTP_DAYLIGHT_OFFSET_SEC 0 ///< daylight offset in seconds. 0 = no daylight saving time. 3600 = +1 hour
|
||||||
|
|
||||||
|
/* ------------------ EXIF CFG ------------------*/
|
||||||
|
#define CAMERA_MAKE "OmniVision" ///< Camera make string
|
||||||
|
#define CAMERA_MODEL "OV2640" ///< Camera model string
|
||||||
|
#define CAMERA_SOFTWARE "Prusa ESP32-cam" ///< Camera software string
|
||||||
|
|
||||||
/* ---------------- FACTORY CFG ----------------*/
|
/* ---------------- FACTORY CFG ----------------*/
|
||||||
#define FACTORY_CFG_PHOTO_REFRESH_INTERVAL 30 ///< in the second
|
#define FACTORY_CFG_PHOTO_REFRESH_INTERVAL 30 ///< in the second
|
||||||
#define FACTORY_CFG_PHOTO_QUALITY 10 ///< 10-63, lower is better
|
#define FACTORY_CFG_PHOTO_QUALITY 10 ///< 10-63, lower is better
|
||||||
|
|
@ -134,6 +139,7 @@
|
||||||
#define FACTORY_CFG_NETWORK_STATIC_MASK "255.255.255.255" ///< Static Mask
|
#define FACTORY_CFG_NETWORK_STATIC_MASK "255.255.255.255" ///< Static Mask
|
||||||
#define FACTORY_CFG_NETWORK_STATIC_GATEWAY "255.255.255.255" ///< Static Gateway
|
#define FACTORY_CFG_NETWORK_STATIC_GATEWAY "255.255.255.255" ///< Static Gateway
|
||||||
#define FACTORY_CFG_NETWORK_STATIC_DNS "255.255.255.255" ///< Static DNS
|
#define FACTORY_CFG_NETWORK_STATIC_DNS "255.255.255.255" ///< Static DNS
|
||||||
|
#define FACTORY_CFG_IMAGE_EXIF_ROTATION 1 ///< Image rotation 1 - 0°, 6 - 90°, 3 - 180°, 8 - 270°
|
||||||
|
|
||||||
/* ---------------- CFG FLAGS ------------------*/
|
/* ---------------- CFG FLAGS ------------------*/
|
||||||
#define CFG_WIFI_SETTINGS_SAVED 0x0A ///< flag saved config
|
#define CFG_WIFI_SETTINGS_SAVED 0x0A ///< flag saved config
|
||||||
|
|
@ -266,6 +272,8 @@
|
||||||
#define EEPROM_ADDR_NETWORK_STATIC_DNS_START (EEPROM_ADDR_NETWORK_STATIC_GATEWAY_START + EEPROM_ADDR_NETWORK_STATIC_GATEWAY_LENGTH)
|
#define EEPROM_ADDR_NETWORK_STATIC_DNS_START (EEPROM_ADDR_NETWORK_STATIC_GATEWAY_START + EEPROM_ADDR_NETWORK_STATIC_GATEWAY_LENGTH)
|
||||||
#define EEPROM_ADDR_NETWORK_STATIC_DNS_LENGTH 4
|
#define EEPROM_ADDR_NETWORK_STATIC_DNS_LENGTH 4
|
||||||
|
|
||||||
|
#define EEPROM_ADDR_IMAGE_ROTATION_START (EEPROM_ADDR_NETWORK_STATIC_DNS_START + EEPROM_ADDR_NETWORK_STATIC_DNS_LENGTH)
|
||||||
|
#define EEPROM_ADDR_IMAGE_ROTATION_LENGTH 1
|
||||||
|
|
||||||
#define EEPROM_SIZE (EEPROM_ADDR_REFRESH_INTERVAL_LENGTH + EEPROM_ADDR_FINGERPRINT_LENGTH + EEPROM_ADDR_TOKEN_LENGTH + \
|
#define EEPROM_SIZE (EEPROM_ADDR_REFRESH_INTERVAL_LENGTH + EEPROM_ADDR_FINGERPRINT_LENGTH + EEPROM_ADDR_TOKEN_LENGTH + \
|
||||||
EEPROM_ADDR_FRAMESIZE_LENGTH + EEPROM_ADDR_BRIGHTNESS_LENGTH + EEPROM_ADDR_CONTRAST_LENGTH + \
|
EEPROM_ADDR_FRAMESIZE_LENGTH + EEPROM_ADDR_BRIGHTNESS_LENGTH + EEPROM_ADDR_CONTRAST_LENGTH + \
|
||||||
|
|
@ -281,7 +289,7 @@
|
||||||
EEPROM_ADDR_AEC_VALUE_LENGTH + EEPROM_ADDR_GAIN_CTRL_LENGTH + EEPROM_ADDR_AGC_GAIN_LENGTH + EEPROM_ADDR_LOG_LEVEL_LENGTH + \
|
EEPROM_ADDR_AEC_VALUE_LENGTH + EEPROM_ADDR_GAIN_CTRL_LENGTH + EEPROM_ADDR_AGC_GAIN_LENGTH + EEPROM_ADDR_LOG_LEVEL_LENGTH + \
|
||||||
EEPROM_ADDR_HOSTNAME_LENGTH + EEPROM_ADDR_SERVICE_AP_ENABLE_LENGTH + EEPROM_ADDR_NETWORK_IP_METHOD_LENGTH +\
|
EEPROM_ADDR_HOSTNAME_LENGTH + EEPROM_ADDR_SERVICE_AP_ENABLE_LENGTH + EEPROM_ADDR_NETWORK_IP_METHOD_LENGTH +\
|
||||||
EEPROM_ADDR_NETWORK_STATIC_IP_LENGTH + EEPROM_ADDR_NETWORK_STATIC_MASK_LENGTH + EEPROM_ADDR_NETWORK_STATIC_GATEWAY_LENGTH + \
|
EEPROM_ADDR_NETWORK_STATIC_IP_LENGTH + EEPROM_ADDR_NETWORK_STATIC_MASK_LENGTH + EEPROM_ADDR_NETWORK_STATIC_GATEWAY_LENGTH + \
|
||||||
EEPROM_ADDR_NETWORK_STATIC_DNS_LENGTH) ///< how many bits do we need for eeprom memory
|
EEPROM_ADDR_NETWORK_STATIC_DNS_LENGTH + EEPROM_ADDR_IMAGE_ROTATION_LENGTH) ///< how many bits do we need for eeprom memory
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,8 +40,28 @@ void Server_InitWebServer() {
|
||||||
if (Server_CheckBasicAuth(request) == false)
|
if (Server_CheckBasicAuth(request) == false)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (SystemCamera.GetCameraCaptureSuccess() == false) {
|
||||||
|
request->send_P(404, "text/plain", "Photo not found!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
SystemLog.AddEvent(LogLevel_Verbose, "Photo size: " + String(SystemCamera.GetPhotoFb()->len) + " bytes");
|
SystemLog.AddEvent(LogLevel_Verbose, "Photo size: " + String(SystemCamera.GetPhotoFb()->len) + " bytes");
|
||||||
|
|
||||||
|
if (SystemCamera.GetPhotoExifData()->header != NULL) {
|
||||||
|
/* send photo with exif data */
|
||||||
|
SystemLog.AddEvent(LogLevel_Verbose, F("Send photo with EXIF data"));
|
||||||
|
size_t total_len = SystemCamera.GetPhotoExifData()->len + SystemCamera.GetPhotoFb()->len - SystemCamera.GetPhotoExifData()->offset;
|
||||||
|
auto response = request->beginResponseStream("image/jpg");
|
||||||
|
response->addHeader("Content-Length", String(total_len));
|
||||||
|
response->write(SystemCamera.GetPhotoExifData()->header, SystemCamera.GetPhotoExifData()->len);
|
||||||
|
response->write(&SystemCamera.GetPhotoFb()->buf[SystemCamera.GetPhotoExifData()->offset], SystemCamera.GetPhotoFb()->len - SystemCamera.GetPhotoExifData()->offset);
|
||||||
|
request->send(response);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
/* send photo without exif data */
|
||||||
|
SystemLog.AddEvent(LogLevel_Verbose, F("Send photo without EXIF data"));
|
||||||
request->send_P(200, "image/jpg", SystemCamera.GetPhotoFb()->buf, SystemCamera.GetPhotoFb()->len);
|
request->send_P(200, "image/jpg", SystemCamera.GetPhotoFb()->buf, SystemCamera.GetPhotoFb()->len);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/* route to jquery */
|
/* route to jquery */
|
||||||
|
|
@ -524,6 +544,14 @@ void Server_InitWebServer_Sets() {
|
||||||
response = true;
|
response = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* set image exif rotation */
|
||||||
|
if (request->hasParam("image_rotation")) {
|
||||||
|
SystemLog.AddEvent(LogLevel_Verbose, F("Set image EXIF rotation"));
|
||||||
|
SystemCamera.SetCameraImageRotation(request->getParam("image_rotation")->value().toInt());
|
||||||
|
response_msg = MSG_SAVE_OK;
|
||||||
|
response = true;
|
||||||
|
}
|
||||||
|
|
||||||
/* set log level /set_int?log_level=2 */
|
/* set log level /set_int?log_level=2 */
|
||||||
if (request->hasParam("log_level")) {
|
if (request->hasParam("log_level")) {
|
||||||
SystemLog.AddEvent(LogLevel_Verbose, F("Set log_level"));
|
SystemLog.AddEvent(LogLevel_Verbose, F("Set log_level"));
|
||||||
|
|
@ -1061,6 +1089,7 @@ String Server_GetJsonData() {
|
||||||
doc_json["net_mask"] = SystemWifiMngt.GetNetStaticMask();
|
doc_json["net_mask"] = SystemWifiMngt.GetNetStaticMask();
|
||||||
doc_json["net_gw"] = SystemWifiMngt.GetNetStaticGateway();
|
doc_json["net_gw"] = SystemWifiMngt.GetNetStaticGateway();
|
||||||
doc_json["net_dns"] = SystemWifiMngt.GetNetStaticDns();
|
doc_json["net_dns"] = SystemWifiMngt.GetNetStaticDns();
|
||||||
|
doc_json["image_rotation"] = SystemCamera.GetCameraImageRotation();
|
||||||
doc_json["sw_build"] = SW_BUILD;
|
doc_json["sw_build"] = SW_BUILD;
|
||||||
doc_json["sw_ver"] = SW_VERSION;
|
doc_json["sw_ver"] = SW_VERSION;
|
||||||
doc_json["sw_new_ver"] = FirmwareUpdate.NewVersionFw;
|
doc_json["sw_new_ver"] = FirmwareUpdate.NewVersionFw;
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,8 @@
|
||||||
#include "wifi_mngt.h"
|
#include "wifi_mngt.h"
|
||||||
#include "stream.h"
|
#include "stream.h"
|
||||||
|
|
||||||
|
#include "exif.h"
|
||||||
|
|
||||||
extern AsyncWebServer server; ///< global variable for web server
|
extern AsyncWebServer server; ///< global variable for web server
|
||||||
|
|
||||||
void Server_LoadCfg();
|
void Server_LoadCfg();
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@
|
||||||
<option value="4">1024x768</option>
|
<option value="4">1024x768</option>
|
||||||
<option value="5">1280x1024</option>
|
<option value="5">1280x1024</option>
|
||||||
<option value="6">1600x1200</option>
|
<option value="6">1600x1200</option>
|
||||||
</select>
|
</select> <span class="pc1">pixels</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr><td style="height: 1px;"></td><td style="height: 1px;"></td></tr>
|
<tr><td style="height: 1px;"></td><td style="height: 1px;"></td></tr>
|
||||||
|
|
@ -26,6 +26,16 @@
|
||||||
<tr><td class="pc1">Contrast</td><td class="pc2">Low <input type="range" class="slider" name="contrast" id=contrastid min="-2" max="2" step="1" onchange="changeValue(this.value, 'set_int?contrast=', 'config')"> High</td></tr>
|
<tr><td class="pc1">Contrast</td><td class="pc2">Low <input type="range" class="slider" name="contrast" id=contrastid min="-2" max="2" step="1" onchange="changeValue(this.value, 'set_int?contrast=', 'config')"> High</td></tr>
|
||||||
<tr><td class="pc1">Saturation</td><td class="pc2">Low <input type="range" class="slider" name="saturation" id=saturationid min="-2" max="2" step="1" onchange="changeValue(this.value, 'set_int?saturation=', 'config')"> High</td></tr>
|
<tr><td class="pc1">Saturation</td><td class="pc2">Low <input type="range" class="slider" name="saturation" id=saturationid min="-2" max="2" step="1" onchange="changeValue(this.value, 'set_int?saturation=', 'config')"> High</td></tr>
|
||||||
<tr><td style="height: 1px;"></td><td style="height: 1px;"></td></tr>
|
<tr><td style="height: 1px;"></td><td style="height: 1px;"></td></tr>
|
||||||
|
<tr>
|
||||||
|
<td class="pc1">Image rotation</td><td><label for="image_rotation"></label>
|
||||||
|
<select class="select" id="image_rotationid" name="image_rotation" onchange="changeValue(this.value, 'set_int?image_rotation=', 'config')">
|
||||||
|
<option value="1">0°</option>
|
||||||
|
<option value="6">90°</option>
|
||||||
|
<option value="3">180°</option>
|
||||||
|
<option value="8">270°</option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
<tr><td class="pc1">Horizontal mirror</td><td class="pc2"><label class="switch"><input type="checkbox" name="hmirror" id=hmirrorid onchange="changeValue(this.checked, 'set_bool?hmirror=', 'config')"><span class="checkbox_slider round"></span></label> <span id="status_hmirror"></span></td></tr>
|
<tr><td class="pc1">Horizontal mirror</td><td class="pc2"><label class="switch"><input type="checkbox" name="hmirror" id=hmirrorid onchange="changeValue(this.checked, 'set_bool?hmirror=', 'config')"><span class="checkbox_slider round"></span></label> <span id="status_hmirror"></span></td></tr>
|
||||||
<tr><td class="pc1">Vertical flip</td><td class="pc2"><label class="switch"><input type="checkbox" name="vflip" id=vflipid onchange="changeValue(this.checked, 'set_bool?vflip=', 'config')"><span class="checkbox_slider round"></span></label> <span id="status_vflip"></span></td></tr>
|
<tr><td class="pc1">Vertical flip</td><td class="pc2"><label class="switch"><input type="checkbox" name="vflip" id=vflipid onchange="changeValue(this.checked, 'set_bool?vflip=', 'config')"><span class="checkbox_slider round"></span></label> <span id="status_vflip"></span></td></tr>
|
||||||
<tr><td style="height: 1px;"></td><td style="height: 1px;"></td></tr>
|
<tr><td style="height: 1px;"></td><td style="height: 1px;"></td></tr>
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ function get_data(val) {
|
||||||
document.getElementById('brightnessid').value = obj.brightness;
|
document.getElementById('brightnessid').value = obj.brightness;
|
||||||
document.getElementById('contrastid').value = obj.contrast;
|
document.getElementById('contrastid').value = obj.contrast;
|
||||||
document.getElementById('saturationid').value = obj.saturation;
|
document.getElementById('saturationid').value = obj.saturation;
|
||||||
|
document.getElementById('image_rotationid').value = obj.image_rotation;
|
||||||
document.getElementById('hmirrorid').checked = obj.hmirror;
|
document.getElementById('hmirrorid').checked = obj.hmirror;
|
||||||
document.getElementById('vflipid').checked = obj.vflip;
|
document.getElementById('vflipid').checked = obj.vflip;
|
||||||
document.getElementById('lencid').checked = obj.lensc;
|
document.getElementById('lencid').checked = obj.lensc;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue