Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
220 changes: 220 additions & 0 deletions src/common/dng_opcode.c
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

#include <glib.h>
#include <stdio.h>
#include <string.h>

#include "debug.h"
#include "dng_opcode.h"
Expand Down Expand Up @@ -57,6 +58,31 @@ static uint32_t _get_long(uint8_t *ptr)
return GUINT32_FROM_BE(in);
}


static void _put_long(uint8_t *ptr, uint32_t val)
{
uint32_t out = GUINT32_TO_BE(val);
memcpy(ptr, &out, sizeof(out));
}

static void _put_double(uint8_t *ptr, double val)
{
guint64 out;
union { guint64 in; double v; } u;
u.v = val;
out = GUINT64_TO_BE(u.in);
memcpy(ptr, &out, sizeof(out));
}

static void _put_float(uint8_t *ptr, float val)
{
guint32 out;
union { guint32 in; float v; } u;
u.v = val;
out = GUINT32_TO_BE(u.in);
memcpy(ptr, &out, sizeof(out));
}

void dt_dng_opcode_process_opcode_list_2(uint8_t *buf, uint32_t buf_size, dt_image_t *img)
{
g_list_free_full(img->dng_gain_maps, g_free);
Expand Down Expand Up @@ -177,3 +203,197 @@ void dt_dng_opcode_process_opcode_list_3(uint8_t *buf, uint32_t buf_size, dt_ima
count--;
}
}


// Serialize OpcodeList2 (gain maps) from dt_image_t to a buffer
// Returns a newly allocated buffer with the opcode list, or NULL on error.
// The caller is responsible for freeing the returned buffer.
uint8_t *dt_dng_opcode_serialize_opcode_list_2(const dt_image_t *img, uint32_t *size)
{
// Only serialize if we have gain maps
if(!img->dng_gain_maps || g_list_length(img->dng_gain_maps) == 0)
{
*size = 0;
return NULL;
}

// Count total parameter size for all gain maps
uint32_t total_param_size = 0;
GList *list = img->dng_gain_maps;
while(list)
{
dt_dng_gain_map_t *gm = (dt_dng_gain_map_t *)list->data;
// Each gain map has 76 bytes fixed + gain_count * 4 bytes
const uint32_t gain_count = gm->map_points_v * gm->map_points_h * gm->map_planes;
total_param_size += 76 + gain_count * sizeof(float);
list = g_list_next(list);
}

const int opcode_count = g_list_length(img->dng_gain_maps);
uint32_t total_size = 4 + opcode_count * 16 + total_param_size;

uint8_t *buf = g_malloc(total_size);
if(!buf)
{
*size = 0;
return NULL;
}

uint8_t *ptr = buf;

// Write opcode count
_put_long(ptr, opcode_count);
ptr += 4;

// Write each gain map opcode
list = img->dng_gain_maps;
while(list)
{
dt_dng_gain_map_t *gm = (dt_dng_gain_map_t *)list->data;

// Opcode header
_put_long(ptr, OPCODE_ID_GAINMAP); // opcode_id
_put_long(ptr + 4, 0); // flags (0 = mandatory)
_put_long(ptr + 8, 1); // version

const uint32_t gain_count = gm->map_points_v * gm->map_points_h * gm->map_planes;
const uint32_t param_size = 76 + gain_count * sizeof(float);
_put_long(ptr + 12, param_size); // param_size

ptr += 16;

// Write parameters
_put_long(ptr, gm->top);
_put_long(ptr + 4, gm->left);
_put_long(ptr + 8, gm->bottom);
_put_long(ptr + 12, gm->right);
_put_long(ptr + 16, gm->plane);
_put_long(ptr + 20, gm->planes);
_put_long(ptr + 24, gm->row_pitch);
_put_long(ptr + 28, gm->col_pitch);
_put_long(ptr + 32, gm->map_points_v);
_put_long(ptr + 36, gm->map_points_h);
_put_double(ptr + 40, gm->map_spacing_v);
_put_double(ptr + 48, gm->map_spacing_h);
_put_double(ptr + 56, gm->map_origin_v);
_put_double(ptr + 64, gm->map_origin_h);
_put_long(ptr + 72, gm->map_planes);
ptr += 76;

// Write gain values
for(uint32_t i = 0; i < gain_count; i++)
{
_put_float(ptr, gm->map_gain[i]);
ptr += 4;
}

list = g_list_next(list);
}

*size = total_size;
return buf;
}


