Browse Source

Add the dngmerge tool to verify dcp loading

This tool loads valid DNG file, merge the color metadata from a valid DCP file on top
of it and then write it out again as a new DNG file.

This verifies that DNG loading and DCP loading works correctly in the libdng code.
Martijn Braam 1 year ago
parent
commit
d40adf59e3
9 changed files with 330 additions and 2 deletions
  1. 4 0
      CMakeLists.txt
  2. 29 0
      include/libdng.h
  3. 132 0
      src/libdng.c
  4. 7 1
      tests/CMakeLists.txt
  5. 81 0
      tests/check_dcp.c
  6. 3 0
      tests/fixture/meson.build
  7. BIN
      tests/fixture/pinephone.dcp
  8. 10 1
      tests/meson.build
  9. 64 0
      util/dngmerge.c

+ 4 - 0
CMakeLists.txt

@@ -19,6 +19,10 @@ add_executable(makedng util/makedng.c)
 target_include_directories(makedng PUBLIC include)
 target_link_libraries(makedng PUBLIC libdng)
 
+add_executable(dngmerge util/dngmerge.c)
+target_include_directories(dngmerge PUBLIC include)
+target_link_libraries(dngmerge PUBLIC libdng)
+
 find_program(BASH bash)
 enable_testing()
 

+ 29 - 0
include/libdng.h

