Browse Source

Add DCP calibration files for sensors

Read .dcp files stored in the same directory as the camera configuration
files and copy over the calibration curves into the DNG files after
taking a picture.
Martijn Braam 2 years ago
parent
commit
b5161db18e

BIN
config/pine64,pinephone,front.dcp


BIN
config/pine64,pinephone,rear.dcp


+ 3 - 0
meson.build

@@ -61,6 +61,7 @@ executable('megapixels',
   'src/pipeline.c',
   'src/process_pipeline.c',
   'src/zbar_pipeline.c',
+  'src/dcp.c',
   resources,
   include_directories: 'src/',
   dependencies: [gtkdep, libfeedback, libm, tiff, zbar, threads, epoxy] + optdeps,
@@ -70,6 +71,8 @@ executable('megapixels',
 install_data(
   [
     'config/pine64,pinephone-1.0.ini',
+    'config/pine64,pinephone,rear.dcp',
+    'config/pine64,pinephone,front.dcp',
     'config/pine64,pinephone-1.1.ini',
     'config/pine64,pinephone-1.2.ini',
     'config/pine64,pinetab.ini',

+ 77 - 0
src/camera_config.c

@@ -73,6 +73,77 @@ find_config(char *conffile)
         return false;
 }
 
+static bool
+find_calibration_by_model(char *conffile, char *model, const char *sensor)
+{
+        // Check config/%model,%sensor.dcp in the current working directory
+        sprintf(conffile, "config/%s,%s.dcp", model, sensor);
+        if (access(conffile, F_OK) != -1) {
+                printf("Found calibration file at %s\n", conffile);
+                return true;
+        }
+
+        // Check for a calibration file in XDG_CONFIG_HOME
+        sprintf(conffile,
+                "%s/megapixels/config/%s,%s.dcp",
+                g_get_user_config_dir(),
+                model,
+                sensor);
+        if (access(conffile, F_OK) != -1) {
+                printf("Found calibration file at %s\n", conffile);
+                return true;
+        }
+
+        // Check user overridden /etc/megapixels/config/%model,%sensor.dcp
+        sprintf(conffile,
+                "%s/megapixels/config/%s,%s.dcp",
+                SYSCONFDIR,
+                model,
+                sensor);
+        if (access(conffile, F_OK) != -1) {
+                printf("Found calibration file at %s\n", conffile);
+                return true;
+        }
+
+        // Check packaged /usr/share/megapixels/config/%model,%sensor.ini
+        sprintf(conffile, "%s/megapixels/config/%s,%s.dcp", DATADIR, model, sensor);
+        if (access(conffile, F_OK) != -1) {
+                printf("Found calibration file at %s\n", conffile);
+                return true;
+        }
+        printf("No calibration found for %s,%s\n", model, sensor);
+        return false;
+}
+
+static bool
+find_calibration(char *conffile, const char *sensor)
+{
+        char model[512];
+        FILE *fp;
+
+        if (access("/proc/device-tree/compatible", F_OK) == -1) {
+                return false;
+        }
+        fp = fopen("/proc/device-tree/compatible", "r");
+        char *modelptr = model;
+        while (1) {
+                int c = fgetc(fp);
+                if (c == EOF) {
+                        *(modelptr) = '\0';
+                        return find_calibration_by_model(conffile, model, sensor);
+                }
+                *(modelptr++) = (char)c;
+                if (c == 0) {
+                        bool res =
+                                find_calibration_by_model(conffile, model, sensor);
+                        if (res) {
+                                return true;
+                        }
+                        modelptr = model;
+                }
+        }
+}
+
 static int
 strtoint(const char *nptr, char **endptr, int base)
 {
@@ -146,6 +217,12 @@ config_ini_handler(void *user,
 
                         cameras[index].index = index;
                         strcpy(cameras[index].cfg_name, section);
+
+                        char cal_path[512];
+                        if (find_calibration(cal_path, section)) {
+                                cameras[index].calibration =
+                                        parse_calibration_file(cal_path);
+                        }
                 }
 
                 struct mp_camera_config *cc = &cameras[index];

+ 3 - 0
src/camera_config.h

@@ -1,5 +1,6 @@
 #pragma once
 
+#include "dcp.h"
 #include "mode.h"
 
 #include <stdbool.h>
@@ -36,6 +37,8 @@ struct mp_camera_config {
         int blacklevel;
         int whitelevel;
 
+        struct MPCameraCalibration calibration;
+
         float focallength;
         float cropfactor;
         double fnumber;

+ 149 - 0
src/dcp.c

@@ -0,0 +1,149 @@
+#include "dcp.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+unsigned int
+get_int32(const unsigned char *buffer, size_t offset)
+{
+        return (buffer[offset + 3] << 24) | (buffer[offset + 2] << 16) |
+               (buffer[offset + 1] << 8) | (buffer[offset]);
+}
+
+unsigned int
+get_int16(const unsigned char *buffer, size_t offset)
+{
+        return (buffer[offset + 1] << 8) | (buffer[offset]);
+}
+
+float
+get_float(unsigned char *buffer, size_t offset)
+{
+        float f;
+        unsigned char b[] = { buffer[offset + 0],
+                              buffer[offset + 1],
+                              buffer[offset + 2],
+                              buffer[offset + 3] };
+        memcpy(&f, &b, sizeof(f));
+        return f;
+}
+
+float
+get_srational(unsigned char *buffer, size_t offset)
+{
+        int a = (int)get_int32(buffer, offset);
+        int b = (int)get_int32(buffer, offset + 4);
+        return (float)a / (float)b;
+}
+
+struct MPCameraCalibration
+parse_calibration_file(const char *path)
+{
+        FILE *fp;
+        size_t size;
+        unsigned char *buffer;
+
+        struct MPCameraCalibration result = { 0 };
+
+        fp = fopen(path, "rb");
+        if (fp == NULL) {
+                return result;
+        }
+
+        fseek(fp, 0, SEEK_END);
+        size = ftell(fp);
+        fseek(fp, 0, SEEK_SET);
+        buffer = malloc(sizeof(char) * size);
+        size_t ret = fread(buffer, 1, size, fp);
+        if (ret != size) {
+                return result;
+        }
+        fclose(fp);
+
+        if (buffer[0] != 'I' || buffer[1] != 'I') {
+                fprintf(stderr, "Magic for DCP file incorrect\n");
+                return result;
+        }
+        if (buffer[2] != 0x52 || buffer[3] != 0x43) {
+                fprintf(stderr, "Invalid DCP version\n");
+                return result;
+        }
+
+        unsigned int ifd0 = get_int32(buffer, 4);
+        unsigned int tag_count = get_int16(buffer, ifd0);
+
+        for (int i = 0; i < tag_count; i++) {
+                int tag_offset = ifd0 + 2 + (i * 12);
+                unsigned int tag = get_int16(buffer, tag_offset + 0);
+                unsigned int type = get_int16(buffer, tag_offset + 2);
+                unsigned int count = get_int32(buffer, tag_offset + 4);
+                unsigned int offset = get_int32(buffer, tag_offset + 8);
+
+                switch (tag) {
+                case DCPTAG_COLOR_MATRIX_1:
+                        for (int j = 0; j < 9; j++) {
+                                float point =
+                                        get_srational(buffer, offset + (j * 8));
+                                result.color_matrix_1[j] = point;
+                        }
+                        break;
+                case DCPTAG_COLOR_MATRIX_2:
+                        for (int j = 0; j < 9; j++) {
+                                float point =
+                                        get_srational(buffer, offset + (j * 8));
+                                result.color_matrix_2[j] = point;
+                        }
+                        break;
+                case DCPTAG_FORWARD_MATRIX_1:
+                        for (int j = 0; j < 9; j++) {
+                                float point =
+                                        get_srational(buffer, offset + (j * 8));
+                                result.forward_matrix_1[j] = point;
+                        }
+                        break;
+                case DCPTAG_FORWARD_MATRIX_2:
+                        for (int j = 0; j < 9; j++) {
+                                float point =
+                                        get_srational(buffer, offset + (j * 8));
+                                result.forward_matrix_2[j] = point;
+                        }
+                        break;
+                case DCPTAG_CALIBRATION_ILLUMINANT_1:
+                        result.illuminant_1 = offset;
+                        break;
+                case DCPTAG_CALIBRATION_ILLUMINANT_2:
+                        result.illuminant_2 = offset;
+                        break;
+                case DCPTAG_PROFILE_TONE_CURVE:
+                        result.tone_curve = malloc(count * sizeof(float));
+                        result.tone_curve_length = count;
+                        for (int j = 0; j < count; j++) {
+                                result.tone_curve[j] =
+                                        get_float(buffer, offset + (j * 4));
+                        }
+                        break;
+                case DCPTAG_PROFILE_HUE_SAT_MAP_DIMS:
+                        result.hue_sat_map_dims[0] = get_int32(buffer, offset);
+                        result.hue_sat_map_dims[1] = get_int32(buffer, offset + 4);
+                        result.hue_sat_map_dims[2] = get_int32(buffer, offset + 8);
+                        break;
+                case DCPTAG_PROFILE_HUE_SAT_MAP_DATA_1:
+                        result.hue_sat_map_data_1 = malloc(count * sizeof(float));
+                        for (int j = 0; j < count; j++) {
+                                result.hue_sat_map_data_1[j] =
+                                        get_float(buffer, offset + (j * 4));
+                        }
+                        break;
+                case DCPTAG_PROFILE_HUE_SAT_MAP_DATA_2:
+                        result.hue_sat_map_data_2 = malloc(count * sizeof(float));
+                        for (int j = 0; j < count; j++) {
+                                result.hue_sat_map_data_2[j] =
+                                        get_float(buffer, offset + (j * 4));
+                        }
+                        break;
+                }
+        }
+
+        return result;
+}

+ 30 - 0
src/dcp.h

@@ -0,0 +1,30 @@
+#pragma once
+
+#include <stdio.h>
+
+struct MPCameraCalibration {
+        float color_matrix_1[9];
+        float color_matrix_2[9];
+        float forward_matrix_1[9];
+        float forward_matrix_2[9];
+        unsigned short illuminant_1;
+        unsigned short illuminant_2;
+        unsigned int hue_sat_map_dims[3];
+        size_t tone_curve_length;
+        float *tone_curve;
+        float *hue_sat_map_data_1;
+        float *hue_sat_map_data_2;
+};
+
+#define DCPTAG_COLOR_MATRIX_1 50721
+#define DCPTAG_COLOR_MATRIX_2 50722
+#define DCPTAG_PROFILE_HUE_SAT_MAP_DIMS 50937
+#define DCPTAG_PROFILE_HUE_SAT_MAP_DATA_1 50938
+#define DCPTAG_PROFILE_HUE_SAT_MAP_DATA_2 50939
+#define DCPTAG_PROFILE_TONE_CURVE 50940
+#define DCPTAG_CALIBRATION_ILLUMINANT_1 50778
+#define DCPTAG_CALIBRATION_ILLUMINANT_2 50779
+#define DCPTAG_FORWARD_MATRIX_1 50964
+#define DCPTAG_FORWARD_MATRIX_2 50965
+
+struct MPCameraCalibration parse_calibration_file(const char *path);

+ 14 - 0
src/io_pipeline.c

@@ -375,6 +375,18 @@ update_process_pipeline()
                         mp_camera_control_get_int32(info->camera, V4L2_CID_EXPOSURE);
         }
 
+        MPControl control;
+        float balance_red = 1.0f;
+        float balance_blue = 1.0f;
+        if (mp_camera_query_control(info->camera, V4L2_CID_RED_BALANCE, &control)) {
+                int red = mp_camera_control_get_int32(info->camera,
+                                                      V4L2_CID_RED_BALANCE);
+                int blue = mp_camera_control_get_int32(info->camera,
+                                                       V4L2_CID_BLUE_BALANCE);
+                balance_red = (float)red / (float)control.max;
+                balance_blue = (float)blue / (float)control.max;
+        }
+
         struct mp_process_pipeline_state pipeline_state = {
                 .camera = camera,
                 .mode = mode,
@@ -385,6 +397,8 @@ update_process_pipeline()
                 .gain_is_manual = current_controls.gain_is_manual,
                 .gain = current_controls.gain,
                 .gain_max = info->gain_max,
+                .balance_red = balance_red,
+                .balance_blue = balance_blue,
                 .exposure_is_manual = current_controls.exposure_is_manual,
                 .exposure = current_controls.exposure,
                 .has_auto_focus_continuous = info->has_auto_focus_continuous,

+ 118 - 3
src/process_pipeline.c

@@ -15,6 +15,7 @@
 #include <sys/mman.h>
 
 #define TIFFTAG_FORWARDMATRIX1 50964
+#define TIFFTAG_FORWARDMATRIX2 50965
 
 static const float colormatrix_srgb[] = { 3.2409, -1.5373, -0.4986, -0.9692, 1.8759,
                                           0.0415, 0.0556,  -0.2039, 1.0569 };
@@ -46,6 +47,7 @@ static int output_buffer_height = -1;
 // static bool gain_is_manual;
 static int gain;
 static int gain_max;
+static float balance[3] = { 1.0f, 1.0f, 1.0f };
 
 static bool exposure_is_manual;
 static int exposure;
@@ -68,6 +70,46 @@ register_custom_tiff_tags(TIFF *tif)
                   1,
                   1,
                   "ForwardMatrix1" },
+                { TIFFTAG_FORWARDMATRIX2,
+                  -1,
+                  -1,
+                  TIFF_SRATIONAL,
+                  FIELD_CUSTOM,
+                  1,
+                  1,
+                  "ForwardMatrix2" },
+                { DCPTAG_PROFILE_TONE_CURVE,
+                  -1,
+                  -1,
+                  TIFF_FLOAT,
+                  FIELD_CUSTOM,
+                  1,
+                  1,
+                  "ProfileToneCurve" },
+                { DCPTAG_PROFILE_HUE_SAT_MAP_DIMS,
+                  -1,
+                  -1,
+                  TIFF_FLOAT,
+                  FIELD_CUSTOM,
+                  1,
+                  1,
+                  "ProfileHueSatMapDims" },
+                { DCPTAG_PROFILE_HUE_SAT_MAP_DATA_1,
+                  -1,
+                  -1,
+                  TIFF_FLOAT,
+                  FIELD_CUSTOM,
+                  1,
+                  1,
+                  "ProfileHueSatMapData1" },
+                { DCPTAG_PROFILE_HUE_SAT_MAP_DATA_2,
+                  -1,
+                  -1,
+                  TIFF_FLOAT,
+                  FIELD_CUSTOM,
+                  1,
+                  1,
+                  "ProfileHueSatMapData2" },
         };
 
         // Add missing dng fields
@@ -529,17 +571,88 @@ process_image_for_capture(const uint8_t *image, int count)
                 mp_get_device_make(),
                 mp_get_device_model());
         TIFFSetField(tif, TIFFTAG_UNIQUECAMERAMODEL, uniquecameramodel);
-        if (camera->colormatrix[0]) {
+
+        // Color matrices
+        if (camera->calibration.color_matrix_1[0]) {
+                TIFFSetField(tif,
+                             TIFFTAG_COLORMATRIX1,
+                             9,
+                             camera->calibration.color_matrix_1);
+        } else if (camera->colormatrix[0]) {
                 TIFFSetField(tif, TIFFTAG_COLORMATRIX1, 9, camera->colormatrix);
         } else {
                 TIFFSetField(tif, TIFFTAG_COLORMATRIX1, 9, colormatrix_srgb);
         }
-        if (camera->forwardmatrix[0]) {
+        if (camera->calibration.color_matrix_2[0]) {
+                TIFFSetField(tif,
+                             TIFFTAG_COLORMATRIX2,
+                             9,
+                             camera->calibration.color_matrix_2);
+        }
+
+        if (camera->calibration.forward_matrix_1[0]) {
+                TIFFSetField(tif,
+                             TIFFTAG_FORWARDMATRIX1,
+                             9,
+                             camera->calibration.forward_matrix_1);
+        } else if (camera->forwardmatrix[0]) {
                 TIFFSetField(tif, TIFFTAG_FORWARDMATRIX1, 9, camera->forwardmatrix);
         }
+
+        if (camera->calibration.forward_matrix_2[0]) {
+                TIFFSetField(tif,
+                             TIFFTAG_FORWARDMATRIX2,
+                             9,
+                             camera->calibration.forward_matrix_2);
+        }
+
         static const float neutral[] = { 1.0, 1.0, 1.0 };
         TIFFSetField(tif, TIFFTAG_ASSHOTNEUTRAL, 3, neutral);
-        TIFFSetField(tif, TIFFTAG_CALIBRATIONILLUMINANT1, 21);
+        if (camera->calibration.illuminant_1) {
+                TIFFSetField(tif,
+                             TIFFTAG_CALIBRATIONILLUMINANT1,
+                             camera->calibration.illuminant_1);
+        } else {
+                TIFFSetField(tif, TIFFTAG_CALIBRATIONILLUMINANT1, 21);
+        }
+
+        if (camera->calibration.illuminant_2) {
+                TIFFSetField(tif,
+                             TIFFTAG_CALIBRATIONILLUMINANT2,
+                             camera->calibration.illuminant_2);
+        }
+
+        if (camera->calibration.tone_curve_length) {
+                TIFFSetField(tif,
+                             DCPTAG_PROFILE_TONE_CURVE,
+                             camera->calibration.tone_curve_length,
+                             camera->calibration.tone_curve);
+        }
+
+        if (camera->calibration.hue_sat_map_dims[0]) {
+                TIFFSetField(tif,
+                             DCPTAG_PROFILE_HUE_SAT_MAP_DIMS,
+                             3,
+                             camera->calibration.hue_sat_map_dims);
+                TIFFSetField(tif,
+                             DCPTAG_PROFILE_HUE_SAT_MAP_DATA_1,
+                             camera->calibration.hue_sat_map_dims[0] *
+                                     camera->calibration.hue_sat_map_dims[1] *
+                                     camera->calibration.hue_sat_map_dims[2] * 3,
+                             camera->calibration.hue_sat_map_data_1);
+                if (camera->calibration.hue_sat_map_data_2 != NULL) {
+                        TIFFSetField(
+                                tif,
+                                DCPTAG_PROFILE_HUE_SAT_MAP_DATA_2,
+                                camera->calibration.hue_sat_map_dims[0] *
+                                        camera->calibration.hue_sat_map_dims[1] *
+                                        camera->calibration.hue_sat_map_dims[2] * 3,
+                                camera->calibration.hue_sat_map_data_2);
+                }
+        }
+
+        TIFFSetField(tif, TIFFTAG_ANALOGBALANCE, 3, balance);
+
         // Write black thumbnail, only windows uses this
         {
                 unsigned char *buf =
@@ -932,6 +1045,8 @@ update_state(MPPipeline *pipeline, const struct mp_process_pipeline_state *state
         // gain_is_manual = state->gain_is_manual;
         gain = state->gain;
         gain_max = state->gain_max;
+        balance[0] = state->balance_red;
+        balance[2] = state->balance_blue;
 
         exposure_is_manual = state->exposure_is_manual;
         exposure = state->exposure;

+ 3 - 0
src/process_pipeline.h

@@ -21,6 +21,9 @@ struct mp_process_pipeline_state {
         int gain;
         int gain_max;
 
+        float balance_red;
+        float balance_blue;
+
         bool exposure_is_manual;
         int exposure;