// Serialize OpcodeList3 (lens corrections) from dt_image_t to a buffer
// Returns a newly allocated buffer with the opcode list, or NULL on error.
// The caller is responsible for freeing the returned buffer.
uint8_t *dt_dng_opcode_serialize_opcode_list_3(const dt_image_t *img, uint32_t *size)
{
const dt_image_correction_data_t *cd = &img->exif_correction_data;

// Only serialize if we have DNG corrections and at least one type is present
if(img->exif_correction_type != CORRECTION_TYPE_DNG) return NULL;
if(!cd->dng.has_warp && !cd->dng.has_vignette) return NULL;

// Count how many opcodes we need to serialize
int opcode_count = 0;
if(cd->dng.has_warp) opcode_count++;
if(cd->dng.has_vignette) opcode_count++;

// Calculate total buffer size:
// - 4 bytes for opcode count
// - For each opcode: 16 bytes header + parameter size
// - Warp rectilinear: 4 (planes) + 8*6 (coefficients per plane) + 8*2 (center) bytes
// - Vignette radial: 8*5 (coefficients) + 8*2 (center) = 56 bytes param
uint32_t total_size = 4; // count at start
if(cd->dng.has_warp)
total_size += 16 + 4 + 8 * 6 * cd->dng.planes + 8 * 2; // header + param
if(cd->dng.has_vignette)
total_size += 16 + 8 * 5 + 8 * 2; // header + param

uint8_t *buf = g_malloc(total_size);
if(!buf) return NULL;

uint8_t *ptr = buf;

// Write opcode count
_put_long(ptr, opcode_count);
ptr += 4;

// Write warp opcode if present
if(cd->dng.has_warp)
{
// Opcode header: opcode_id (4), flags (4), version (4), param_size (4)
_put_long(ptr, OPCODE_ID_WARP_RECTILINEAR); // opcode_id
_put_long(ptr + 4, 0); // flags (0 = mandatory)
_put_long(ptr + 8, 1); // version

const uint32_t param_size = 4 + 8 * 6 * cd->dng.planes + 8 * 2;
_put_long(ptr + 12, param_size); // param_size

ptr += 16;

// Write parameters: planes (4), then coefficients
_put_long(ptr, cd->dng.planes);
ptr += 4;

// Write warp coefficients for each plane
for(int p = 0; p < cd->dng.planes; p++)
{
for(int i = 0; i < 6; i++)
{
_put_double(ptr, cd->dng.cwarp[p][i]);
ptr += 8;
}
}

// Write center coordinates
for(int i = 0; i < 2; i++)
{
_put_double(ptr, cd->dng.centre_warp[i]);
ptr += 8;
}
}

// Write vignette opcode if present
if(cd->dng.has_vignette)
{
// Opcode header
_put_long(ptr, OPCODE_ID_VIGNETTE_RADIAL); // opcode_id
_put_long(ptr + 4, 0); // flags (0 = mandatory)
_put_long(ptr + 8, 1); // version

const uint32_t param_size = 8 * (5 + 2); // 5 coefficients + 2 center coordinates
_put_long(ptr + 12, param_size); // param_size

ptr += 16;

// Write vignette coefficients
for(int i = 0; i < 5; i++)
{
_put_double(ptr, cd->dng.cvig[i]);
ptr += 8;
}

// Write center coordinates
for(int i = 0; i < 2; i++)
{
_put_double(ptr, cd->dng.centre_vig[i]);
ptr += 8;
}
}

*size = total_size;
return buf;
}
4 changes: 4 additions & 0 deletions src/common/dng_opcode.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ typedef struct dt_dng_gain_map_t
void dt_dng_opcode_process_opcode_list_2(uint8_t *buf, uint32_t size, dt_image_t *img);
void dt_dng_opcode_process_opcode_list_3(uint8_t *buf, uint32_t size, dt_image_t *img);

// Serialize DNG opcode lists for writing
uint8_t *dt_dng_opcode_serialize_opcode_list_2(const dt_image_t *img, uint32_t *size);
uint8_t *dt_dng_opcode_serialize_opcode_list_3(const dt_image_t *img, uint32_t *size);