@@ -60,6 +60,28 @@ typedef struct {
 #define LIBDNG_EXPOSUREPROGRAM_PORTRAIT 7
 #define LIBDNG_EXPOSUREPROGRAM_LANDSCAPE 8
 
+#define LIBDNG_ILLUMINANT_UNKNOWN 0
+#define LIBDNG_ILLUMINANT_DAYLIGHT 1
+#define LIBDNG_ILLUMINANT_FLUORESCENT 2
+#define LIBDNG_ILLUMINANT_TUNGSTEN 3
+#define LIBDNG_ILLUMINANT_FLASH 4
+#define LIBDNG_ILLUMINANT_FINE_WEATHER 9
+#define LIBDNG_ILLUMINANT_CLOUDY_WEATHER 10
+#define LIBDNG_ILLUMINANT_SHADE 11
+#define LIBDNG_ILLUMINANT_DAYLIGHT_FLUORESCENT 12
+#define LIBDNG_ILLUMINANT_DAY_WHITE_FLUORESCENT 13
+#define LIBDNG_ILLUMINANT_COOL_WHITE_FLUORESCENT 14
+#define LIBDNG_ILLUMINANT_WHITE_FLUORESCENT 15
+#define LIBDNG_ILLUMINANT_STANDARD_A 17
+#define LIBDNG_ILLUMINANT_STANDARD_B 18
+#define LIBDNG_ILLUMINANT_STANDARD_C 19
+#define LIBDNG_ILLUMINANT_D55 20
+#define LIBDNG_ILLUMINANT_D65 21
+#define LIBDNG_ILLUMINANT_D75 22
+#define LIBDNG_ILLUMINANT_D50 23
+#define LIBDNG_ILLUMINANT_ISO_TUNGSTEN 24
+#define LIBDNG_ILLUMINANT_OTHER 255
+
 EXPORT int
 libdng_init();
 
@@ -111,4 +133,11 @@ libdng_write_with_thumbnail(libdng_info *dng, const char *path, unsigned int wid
 	const uint8_t *data,
 	size_t length, unsigned int thumb_width, unsigned int thumb_height, const uint8_t *thumb, size_t thumb_length);
 
+EXPORT int
+libdng_read(libdng_info *dng, const char *path);
+
+EXPORT int
+libdng_read_image(libdng_info *dng, const char *path, uint8_t index, uint8_t **data, size_t *length, uint32_t *width,
+	uint32_t *height);
+
 #endif //LIBDNG_LIBRARY_H

+ 132 - 0
src/libdng.c

@@ -350,3 +350,135 @@ libdng_write_with_thumbnail(libdng_info *dng, const char *path, unsigned int wid
 
 	return 1;
 }
+
+int
+libdng_read(libdng_info *dng, const char *path)
+{
+	TIFF *tif = TIFFOpen(path, "r");
+	if (!tif) {
+		return 0;
+	}
+	unsigned short count;
+	char *cvalues;
+	float *fvalues;
+	uint8_t *u8values;
+	uint32_t *u32values;
+
+	// Reading the "main" image which is the thumbnail
+	TIFFGetField(tif, TIFFTAG_ORIENTATION, &dng->orientation);
+	if (TIFFGetField(tif, TIFFTAG_MAKE, &cvalues) == 1) {
+		dng->camera_make = strdup(cvalues);
+	}
+	if (TIFFGetField(tif, TIFFTAG_MODEL, &cvalues) == 1) {
+		dng->camera_model = strdup(cvalues);
+	}
+	if (TIFFGetField(tif, TIFFTAG_UNIQUECAMERAMODEL, &dng->unique_camera_model) == 1) {
+		dng->unique_camera_model = strdup(cvalues);
+	}
+	if (TIFFGetField(tif, TIFFTAG_SOFTWARE, &cvalues) == 1) {
+		dng->software = strdup(cvalues);
+	}
+
+	if (TIFFGetField(tif, DNGTAG_ASSHOTNEUTRAL, &count, &fvalues) == 1) {
+		for (int i = 0; i < count; i++) {
+			dng->neutral[i] = fvalues[i];
+		}
+	}
+	if (TIFFGetField(tif, DNGTAG_ANALOGBALANCE, &count, &fvalues) == 1) {
+		for (int i = 0; i < count; i++) {
+			dng->analogbalance[i] = fvalues[i];
+		}
+	}
+
+	if (TIFFGetField(tif, DNGTAG_COLOR_MATRIX_1, &count, &fvalues) == 1) {
+		for (int i = 0; i < count; i++) {
+			dng->color_matrix_1[i] = fvalues[i];
+		}
+	}
+	if (TIFFGetField(tif, DNGTAG_COLOR_MATRIX_2, &count, &fvalues) == 1) {
+		for (int i = 0; i < count; i++) {
+			dng->color_matrix_2[i] = fvalues[i];
+		}
+	}
+	if (TIFFGetField(tif, DNGTAG_FORWARDMATRIX1, &count, &fvalues) == 1) {
+		for (int i = 0; i < count; i++) {
+			dng->forward_matrix_1[i] = fvalues[i];
+		}
+	}
+	if (TIFFGetField(tif, DNGTAG_FORWARDMATRIX2, &count, &fvalues) == 1) {
+		for (int i = 0; i < count; i++) {
+			dng->forward_matrix_2[i] = fvalues[i];
+		}
+	}
+
+	int subifd_count = 0;
+	void *ptr;
+	toff_t subifd_offsets[2];
+	TIFFGetField(tif, TIFFTAG_SUBIFD, &subifd_count, &ptr);
+	memcpy(subifd_offsets, ptr, subifd_count * sizeof(subifd_offsets[0]));
+	TIFFSetSubDirectory(tif, subifd_offsets[0]);
+
+	TIFFGetField(tif, TIFFTAG_BITSPERSAMPLE, &dng->bit_depth);
+
+	if (TIFFGetField(tif, DNGTAG_WHITELEVEL, &count, &u32values) == 1) {
+		dng->whitelevel = u32values[0];
+	} else {
+		dng->whitelevel = (1 << dng->bit_depth) - 1;
+	}
+	if (TIFFGetField(tif, DNGTAG_CFAPATTERN, &count, &u8values) == 1) {
+		if (count > 4) {
+			fprintf(stderr, "overflow in CFAPATTERN length %d > 4\n", count);
+			return 0;
+		}
+		for (int i = 0; i < count; i++) {
+			dng->cfapattern[i] = u8values[i];
+		}
+	}
+
+	TIFFClose(tif);
+	return 1;
+}
+
+int
+libdng_read_image(libdng_info *dng, const char *path, uint8_t index, uint8_t **data, size_t *length, uint32_t *width,
+	uint32_t *height)
+{
+	TIFF *tif = TIFFOpen(path, "r");
+	if (!tif) {
+		return 0;
+	}
+
+	if (index == 1) {
+		int subifd_count = 0;
+		void *ptr;
+		toff_t subifd_offsets[2];
+		TIFFGetField(tif, TIFFTAG_SUBIFD, &subifd_count, &ptr);
+		memcpy(subifd_offsets, ptr, subifd_count * sizeof(subifd_offsets[0]));
+		TIFFSetSubDirectory(tif, subifd_offsets[0]);
+	}
+
+	uint32_t samples_per_pixel;
+	uint32_t bits_per_sample;
+
+	TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, width);
+	TIFFGetField(tif, TIFFTAG_IMAGELENGTH, height);
+	TIFFGetField(tif, TIFFTAG_SAMPLESPERPIXEL, &samples_per_pixel);
+	TIFFGetField(tif, TIFFTAG_BITSPERSAMPLE, &bits_per_sample);
+
+	tsize_t scanline_size = TIFFScanlineSize(tif);
+
+	float bytes_per_pixel = (float) samples_per_pixel * (float) bits_per_sample / 8.0f;
+	(*length) = (uint32_t) ((float) (*width) * (float) (*height) * bytes_per_pixel);
+	(*data) = malloc(*length);
+	if (*data == NULL) {
+		fprintf(stderr, "Could not allocate memory in libdng_read_image\n");
+		return 0;
+	}
+
+	for (uint32_t y = 0; y < *height; y++) {
+		TIFFReadScanline(tif, (*data) + (y * scanline_size), y, 0);
+	}
+
+	TIFFClose(tif);
+	return 1;
+}

