Browse Source

Initial commit

Martijn Braam 1 year ago
commit
27195b1287
6 changed files with 468 additions and 0 deletions
  1. 20 0
      CMakeLists.txt
  2. 17 0
      README.md
  3. 32 0
      include/libdng.h
  4. 145 0
      src/dng.h
  5. 168 0
      src/libdng.c
  6. 86 0
      util/makedng.c

+ 20 - 0
CMakeLists.txt

@@ -0,0 +1,20 @@
+cmake_minimum_required(VERSION 3.25)
+project(libdng C)
+
+set(CMAKE_C_STANDARD 23)
+set(LIBRARY_VERSION_MAJOR 0)
+set(LIBRARY_VERSION_STRING 0.1)
+set(CMAKE_C_VISIBILITY_PRESET hidden)
+
+add_library(libdng SHARED include/libdng.h src/libdng.c src/dng.h)
+
+set_target_properties(libdng PROPERTIES
+        VERSION ${LIBRARY_VERSION_STRING}
+        SOVERSION ${LIBRARY_VERSION_MAJOR}
+        PUBLIC_HEADER include/libdng.h)
+target_include_directories(libdng PUBLIC include)
+target_link_libraries(libdng "tiff")
+
+add_executable(makedng util/makedng.c)
+target_include_directories(makedng PUBLIC include)
+target_link_libraries(makedng PUBLIC libdng)

+ 17 - 0
README.md

@@ -0,0 +1,17 @@
+## Inside a DNG file
+
+* SubIFDType 0 is the original raw data
+* SubIFDType 1 is the thumbnail data
+* The recommendation is to store the thumbnail as the first IFD
+* TIFF metdata goes in the first IFD
+* EXIF tags are preferred
+* Camera profiles are stored in the first IFD
+
+## Required tags
+
+* DNGVersion
+* UniqueCameraModel
+
+## Neat tags
+
+* AnalogBalance stores the gains applied to the RGB channels by the sensor

+ 32 - 0
include/libdng.h

@@ -0,0 +1,32 @@
+#ifndef LIBDNG_LIBRARY_H
+#define LIBDNG_LIBRARY_H
+
+#include <limits.h>
+#include <time.h>
+#include <stdint.h>
+
+#define EXPORT __attribute__((__visibility__("default")))
+
+typedef struct {
+		char *camera_make;
+		char *camera_model;
+		char *unique_camera_model;
+		char *software;
+		uint16_t orientation;
+		struct tm datetime;
+} libdng_info;
+
+EXPORT int
+libdng_init();
+
+EXPORT void
+libdng_new(libdng_info *dng);
+
+EXPORT void
+libdng_free(libdng_info *dng);
+
+EXPORT int
+libdng_write(libdng_info *dng, const char *path, unsigned int width, unsigned int height, uint8_t *data,
+	size_t length);
+
+#endif //LIBDNG_LIBRARY_H

+ 145 - 0
src/dng.h