G_END_DECLS

// clang-format off
Expand Down
40 changes: 40 additions & 0 deletions src/imageio/imageio_dng.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include "imageio/imageio_dng.h"
#include "imageio/imageio_jpeg.h"
#include "common/colorspaces_inline_conversions.h"
#include "common/dng_opcode.h"
#include "common/exif.h"
#include "common/image.h"
#include "develop/imageop_math.h"
Expand All @@ -42,10 +43,43 @@
#define TIFFTAG_PREVIEWCOLORSPACE 50970
#endif

// fallback for older libtiff without DNG opcode tags
#ifndef TIFFTAG_OPCODELIST1
#define TIFFTAG_OPCODELIST1 51008
#endif
#ifndef TIFFTAG_OPCODELIST2
#define TIFFTAG_OPCODELIST2 51009
#endif
#ifndef TIFFTAG_OPCODELIST3
#define TIFFTAG_OPCODELIST3 51022
#endif

// DNG uses SRATIONAL / RATIONAL for matrix and WB tags. libtiff accepts
// these as float/double arrays and handles the conversion; we just pass
// the values as double

// Write DNG opcode lists to the current TIFF directory
static void _write_dng_opcode_lists(TIFF *tif, const dt_image_t *img)
{
// Serialize and write OpcodeList2 (gain maps) if present
uint32_t opcode_list_2_size = 0;
uint8_t *opcode_list_2 = dt_dng_opcode_serialize_opcode_list_2(img, &opcode_list_2_size);
if(opcode_list_2 && opcode_list_2_size > 0)
{
TIFFSetField(tif, TIFFTAG_OPCODELIST2, opcode_list_2_size, opcode_list_2);
g_free(opcode_list_2);
}

// Serialize and write OpcodeList3 (lens corrections) if present
uint32_t opcode_list_3_size = 0;
uint8_t *opcode_list_3 = dt_dng_opcode_serialize_opcode_list_3(img, &opcode_list_3_size);
if(opcode_list_3 && opcode_list_3_size > 0)
{
TIFFSetField(tif, TIFFTAG_OPCODELIST3, opcode_list_3_size, opcode_list_3);
g_free(opcode_list_3);
}
}

// libtiff error/warning handlers — replacing whatever else in the
// process may have installed (e.g. ImageMagick's, which crashes on
// NULL exception context). these just log to stderr and never abort
Expand Down Expand Up @@ -474,6 +508,9 @@ int dt_imageio_dng_write_cfa_bayer(const char *filename,
TIFFSetField(tif, TIFFTAG_DEFAULTCROPORIGIN, default_crop_origin);
TIFFSetField(tif, TIFFTAG_DEFAULTCROPSIZE, default_crop_size);

// Write DNG opcode lists (lens corrections, gain maps) if present
_write_dng_opcode_lists(tif, img);

// scanline write
int res = 0;
for(int y = 0; y < height && res == 0; y++)
Expand Down Expand Up @@ -613,6 +650,9 @@ int dt_imageio_dng_write_linear(const char *filename,
TIFFSetField(tif, TIFFTAG_DEFAULTCROPORIGIN, default_crop_origin);
TIFFSetField(tif, TIFFTAG_DEFAULTCROPSIZE, default_crop_size);

// Write DNG opcode lists (lens corrections, gain maps) if present
_write_dng_opcode_lists(tif, img);

// scanline write: float [0, 1] normalized camRGB -> uint16
// [0, 65535]. BlackLevel=0 and WhiteLevel=65535 let the
// re-importer recover the [0, 1] range via the standard raw
Expand Down
1 change: 1 addition & 0 deletions src/imageio/imageio_dng.h
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ void dt_imageio_dng_write_float(const char *filename,
// - AsShotNeutral from img->wb_coeffs (inverted)
// - ColorMatrix1 from img->adobe_XYZ_to_CAM
// - Make / Model / UniqueModel from img->camera_maker / camera_model
// - OpcodeList2/3 from img->exif_correction_data (if present)
//
// @param filename output path (UTF-8)
// @param cfa Bayer mosaic (uint16, width * height samples, row-major)
Expand Down
Loading