+ 7 - 1
tests/CMakeLists.txt

@@ -10,5 +10,11 @@ add_executable(check_dng check_dng.c greatest.h)
 target_include_directories(check_dng PUBLIC include)
 target_link_libraries(check_dng PUBLIC libdng check)
 
+add_executable(check_dcp check_dcp.c greatest.h)
+target_include_directories(check_dcp PUBLIC include)
+target_link_libraries(check_dcp PUBLIC libdng check)
+configure_file(fixture/pinephone.dcp ${CMAKE_CURRENT_BINARY_DIR}/fixture/pinephone.dcp COPYONLY)
+
 add_test(NAME check_mode COMMAND check_mode)
-add_test(NAME check_dng COMMAND check_dng)
+add_test(NAME check_dng COMMAND check_dng)
+add_test(NAME check_dcp COMMAND check_dcp)

+ 81 - 0
tests/check_dcp.c

@@ -0,0 +1,81 @@
+#include <stdbool.h>
+#include <tiffio.h>
+#include "greatest.h"
+#include "libdng.h"
+
+
+TEST load_pinephone_dcp(void)
+{
+	libdng_info info = {0};
+	libdng_new(&info);
+
+	if (!libdng_load_calibration_file(&info, "fixture/pinephone.dcp")) {
+			FAILm("Could not load calibration file");
+	}
+
+	float colormatrix1[] = {
+		1.8464f, -0.9617f, 0.096f,
+		-0.4260f, 1.3753f, 0.3348f,
+		-0.1147f, 0.4933f, 1.6042f,
+	};
+	char name[20] = {0};
+	for (int i = 0; i < 9; i++) {
+		snprintf(name, 20, "COLORMATRIX1[%d]", i);
+			ASSERT_EQ_FMTm(strdup(name), colormatrix1[i], info.color_matrix_1[i], "%f");
+	}
+
+	float forwardmatrix1[] = {
+		0.7063f, 0.1361f, 0.1219f,
+		0.2691f, 0.6969f, 0.0340f,
+		0.0003f, 0.0855f, 0.7393f,
+	};
+	for (int i = 0; i < 9; i++) {
+		snprintf(name, 20, "FORWARDMATRIX1[%d]", i);
+			ASSERT_EQ_FMTm(strdup(name), forwardmatrix1[i], info.forward_matrix_1[i], "%f");
+	}
+
+		ASSERT_EQ_FMTm("ILLUMINANT1", LIBDNG_ILLUMINANT_STANDARD_A, info.illuminant_1, "%d");
+		ASSERT_EQ_FMTm("ILLUMINANT2", LIBDNG_ILLUMINANT_D65, info.illuminant_2, "%d");
+
+		ASSERT_EQ_FMTm("TONE_CURVE_LEN", (size_t) 4, info.tone_curve_length, "%zu");
+	double tonecurve[] = {0.0, 0.0, 1.0, 1.0};
+	for (size_t i = 0; i < info.tone_curve_length; i++) {
+		snprintf(name, 20, "TONECURVE[%zu]", i);
+			ASSERT_EQ_FMTm(strdup(name), tonecurve[i], info.tone_curve[i], "%f");
+	}
+
+	// HueSat map is 90x30x1 in this DCP (ProfileHueSatMapDims: Hues = 90, Sats = 30, Vals = 1)
+		ASSERT_EQ_FMTm("HUE_SAT_MAP_DIMS[0]", 90, info.hue_sat_map_dims[0], "%d");
+		ASSERT_EQ_FMTm("HUE_SAT_MAP_DIMS[1]", 30, info.hue_sat_map_dims[1], "%d");
+		ASSERT_EQ_FMTm("HUE_SAT_MAP_DIMS[2]", 1, info.hue_sat_map_dims[2], "%d");
+
+	size_t entries = info.hue_sat_map_dims[0] * info.hue_sat_map_dims[1] * info.hue_sat_map_dims[2];
+	for (size_t i = 0; i < entries; i = i + 3) {
+		if (info.hue_sat_map_data_1[i] < -360.0f || info.hue_sat_map_data_1[i] > 360.0f) {
+			snprintf(name, 20, "HUESATMAP1[%zu]", i);
+				FAILm(strdup(name));
+		}
+		if (info.hue_sat_map_data_2[i] < -360.0f || info.hue_sat_map_data_2[i] > 360.0f) {
+			snprintf(name, 20, "HUESATMAP2[%zu]", i);
+				FAILm(strdup(name));
+		}
+
+	}
+		PASS();
+}
+
+SUITE (test_suite)
+{
+		RUN_TEST(load_pinephone_dcp);
+}
+
+GREATEST_MAIN_DEFS();
+
+int
+main(int argc, char **argv)
+{
+	GREATEST_MAIN_BEGIN();
+	libdng_init();
+	RUN_SUITE(test_suite);
+	GREATEST_MAIN_END();
+}

