diff --git a/config/pine64,pinephone,front.dcp b/config/pine64,pinephone,front.dcp new file mode 100755 index 0000000..a9b009f Binary files /dev/null and b/config/pine64,pinephone,front.dcp differ diff --git a/config/pine64,pinephone,rear.dcp b/config/pine64,pinephone,rear.dcp new file mode 100755 index 0000000..242c54b Binary files /dev/null and b/config/pine64,pinephone,rear.dcp differ diff --git a/meson.build b/meson.build index a9b9b4e..2c9bb10 100644 --- a/meson.build +++ b/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', diff --git a/src/camera_config.c b/src/camera_config.c index 6ff74d1..8b2ad1e 100644 --- a/src/camera_config.c +++ b/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]; diff --git a/src/camera_config.h b/src/camera_config.h index d53d36f..eb060e4 100644 --- a/src/camera_config.h +++ b/src/camera_config.h @@ -1,5 +1,6 @@ #pragma once +#include "dcp.h" #include "mode.h" #include @@ -36,6 +37,8 @@ struct mp_camera_config { int blacklevel; int whitelevel; + struct MPCameraCalibration calibration; + float focallength; float cropfactor; double fnumber; diff --git a/src/dcp.c b/src/dcp.c new file mode 100644 index 0000000..98abbb6 --- /dev/null +++ b/src/dcp.c @@ -0,0 +1,149 @@ +#include "dcp.h" + +#include +#include +#include + +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; +} diff --git a/src/dcp.h b/src/dcp.h new file mode 100644 index 0000000..6b1eb50 --- /dev/null +++ b/src/dcp.h @@ -0,0 +1,30 @@ +#pragma once + +#include + +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); diff --git a/src/io_pipeline.c b/src/io_pipeline.c index e9f80c2..a94bf3b 100644 --- a/src/io_pipeline.c +++ b/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, diff --git a/src/process_pipeline.c b/src/process_pipeline.c index e6db37e..c6c37eb 100644 --- a/src/process_pipeline.c +++ b/src/process_pipeline.c @@ -15,6 +15,7 @@ #include #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; diff --git a/src/process_pipeline.h b/src/process_pipeline.h index cbc1724..c3b8eac 100644 --- a/src/process_pipeline.h +++ b/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;