@@ -0,0 +1,145 @@
+#pragma once
+
+/*
+ * Tags copied from libtiff and renamed
+ */
+
+/* Adobe Digital Negative (DNG) format tags */
+#include <tiffio.h>
+
+#define DNGTAG_DNGVERSION 50706         /* &DNG version number */
+#define DNGTAG_DNGBACKWARDVERSION 50707 /* &DNG compatibility version */
+#define DNGTAG_UNIQUECAMERAMODEL 50708  /* &name for the camera model */
+#define DNGTAG_LOCALIZEDCAMERAMODEL                                           \
+    50709 /* &localized camera model                                           \
+             name */
+#define DNGTAG_CFAPLANECOLOR                                                  \
+    50710                                /* &CFAPattern->LinearRaw space       \
+                                            mapping */
+#define DNGTAG_CFALAYOUT 50711          /* &spatial layout of the CFA */
+#define DNGTAG_LINEARIZATIONTABLE 50712 /* &lookup table description */
+#define DNGTAG_BLACKLEVELREPEATDIM                                            \
+    50713                        /* &repeat pattern size for                   \
+                                    the BlackLevel tag */
+#define DNGTAG_BLACKLEVEL 50714 /* &zero light encoding level */
+#define DNGTAG_BLACKLEVELDELTAH                                               \
+    50715 /* &zero light encoding level                                        \
+             differences (columns) */
+#define DNGTAG_BLACKLEVELDELTAV                                               \
+    50716 /* &zero light encoding level                                        \
+             differences (rows) */
+#define DNGTAG_WHITELEVEL                                                     \
+    50717                          /* &fully saturated encoding                \
+                                      level */
+#define DNGTAG_DEFAULTSCALE 50718 /* &default scale factors */
+#define DNGTAG_DEFAULTCROPORIGIN                                              \
+    50719 /* &origin of the final image                                        \
+             area */
+#define DNGTAG_DEFAULTCROPSIZE                                                \
+    50720 /* &size of the final image                                          \
+             area */
+#define DNGTAG_COLORMATRIX1                                                   \
+    50721 /* &XYZ->reference color space                                       \
+             transformation matrix 1 */
+#define DNGTAG_COLORMATRIX2                                                   \
+    50722                                /* &XYZ->reference color space        \
+                                            transformation matrix 2 */
+#define DNGTAG_CAMERACALIBRATION1 50723 /* &calibration matrix 1 */
+#define DNGTAG_CAMERACALIBRATION2 50724 /* &calibration matrix 2 */
+#define DNGTAG_REDUCTIONMATRIX1                                               \
+    50725 /* &dimensionality reduction                                         \
+             matrix 1 */
+#define DNGTAG_REDUCTIONMATRIX2                                               \
+    50726 /* &dimensionality reduction                                         \
+             matrix 2 */
+#define DNGTAG_ANALOGBALANCE                                                  \
+    50727 /* &gain applied the stored raw                                      \
+             values*/
+#define DNGTAG_ASSHOTNEUTRAL                                                  \
+    50728 /* &selected white balance in                                        \
+             linear reference space */
+#define DNGTAG_ASSHOTWHITEXY                                                  \
+    50729 /* &selected white balance in                                        \
+             x-y chromaticity                                                  \
+             coordinates */
+#define DNGTAG_BASELINEEXPOSURE                                               \
+    50730                           /* &how much to move the zero              \
+                                       point */
+#define DNGTAG_BASELINENOISE 50731 /* &relative noise level */
+#define DNGTAG_BASELINESHARPNESS                                              \
+    50732 /* &relative amount of                                               \
+             sharpening */
+#define DNGTAG_BAYERGREENSPLIT                                                \
+    50733                                 /* &how closely the values of        \
+                                             the green pixels in the           \
+                                             blue/green rows track the         \
+                                             values of the green pixels        \
+                                             in the red/green rows */
+#define DNGTAG_LINEARRESPONSELIMIT 50734 /* &non-linear encoding range */
+#define DNGTAG_CAMERASERIALNUMBER 50735  /* &camera's serial number */
+#define DNGTAG_LENSINFO 50736            /* info about the lens */
+#define DNGTAG_CHROMABLURRADIUS 50737    /* &chroma blur radius */
+#define DNGTAG_ANTIALIASSTRENGTH                                              \
+    50738                            /* &relative strength of the              \
+                                        camera's anti-alias filter */
+#define DNGTAG_SHADOWSCALE 50739    /* &used by Adobe Camera Raw */
+#define DNGTAG_DNGPRIVATEDATA 50740 /* &manufacturer's private data */
+#define DNGTAG_MAKERNOTESAFETY                                                \
+    50741                                    /* &whether the EXIF MakerNote    \
+                                                tag is safe to preserve        \
+                                                along with the rest of the     \
+                                                EXIF data */
+#define DNGTAG_CALIBRATIONILLUMINANT1 50778 /* &illuminant 1 */
+#define DNGTAG_CALIBRATIONILLUMINANT2 50779 /* &illuminant 2 */
+#define DNGTAG_BESTQUALITYSCALE 50780       /* &best quality multiplier */
+#define DNGTAG_RAWDATAUNIQUEID                                                \
+    50781 /* &unique identifier for                                            \
+             the raw image data */
+#define DNGTAG_ORIGINALRAWFILENAME                                            \
+    50827 /* &file name of the original                                        \
+             raw file */
+#define DNGTAG_ORIGINALRAWFILEDATA                                            \
+    50828 /* &contents of the original                                         \
+             raw file */
+#define DNGTAG_ACTIVEAREA                                                     \
+    50829 /* &active (non-masked) pixels                                       \
+             of the sensor */
+#define DNGTAG_MASKEDAREAS                                                    \
+    50830                              /* &list of coordinates                 \
+                                          of fully masked pixels */
+#define DNGTAG_ASSHOTICCPROFILE 50831 /* &these two tags used to */
+#define DNGTAG_ASSHOTPREPROFILEMATRIX                                         \
+    50832                                     /* map cameras's color space     \
+                                                 into ICC profile space */
+#define DNGTAG_CURRENTICCPROFILE 50833       /* & */
+#define DNGTAG_CURRENTPREPROFILEMATRIX 50834 /* & */
+
+
+// DNG tags not present in libtiff
+#define DNGTAG_FORWARDMATRIX1 50964
+#define DNGTAG_FORWARDMATRIX2 50965
+#define DNGTAG_COLOR_MATRIX_1 50721
+#define DNGTAG_COLOR_MATRIX_2 50722
+#define DNGTAG_PROFILE_HUE_SAT_MAP_DIMS 50937
+#define DNGTAG_PROFILE_HUE_SAT_MAP_DATA_1 50938
+#define DNGTAG_PROFILE_HUE_SAT_MAP_DATA_2 50939
+#define DNGTAG_PROFILE_TONE_CURVE 50940
+#define DNGTAG_CALIBRATION_ILLUMINANT_1 50778
+#define DNGTAG_CALIBRATION_ILLUMINANT_2 50779
+#define DNGTAG_FORWARD_MATRIX_1 50964
+#define DNGTAG_FORWARD_MATRIX_2 50965
+
+
+/*
+ * Field definitions for the tags in the DNG spec
+ */
+
+static const TIFFFieldInfo custom_dng_fields[] = {
+	{DNGTAG_DNGVERSION,                 -1, -1, TIFF_BYTE,      FIELD_CUSTOM, 1, 1, "DNGVersion"},
+	{DNGTAG_FORWARDMATRIX1,             -1, -1, TIFF_SRATIONAL, FIELD_CUSTOM, 1, 1, "ForwardMatrix1"},
+	{DNGTAG_FORWARDMATRIX2,             -1, -1, TIFF_SRATIONAL, FIELD_CUSTOM, 1, 1, "ForwardMatrix2"},
+	{DNGTAG_PROFILE_TONE_CURVE,         -1, -1, TIFF_FLOAT,     FIELD_CUSTOM, 1, 1, "ProfileToneCurve"},
+	{DNGTAG_PROFILE_HUE_SAT_MAP_DIMS,   -1, -1, TIFF_FLOAT,     FIELD_CUSTOM, 1, 1, "ProfileHueSatMapDims"},
+	{DNGTAG_PROFILE_HUE_SAT_MAP_DATA_1, -1, -1, TIFF_FLOAT,     FIELD_CUSTOM, 1, 1, "ProfileHueSatMapData1"},
+	{DNGTAG_PROFILE_HUE_SAT_MAP_DATA_2, -1, -1, TIFF_FLOAT,     FIELD_CUSTOM, 1, 1, "ProfileHueSatMapData2"},
+};