+ 3 - 0
tests/fixture/meson.build

@@ -0,0 +1,3 @@
+
+fs = import('fs')
+fs.copyfile('pinephone.dcp')

BIN
tests/fixture/pinephone.dcp


+ 10 - 1
tests/meson.build

@@ -9,6 +9,15 @@ check_dng = executable('check_dng', 'check_dng.c', 'greatest.h',
     dependencies: libtiff,
     install: false,
 )
+check_dcp = executable('check_dcp', 'check_dcp.c', 'greatest.h',
+    include_directories: inc,
+    link_with: libdng,
+    dependencies: libtiff,
+    install: false,
+)
+
+subdir('fixture')
 
 test('check_mode', check_mode)
-test('check_dng', check_dng)
+test('check_dng', check_dng)
+test('check_dcp', check_dcp, workdir: meson.current_build_dir())

+ 64 - 0
util/dngmerge.c

@@ -0,0 +1,64 @@
+#include <getopt.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include "libdng.h"
+
+void
+usage(char *name)
+{
+	fprintf(stderr, "Usage: %s src-file dcp-file dst-file\n", name);
+	fprintf(stderr, "Merge color metadata from one TIFF file into the image data of another TIFF\n\n");
+}
+
+int
+main(int argc, char *argv[])
+{
+	libdng_init();
+	libdng_info info = {0};
+	libdng_new(&info);
+
+	if (argc < 3) {
+		fprintf(stderr, "Missing required argument\n");
+		usage(argv[0]);
+		return 1;
+	}
+
+	uint32_t width, height, thumb_width, thumb_height;
+	uint8_t *thumb;
+	uint8_t *image;
+	size_t thumb_length;
+	size_t image_length;
+
+	if (!libdng_read(&info, argv[1])) {
+		fprintf(stderr, "Could not load the metadata from the original file\n");
+		exit(1);
+	}
+
+	if (!libdng_read_image(&info, argv[1], 0, &thumb, &thumb_length, &thumb_width, &thumb_height)) {
+		fprintf(stderr, "Could not load thumbnail from the original file\n");
+		exit(1);
+	}
+	printf("Got %dx%d thumbnail of %zu bytes\n", thumb_width, thumb_height, thumb_length);
+
+	if (!libdng_read_image(&info, argv[1], 1, &image, &image_length, &width, &height)) {
+		fprintf(stderr, "Could not load image data from the original file\n");
+		exit(1);
+	}
+	printf("Got %dx%d image of %zu bytes\n", width, height, image_length);
+
+	if (!libdng_load_calibration_file(&info, argv[2])) {
+		fprintf(stderr, "Could not load calibration files: %s\n", argv[2]);
+		exit(1);
+	}
+
+	libdng_write_with_thumbnail(&info, argv[3], width, height, image, image_length, thumb_width, thumb_height, thumb,
+		thumb_length);
+
+	free(thumb);
+	free(image);
+	libdng_free(&info);
+	return 0;
+}