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
7 changes: 7 additions & 0 deletions data/darktableconfig.xml.in
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,13 @@
<shortdescription>color manage cached thumbnails</shortdescription>
<longdescription>if enabled, cached thumbnails will be color managed so that lighttable and filmstrip can show correct colors. otherwise the results may look wrong once the display profile gets changed.</longdescription>
</dtconfig>
<dtconfig>
<name>cache/import_raw_jpeg_optimization</name>
<type>bool</type>
<default>true</default>
<shortdescription>optimize RAW+JPEG import using JPEG for thumbnails</shortdescription>
<longdescription>when enabled and both RAW and JPEG files are imported together, the JPEG file will be used to quickly populate the mipmap cache instead of expensive RAW demosaicing. this significantly speeds up thumbnail generation. the feature is only active when companion JPEG files are found during RAW import.</longdescription>
</dtconfig>
<dtconfig>
<name>opencl_device_priority</name>
<type>string</type>
Expand Down
5 changes: 3 additions & 2 deletions src/cli/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -569,7 +569,8 @@ int main(int argc, char *arg[])
if(!g_file_test(fullname, G_FILE_TEST_IS_DIR) && dt_supported_image(fname))
{
// Import each supported image file directly
const dt_imgid_t imgid = dt_image_import(filmid, fullname, TRUE, TRUE);
const dt_imgid_t imgid =
dt_image_import(filmid, fullname, NULL, TRUE, TRUE);
if(dt_is_valid_imgid(imgid))
{
id_list = g_list_append(id_list, GINT_TO_POINTER(imgid));
Expand Down Expand Up @@ -598,7 +599,7 @@ int main(int argc, char *arg[])

gchar *directory = g_path_get_dirname(input);
filmid = dt_film_new(&film, directory);
const dt_imgid_t id = dt_image_import(filmid, input, TRUE, TRUE);
const dt_imgid_t id = dt_image_import(filmid, input, NULL, TRUE, TRUE);
g_free(directory);
if(!dt_is_valid_imgid(id))
{
Expand Down
40 changes: 39 additions & 1 deletion src/common/camera_control.c
Original file line number Diff line number Diff line change
Expand Up @@ -1213,9 +1213,47 @@ static gboolean _camera_initialize(const dt_camctl_t *c,
return TRUE;
}

static int _ext_priority(const char *ext) {
/* Ensure JPEG/preview is imported before RAW for identical basenames
— this populates the mipmap/preview cache (speeding thumbnails) */
if (dt_imageio_is_raw_preview_by_extension(ext)) {
return 0;
} else {
return 1;
}
}

static int _sort_filename(gchar *a, gchar *b)
{
return g_strcmp0(a, b);
/* compare by basename (without extension); if equal prefer extensions by
* priority */
gchar *ba = g_path_get_basename(a);
gchar *bb = g_path_get_basename(b);

char *da = strrchr(ba, '.');
char *db = strrchr(bb, '.');

if (da)
*da = '\0';
if (db)
*db = '\0';

int res = g_strcmp0(ba, bb);
if (res == 0) {
/* same basename, decide by extension priority */
const char *exta = da ? da + 1 : NULL;
const char *extb = db ? db + 1 : NULL;
int pa = _ext_priority(exta);
int pb = _ext_priority(extb);
if (pa != pb)
res = pa - pb;
else
res = g_strcmp0(a, b);
}

g_free(ba);
g_free(bb);
return res;
}

void dt_camctl_import(const dt_camctl_t *c,
Expand Down
2 changes: 1 addition & 1 deletion src/common/darktable.c
Original file line number Diff line number Diff line change
Expand Up @@ -447,7 +447,7 @@ dt_imgid_t dt_load_from_string(const gchar *input,
gchar *directory = g_path_get_dirname((const gchar *)filename);
dt_film_t film;
const dt_filmid_t filmid = dt_film_new(&film, directory);
imgid = dt_image_import(filmid, filename, TRUE, TRUE);
imgid = dt_image_import(filmid, filename, NULL, TRUE, TRUE);
g_free(directory);
if(dt_is_valid_imgid(imgid))
{
Expand Down
35 changes: 22 additions & 13 deletions src/common/image.c
Original file line number Diff line number Diff line change
Expand Up @@ -1795,10 +1795,10 @@ static int _image_read_duplicates(const uint32_t id,

static dt_imgid_t _image_import_internal(const dt_filmid_t film_id,
const char *filename,
const char *preview_jpeg_filepath,
const gboolean override_ignore_nonraws,
const gboolean lua_locking,
const gboolean raise_signals)
{
const gboolean raise_signals) {
char *normalized_filename = dt_util_normalize_path(filename);
if(!normalized_filename || !dt_util_test_image_file(normalized_filename))
{
Expand Down Expand Up @@ -2056,6 +2056,16 @@ static dt_imgid_t _image_import_internal(const dt_filmid_t film_id,
// make sure that there are no stale thumbnails left
dt_mipmap_cache_remove(id);

// Try to optimize RAW+JPEG import by using companion JPEG for mipmap cache
// This speeds up thumbnail generation by avoiding RAW demosaicing
if (preview_jpeg_filepath != NULL) {
dt_print(DT_DEBUG_ALWAYS,
"[image_import_internal] importing companion JPEG `%s' for mipmap "
"cache of img id %d\n",
preview_jpeg_filepath, id);
dt_mipmap_cache_import_jpeg_to_mips(id, preview_jpeg_filepath);
}

// Always keep write timestamp in database and possibly write xmp
dt_image_synch_all_xmp(normalized_filename);

Expand Down Expand Up @@ -2137,20 +2147,19 @@ dt_imgid_t dt_image_get_id(const dt_filmid_t film_id,
return id;
}

dt_imgid_t dt_image_import(const dt_filmid_t film_id,
const char *filename,
dt_imgid_t dt_image_import(const dt_filmid_t film_id, const char *filename,
const char *preview_jpeg_filepath,
const gboolean override_ignore_nonraws,
const gboolean raise_signals)
{
return _image_import_internal(film_id, filename, override_ignore_nonraws,
TRUE, raise_signals);
const gboolean raise_signals) {
return _image_import_internal(film_id, filename, preview_jpeg_filepath,
override_ignore_nonraws, TRUE, raise_signals);
}

dt_imgid_t dt_image_import_lua(const dt_filmid_t film_id,
const char *filename,
const gboolean override_ignore_nonraws)
{
return _image_import_internal(film_id, filename, override_ignore_nonraws, FALSE, TRUE);
dt_imgid_t dt_image_import_lua(const dt_filmid_t film_id, const char *filename,
const char *preview_jpeg_filepath,
const gboolean override_ignore_nonraws) {
return _image_import_internal(film_id, filename, preview_jpeg_filepath,
override_ignore_nonraws, FALSE, TRUE);
}

void dt_image_init(dt_image_t *img)
Expand Down
8 changes: 4 additions & 4 deletions src/common/image.h
Original file line number Diff line number Diff line change
Expand Up @@ -444,14 +444,14 @@ dt_imgid_t dt_image_get_id_full_path(const gchar *filename);
dt_imgid_t dt_image_get_id(const dt_filmid_t film_id,
const gchar *filename);
/** imports a new image from raw/etc file and adds it to the data base and image cache. Use from threads other than lua.*/
dt_imgid_t dt_image_import(dt_filmid_t film_id,
const char *filename,
dt_imgid_t dt_image_import(dt_filmid_t film_id, const char *filename,
const char *preview_jpeg_filepath,
const gboolean override_ignore_nonraws,
const gboolean raise_signals);
/** imports a new image from raw/etc file and adds it to the data base
* and image cache. Use from lua thread.*/
dt_imgid_t dt_image_import_lua(const dt_filmid_t film_id,
const char *filename,
dt_imgid_t dt_image_import_lua(const dt_filmid_t film_id, const char *filename,
const char *preview_jpeg_filepath,
const gboolean override_ignore_nonraws);
/** removes the given image from the database. */
void dt_image_remove(const dt_imgid_t imgid);
Expand Down
3 changes: 2 additions & 1 deletion src/common/import_session.c
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,8 @@ void dt_import_session_unref(dt_import_session_t *self)

void dt_import_session_import(dt_import_session_t *self)
{
const dt_imgid_t imgid = dt_image_import(self->film->id, self->current_filename, TRUE, TRUE);
const dt_imgid_t imgid =
dt_image_import(self->film->id, self->current_filename, NULL, TRUE, TRUE);
if(dt_is_valid_imgid(imgid))
{
DT_CONTROL_SIGNAL_RAISE(DT_SIGNAL_VIEWMANAGER_THUMBTABLE_ACTIVATE, imgid);
Expand Down
103 changes: 103 additions & 0 deletions src/common/mipmap_cache.c
Original file line number Diff line number Diff line change
Expand Up @@ -1715,6 +1715,109 @@ void dt_mipmap_cache_copy_thumbnails(const dt_imgid_t dst_imgid,
}
}

// Optimize RAW+JPEG import by using companion JPEG file to populate mipmap
// cache. When RAW and JPEG files are imported together, this function reads the
// JPEG and uses it to populate mipmap level 8, then downscales to generate
// levels 0-7. This avoids expensive RAW demosaicing for thumbnail generation.
// Returns TRUE if optimization was applied, FALSE if no companion JPEG found or
// feature disabled.
gboolean dt_mipmap_cache_import_jpeg_to_mips(const dt_imgid_t imgid,
const char *filename) {
// Check if feature is enabled via preference
if (!dt_conf_get_bool("cache/import_raw_jpeg_optimization"))
return FALSE;

if (!dt_is_valid_imgid(imgid) || !filename || !filename[0])
return FALSE;

// Read the JPEG file into memory
uint8_t *jpeg_blob = NULL;
size_t jpeg_blob_len = 0;
GError *gerror = NULL;

if (!g_file_get_contents(filename, (gchar **)&jpeg_blob, &jpeg_blob_len,
&gerror)) {
dt_print(DT_DEBUG_CACHE,
"[mipmap_cache] failed to read companion JPEG %s: %s", filename,
gerror->message);
g_error_free(gerror);
return FALSE;
}

// Decompress JPEG header to get dimensions and color space
dt_imageio_jpeg_t jpg;
if (dt_imageio_jpeg_decompress_header(jpeg_blob, jpeg_blob_len, &jpg)) {
dt_print(DT_DEBUG_CACHE,
"[mipmap_cache] failed to read JPEG header from %s", filename);
g_free(jpeg_blob);
return FALSE;
}

// Get write-locked mipmap buffer for level 8 (full preview)
dt_mipmap_buffer_t buf;
dt_mipmap_cache_get(&buf, imgid, DT_MIPMAP_8, DT_MIPMAP_BLOCKING, 'w');

if (!buf.cache_entry || !buf.buf) {
dt_print(DT_DEBUG_CACHE,
"[mipmap_cache] failed to get mipmap 8 buffer for image %u",
imgid);
g_free(jpeg_blob);
return FALSE;
}

// Decompress JPEG into a temporary buffer first
uint8_t *tmp = dt_alloc_align_uint8((size_t)jpg.width * jpg.height * 4);
if (!tmp) {
dt_print(DT_DEBUG_CACHE,
"[mipmap_cache] memory allocation failed for temp jpeg buffer for "
"image %u",
imgid);
dt_mipmap_cache_release(&buf);
g_free(jpeg_blob);
return FALSE;
}

if (dt_imageio_jpeg_decompress(&jpg, tmp)) {
dt_print(DT_DEBUG_CACHE,
"[mipmap_cache] failed to decompress JPEG for image %u", imgid);
dt_free_align(tmp);
dt_mipmap_cache_release(&buf);
g_free(jpeg_blob);
return FALSE;
}

// Apply RAW image orientation so the cached image matches the RAW
const dt_image_orientation_t orientation = dt_image_get_orientation(imgid);

// Destination buffer is the cache payload
dt_mipmap_buffer_dsc_t *dsc = (dt_mipmap_buffer_dsc_t *)buf.cache_entry->data;
uint32_t out_w = 0, out_h = 0;
// use flip_and_zoom with destination size equal to source, so we just
// reorient without scaling
dt_iop_flip_and_zoom_8(tmp, jpg.width, jpg.height, buf.buf, jpg.width,
jpg.height, orientation, &out_w, &out_h);

dsc->width = out_w;
dsc->height = out_h;
dsc->iscale = 1.0f;
dsc->color_space = dt_imageio_jpeg_read_color_space(&jpg);
dsc->flags &= ~DT_MIPMAP_BUFFER_DSC_FLAG_GENERATE; // Mark as generated

dt_print(DT_DEBUG_CACHE,
"[mipmap_cache] populated mipmap 8 (%dx%d) from companion JPEG for "
"image %u (oriented)",
dsc->width, dsc->height, imgid);

dt_free_align(tmp);
dt_mipmap_cache_release(&buf);
g_free(jpeg_blob);

// TODO: Generate mipmap levels 0-7 by downscaling from level 8
// This would require cairo or similar scaling infrastructure

return TRUE;
}

// clang-format off
// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
// vim: shiftwidth=2 expandtab tabstop=2 cindent
Expand Down
7 changes: 7 additions & 0 deletions src/common/mipmap_cache.h
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,13 @@ void dt_mipmap_cache_copy_thumbnails(const dt_imgid_t dst_imgid, const dt_imgid_
// return the mipmap corresponding to text value saved in prefs
dt_mipmap_size_t dt_mipmap_cache_get_min_mip_from_pref(const char *value);

// optimize RAW+JPEG import by extracting embedded JPEG and using it as base for
// mipmap cache. extracts JPEG from RAW file, writes to mipmap level 8, then
// downscales to create all mipmap tiers. returns TRUE if optimization was
// applied, FALSE if JPEG extraction failed or feature disabled.
gboolean dt_mipmap_cache_import_jpeg_to_mips(const dt_imgid_t imgid,
const char *filename);

G_END_DECLS

// clang-format off
Expand Down
Loading