+ 168 - 0
src/libdng.c

@@ -0,0 +1,168 @@
+#include "libdng.h"
+#include "dng.h"
+
+#include <stdio.h>
+#include <tiffio.h>
+#include <sys/time.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define DNG_SUBFILETYPE_ORIGINAL 0
+#define DNG_SUBFILETYPE_THUMBNAIL 1
+#define DNG_SUBFILETYPE_ALPHAMASK 4
+#define DNG_SUBFILETYPE_THUMBNAIL_ALPHAMASK 5
+#define DNG_SUBFILETYPE_THUMBNAIL_EXTRA 0x10001
+
+static void
+register_dng_tags(TIFF *tif)
+{
+	TIFFMergeFieldInfo(tif,
+		custom_dng_fields,
+		sizeof(custom_dng_fields) / sizeof(custom_dng_fields[0]));
+}
+
+int
+libdng_init()
+{
+	TIFFSetTagExtender(register_dng_tags);
+	return 0;
+}
+
+void
+libdng_new(libdng_info *dng)
+{
+	dng->orientation = 1;
+}
+
+int
+libdng_set_make_model(libdng_info *dng, char *make, char *model)
+{
+	if (dng == NULL)
+		return 0;
+
+	dng->camera_make = strdup(make);
+	dng->camera_model = strdup(model);
+	return 1;
+}
+
+void
+libdng_free(libdng_info *dng)
+{
+	if (dng->camera_make != NULL)
+		free(dng->camera_make);
+}
+
+int
+libdng_set_datetime(libdng_info *dng, struct tm time)
+{
+	if (dng == NULL)
+		return 0;
+
+	dng->datetime = time;
+	return 1;
+}
+
+int
+libdng_set_datetime_now(libdng_info *dng)
+{
+	if (dng == NULL)
+		return 0;
+
+	time_t rawtime;
+	time(&rawtime);
+	dng->datetime = *(localtime(&rawtime));
+	return 1;
+}
+
+int
+libdng_write(libdng_info *dng, const char *path, unsigned int width, unsigned int height, uint8_t *data, size_t length)
+{
+	TIFF *tif = TIFFOpen(path, "w");
+	if (!tif) {
+		return -1;
+	}
+
+	char datetime[20] = {0};
+	if (dng->datetime.tm_year) {
+		strftime(datetime, 20, "%Y:%m:%d %H:%M:%S", &dng->datetime);
+	}
+
+	// First IFD describes the thumbnail and contains most of the metadata
+	// Tags are in numerical order
+	TIFFSetField(tif, TIFFTAG_SUBFILETYPE, DNG_SUBFILETYPE_THUMBNAIL);
+	TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, width >> 4);
+	TIFFSetField(tif, TIFFTAG_IMAGELENGTH, height >> 4);
+	TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 8);
+	TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_NONE);
+	TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB);
+
+	if (dng->camera_make != NULL)
+		TIFFSetField(tif, TIFFTAG_MAKE, dng->camera_make);
+	if (dng->camera_model != NULL)
+		TIFFSetField(tif, TIFFTAG_MODEL, dng->camera_model);
+	if (dng->orientation != 1)
+		TIFFSetField(tif, TIFFTAG_ORIENTATION, dng->orientation);
+	if (dng->software != NULL)
+		TIFFSetField(tif, TIFFTAG_SOFTWARE, dng->software);
+
+	if (dng->datetime.tm_year) {
+		TIFFSetField(tif, TIFFTAG_DATETIME, datetime);
+	}
+
+	TIFFSetField(tif, DNGTAG_DNGVERSION, "\001\004\0\0");
+
+	char ucm[255];
+	if (dng->unique_camera_model != NULL) {
+		snprintf(ucm, sizeof(ucm), "%s", dng->unique_camera_model);
+	} else if (dng->camera_make == NULL && dng->camera_model == NULL) {
+		snprintf(ucm, sizeof(ucm), "%s", "LibDNG");
+	} else {
+		snprintf(ucm, sizeof(ucm), "%s %s", dng->camera_make, dng->camera_model);
+	}
+	TIFFSetField(tif, DNGTAG_UNIQUECAMERAMODEL, ucm);
+
+	// Write black thumbnail, only windows uses this
+	{
+		unsigned char *buf = (unsigned char *) calloc(1, (width >> 4) * 3);
+		for (int row = 0; row < (height >> 4); row++) {
+			TIFFWriteScanline(tif, buf, row, 0);
+		}
+		free(buf);
+	}
+
+	if (!TIFFWriteDirectory(tif)) {
+		return -1;
+	}
+
+	// Define the raw data IFD
+	TIFFSetField(tif, TIFFTAG_SUBFILETYPE, DNG_SUBFILETYPE_ORIGINAL);
+	TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, width);
+	TIFFSetField(tif, TIFFTAG_IMAGELENGTH, height);
+	unsigned int stride = width;
+	for (int row = 0; row < height; row++) {
+		TIFFWriteScanline(tif, (void *) data + (row * stride), row, 0);
+	}
+	if (!TIFFWriteDirectory(tif)) {
+		return -1;
+	}
+
+	TIFFCreateEXIFDirectory(tif);
+
+	if (dng->datetime.tm_year) {
+		TIFFSetField(tif, EXIFTAG_DATETIMEORIGINAL, datetime);
+		TIFFSetField(tif, EXIFTAG_DATETIMEDIGITIZED, datetime);
+	}
+
+	uint64_t exif_offset = 0;
+	TIFFWriteCustomDirectory(tif, &exif_offset);
+	TIFFFreeDirectory(tif);
+
+	// Update exif pointer
+	TIFFSetDirectory(tif, 0);
+	TIFFSetField(tif, TIFFTAG_EXIFIFD, exif_offset);
+	TIFFRewriteDirectory(tif);
+
+	TIFFClose(tif);
+
+	return 0;
+}

