From 4bd4892fd0ff756844c95b69089f1e1c213a66cc Mon Sep 17 00:00:00 2001 From: Sameer Rahmani Date: Mon, 9 Jun 2025 10:16:36 +0100 Subject: [PATCH] Add the basic support for loading RAW format files with libRAW Co-authored-by: Kleis Auke Wolthuizen --- libvips/foreign/dcrawload.c | 342 +++++++++++++++++++++++++++++++++ libvips/foreign/foreign.c | 6 + libvips/foreign/meson.build | 1 + libvips/include/vips/foreign.h | 4 + meson.build | 7 + meson_options.txt | 5 + 6 files changed, 365 insertions(+) create mode 100644 libvips/foreign/dcrawload.c diff --git a/libvips/foreign/dcrawload.c b/libvips/foreign/dcrawload.c new file mode 100644 index 0000000000..b20f863a44 --- /dev/null +++ b/libvips/foreign/dcrawload.c @@ -0,0 +1,342 @@ +/* load RAW camera files with libraw + */ + +/* + + This file is part of VIPS. + + VIPS is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + + */ + +/* + + These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk + + */ + +/* +#define DEBUG + */ + +#ifdef HAVE_CONFIG_H +#include +#endif /*HAVE_CONFIG_H*/ +#include + +#include +#include + +#include + +#ifdef HAVE_LIBRAW + +#include + +#define VIPS_TYPE_FOREIGN_LOAD_DCRAW (vips_foreign_load_dcraw_get_type()) +#define VIPS_FOREIGN_LOAD_DCRAW(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), \ + VIPS_TYPE_FOREIGN_LOAD_DCRAW, VipsForeignLoadDcRaw)) + +static const char *vips_foreign_dcraw_suffs[] = { + ".arw", ".cr2", ".cr3", ".crw", ".dng", ".nef", ".nrw", + ".orf", ".pef", ".raf", ".raw", ".rw2", ".srw", ".x3f", + ".erf", ".kdc", ".mdc", ".mos", ".pxn", ".srf", + ".3fr", ".ari", ".cap", ".cin", ".dcr", ".fff", ".iiq", ".k25", + ".mrw", ".ori", ".rwl", ".sr2", NULL +}; + +typedef struct _VipsForeignLoadDcRaw { + VipsForeignLoad parent_object; + + /* LibRaw processor. + */ + libraw_data_t *raw_processor; + + /* Internal Libraw processed image + */ + libraw_processed_image_t *processed; + + /* Load from this source (set by subclasses). + */ + VipsSource *source; + +} VipsForeignLoadDcRaw; + +typedef VipsForeignLoadClass VipsForeignLoadDcRawClass; + +G_DEFINE_ABSTRACT_TYPE(VipsForeignLoadDcRaw, vips_foreign_load_dcraw, + VIPS_TYPE_FOREIGN_LOAD); + +static void +vips_foreign_load_dcraw_dispose(GObject *gobject) +{ + VipsForeignLoadDcRaw *raw = (VipsForeignLoadDcRaw *) gobject; + + VIPS_FREEF(libraw_dcraw_clear_mem, raw->processed); + VIPS_FREEF(libraw_close, raw->raw_processor); + VIPS_UNREF(raw->source); + + G_OBJECT_CLASS(vips_foreign_load_dcraw_parent_class)->dispose(gobject); +} + +static int +vips_foreign_load_dcraw_build(VipsObject *object) +{ + VipsForeignLoadDcRaw *raw = VIPS_FOREIGN_LOAD_DCRAW(object); + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS(raw); + raw->raw_processor = libraw_init(0); + + if (!raw->raw_processor) { + vips_error(class->nickname, _("unable to initialize libraw")); + return -1; + } + + return VIPS_OBJECT_CLASS(vips_foreign_load_dcraw_parent_class) + ->build(object); +} + +static VipsForeignFlags +vips_foreign_load_dcraw_get_flags_filename(const char *filename) +{ + return 0; +} + +static VipsForeignFlags +vips_foreign_load_dcraw_get_flags(VipsForeignLoad *load) +{ + return 0; +} + +static int +vips_foreign_load_dcraw_header(VipsForeignLoad *load) +{ + VipsForeignLoadDcRaw *raw = (VipsForeignLoadDcRaw *) load; + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS(raw); + + const void *data; + size_t length; + VipsImage *image; + VipsImage *x; + GDateTime *dt; + + /* Map the whole source into memory. + */ + if (!(data = vips_source_map(raw->source, &length))) + return -1; + + /* Enforce VIPS_FORMAT_USHORT output. + */ + raw->raw_processor->params.output_bps = 16; + + /* Apply camera white balance. + */ + raw->raw_processor->params.use_camera_wb = 1; + + int result = libraw_open_buffer(raw->raw_processor, data, length); + if (result != LIBRAW_SUCCESS) { + vips_error(class->nickname, "%s : %s", + _("unable to read the source"), libraw_strerror(result)); + return -1; + } + + result = libraw_unpack(raw->raw_processor); + if (result != LIBRAW_SUCCESS) { + vips_error(class->nickname, "%s : %s", + _("unable to unpack the source"), libraw_strerror(result)); + return -1; + } + + /* Process the image (demosaicing, white balance, etc.) + */ + result = libraw_dcraw_process(raw->raw_processor); + + if (result != LIBRAW_SUCCESS) { + vips_error(class->nickname, + "error processing RAW data: %s\n", libraw_strerror(result)); + return -1; + } + + if (!(raw->processed = + libraw_dcraw_make_mem_image(raw->raw_processor, &result))) { + vips_error(class->nickname, + "error creating processed image: %s\n", libraw_strerror(result)); + return -1; + } + + if (!(image = vips_image_new_from_memory( + raw->processed->data, raw->processed->data_size, + raw->processed->width, raw->processed->height, + raw->processed->colors, VIPS_FORMAT_USHORT))) + return -1; + + VIPS_SETSTR(image->filename, + vips_connection_filename(VIPS_CONNECTION(raw->source))); + + /* Set custom metadata. + */ + vips_image_set_string(image, "raw-make", + raw->raw_processor->idata.make); + vips_image_set_string(image, "raw-model", + raw->raw_processor->idata.model); + vips_image_set_double(image, "raw-iso", + raw->raw_processor->other.iso_speed); + vips_image_set_double(image, "raw-shutter", + raw->raw_processor->other.shutter); + vips_image_set_double(image, "raw-aperture", + raw->raw_processor->other.aperture); + vips_image_set_double(image, "raw-focal-length", + raw->raw_processor->other.focal_len); + + if (raw->raw_processor->other.timestamp && + (dt = g_date_time_new_from_unix_utc( + raw->raw_processor->other.timestamp))) { + vips_image_set_string(image, "raw-timestamp", + g_date_time_format_iso8601(dt)); + g_date_time_unref(dt); + } + + /* What a hack. Remove the @out that's there now and replace it with + * our image. + */ + g_object_get(load, "out", &x, NULL); + g_object_unref(x); + g_object_unref(x); + + g_object_set(load, "out", image, NULL); + + return 0; +} + +static void +vips_foreign_load_dcraw_class_init(VipsForeignLoadDcRawClass *class) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS(class); + VipsObjectClass *object_class = (VipsObjectClass *) class; + VipsOperationClass *operation_class = VIPS_OPERATION_CLASS(class); + VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class; + + gobject_class->dispose = vips_foreign_load_dcraw_dispose; + + object_class->nickname = "dcrawload_base"; + object_class->description = _("load RAW camera files"); + object_class->build = vips_foreign_load_dcraw_build; + + /* LibRaw is fuzzed, but not by us. + */ + operation_class->flags |= VIPS_OPERATION_UNTRUSTED; + + load_class->get_flags = vips_foreign_load_dcraw_get_flags; + load_class->header = vips_foreign_load_dcraw_header; + load_class->load = NULL; +} + +static void +vips_foreign_load_dcraw_init(VipsForeignLoadDcRaw *raw) +{ +} + +typedef struct _VipsForeignLoadDcRawFile { + VipsForeignLoadDcRaw parent_object; + + /* Filename for load. + */ + char *filename; + +} VipsForeignLoadDcRawFile; + +typedef VipsForeignLoadDcRawClass VipsForeignLoadDcRawFileClass; + +G_DEFINE_TYPE(VipsForeignLoadDcRawFile, vips_foreign_load_dcraw_file, + vips_foreign_load_dcraw_get_type()); + +static int +vips_foreign_load_dcraw_file_build(VipsObject *object) +{ + VipsForeignLoadDcRaw *raw = VIPS_FOREIGN_LOAD_DCRAW(object); + VipsForeignLoadDcRawFile *file = (VipsForeignLoadDcRawFile *) object; + + if (file->filename && + !(raw->source = vips_source_new_from_file(file->filename))) + return -1; + + return VIPS_OBJECT_CLASS(vips_foreign_load_dcraw_file_parent_class) + ->build(object); +} + +static void +vips_foreign_load_dcraw_file_class_init(VipsForeignLoadDcRawFileClass *class) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS(class); + VipsObjectClass *object_class = (VipsObjectClass *) class; + VipsForeignClass *foreign_class = (VipsForeignClass *) class; + VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class; + + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + object_class->nickname = "dcrawload"; + object_class->description = _("load RAW with libraw"); + object_class->build = vips_foreign_load_dcraw_file_build; + + foreign_class->suffs = vips_foreign_dcraw_suffs; + + load_class->get_flags_filename = + vips_foreign_load_dcraw_get_flags_filename; + + VIPS_ARG_STRING(class, "filename", 1, + _("Filename"), + _("Filename to load from"), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET(VipsForeignLoadDcRawFile, filename), + NULL); +} + +static void +vips_foreign_load_dcraw_file_init(VipsForeignLoadDcRawFile *file) +{ +} + +#endif /*HAVE_LIBRAW*/ + +/** + * vips_dcrawload: + * @filename: file to load + * @out: (out): output image + * @...: `NULL`-terminated list of optional named arguments + * + * Read a RAW camera file using LibRaw. + * + * This loader supports the most RAW formats including: + * ARW, CR2, CR3, CRW, DNG, NEF, NRW, ORF, PEF, RAF, RAW, RW2, SRW, X3F, ... + * + * The loader applies demosaicing and basic processing to produce an RGB or + * grayscale image suitable for further processing. + * + * Returns: 0 on success, -1 on error. + */ +int +vips_dcrawload(const char *filename, VipsImage **out, ...) +{ + va_list ap; + int result; + + va_start(ap, out); + result = vips_call_split("dcrawload", ap, filename, out); + va_end(ap); + + return result; +} diff --git a/libvips/foreign/foreign.c b/libvips/foreign/foreign.c index a7d90489ea..02a2688b1f 100644 --- a/libvips/foreign/foreign.c +++ b/libvips/foreign/foreign.c @@ -3056,6 +3056,8 @@ vips_foreign_operation_init(void) extern GType vips_foreign_save_cgif_buffer_get_type(void); extern GType vips_foreign_save_cgif_target_get_type(void); + extern GType vips_foreign_load_dcraw_file_get_type(void); + vips_foreign_load_csv_file_get_type(); vips_foreign_load_csv_source_get_type(); vips_foreign_save_csv_file_get_type(); @@ -3143,6 +3145,10 @@ vips_foreign_operation_init(void) vips_foreign_load_nsgif_source_get_type(); #endif /*HAVE_NSGIF*/ +#ifdef HAVE_LIBRAW + vips_foreign_load_dcraw_file_get_type(); +#endif /*HAVE_LIBRAW*/ + #ifdef HAVE_CGIF vips_foreign_save_cgif_file_get_type(); vips_foreign_save_cgif_buffer_get_type(); diff --git a/libvips/foreign/meson.build b/libvips/foreign/meson.build index 71d55c882d..9f05529eb4 100644 --- a/libvips/foreign/meson.build +++ b/libvips/foreign/meson.build @@ -10,6 +10,7 @@ foreign_sources = files( 'cgifsave.c', 'csvload.c', 'csvsave.c', + 'dcrawload.c', 'dzsave.c', 'exif.c', 'fits.c', diff --git a/libvips/include/vips/foreign.h b/libvips/include/vips/foreign.h index 4a7ebbe2f2..d3e3c5e17e 100644 --- a/libvips/include/vips/foreign.h +++ b/libvips/include/vips/foreign.h @@ -872,6 +872,10 @@ VIPS_API int vips_gifsave_target(VipsImage *in, VipsTarget *target, ...) G_GNUC_NULL_TERMINATED; +VIPS_API +int vips_dcrawload(const char *filename, VipsImage **out, ...) + G_GNUC_NULL_TERMINATED; + VIPS_API int vips_heifload(const char *filename, VipsImage **out, ...) G_GNUC_NULL_TERMINATED; diff --git a/meson.build b/meson.build index 9e689682d0..b37b059bab 100644 --- a/meson.build +++ b/meson.build @@ -429,6 +429,12 @@ if openexr_dep.found() cfg_var.set('HAVE_OPENEXR', true) endif +libraw_dep = dependency('libraw_r', required: get_option('raw')) +if libraw_dep.found() + external_deps += libraw_dep + cfg_var.set('HAVE_LIBRAW', true) +endif + # 2.4 is the first one to have working threading and tiling libopenjp2_dep = dependency('libopenjp2', version: '>=2.4', required: get_option('openjpeg')) if libopenjp2_dep.found() @@ -689,6 +695,7 @@ build_features = { 'NIfTI load/save': ['libnifti', libnifti_found ? libnifti_dep : disabler()], 'FITS load/save': ['cfitsio', cfitsio_dep], 'GIF save': ['cgif', cgif_dep], + 'RAW load': ['libraw', libraw_dep], 'Magick @0@'.format('/'.join(get_option('magick-features'))): [get_option('magick-package'), magick_found ? magick_dep : disabler(), magick_module], }, } diff --git a/meson_options.txt b/meson_options.txt index 175a46c5e6..2de9efc281 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -203,6 +203,11 @@ option('quantizr', value: 'auto', description: 'Build with quantizr') +option('raw', + type: 'feature', + value: 'auto', + description: 'Build with libraw') + option('rsvg', type: 'feature', value: 'auto',