+ 86 - 0
util/makedng.c

@@ -0,0 +1,86 @@
+#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 -w width -h height srcfile dstfile\n", name);
+	fprintf(stderr, "Convert raw sensor data to DNG\n\n");
+	fprintf(stderr, "Arguments:\n");
+	fprintf(stderr, "  -w width    Source data width\n");
+	fprintf(stderr, "  -h height   Source data height\n");
+}
+
+int
+main(int argc, char *argv[])
+{
+	int c;
+
+	char *end;
+	long val;
+
+	libdng_init();
+	libdng_info info;
+	libdng_new(&info);
+	unsigned int width = 0;
+	unsigned int height = 0;
+
+	while ((c = getopt(argc, argv, "w:h:")) != -1) {
+		switch (c) {
+			case 'w':
+				val = strtol(optarg, &end, 10);
+				width = (unsigned int) val;
+				break;
+			case 'h':
+				val = strtol(optarg, &end, 10);
+				height = (unsigned int) val;
+				break;
+			case '?':
+				if (optopt == 'd' || optopt == 'l') {
+					fprintf(stderr, "Option -%c requires an argument.\n", optopt);
+				} else if (isprint(optopt)) {
+					fprintf(stderr, "Unknown option '-%c'\n", optopt);
+				} else {
+					fprintf(stderr, "Unknown option character x%x\n", optopt);
+				}
+				return 1;
+			default:
+				return 1;
+		}
+	}
+
+	if (argc - optind < 2) {
+		fprintf(stderr, "Missing required argument\n");
+		usage(argv[0]);
+		return 1;
+	}
+
+	if (width == 0) {
+		fprintf(stderr, "The width argument is required\n");
+		usage(argv[0]);
+		return 1;
+	}
+
+	printf("Reading %s\n", argv[optind]);
+	FILE *src = fopen(argv[optind], "r");
+	if (src == NULL) {
+		fprintf(stderr, "Can't open source file: %s\n", strerror(errno));
+		return 1;
+	}
+	fseek(src, 0L, SEEK_END);
+	long src_size = ftell(src);
+	rewind(src);
+	uint8_t *data = malloc(src_size);
+	fread(data, src_size, 1, src);
+	fclose(src);
+
+	libdng_write(&info, argv[optind + 1], width, height, data, src_size);
+	free(data);
+	libdng_free(&info);
+	return 0;
+}