From 2e8bbf0e43a49abcaa7ac55958b644be2f5e1af1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Eric=20Janouch?= Date: Mon, 5 Jun 2023 09:27:16 +0200 Subject: Improve LibRaw thumbnail choice Make use of LibRaw 0.21.0's extended thumbnail API. --- fiv-thumbnail.c | 201 ++++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 152 insertions(+), 49 deletions(-) diff --git a/fiv-thumbnail.c b/fiv-thumbnail.c index 12be5aa..848c299 100644 --- a/fiv-thumbnail.c +++ b/fiv-thumbnail.c @@ -245,10 +245,152 @@ orient_thumbnail(cairo_surface_t *surface) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #ifdef HAVE_LIBRAW +#if LIBRAW_VERSION >= LIBRAW_MAKE_VERSION(0, 21, 0) + +static int +extract_libraw_compare(const void *a, const void *b) +{ + const libraw_thumbnail_item_t **t1 = (const libraw_thumbnail_item_t **) a; + const libraw_thumbnail_item_t **t2 = (const libraw_thumbnail_item_t **) b; + float p1 = (float) (*t1)->twidth * (*t1)->theight; + float p2 = (float) (*t2)->twidth * (*t2)->theight; + return (p2 < p1) - (p1 < p2); +} + +static gboolean +extract_libraw_unpack(libraw_data_t *iprc, int *flip, GError **error) +{ + int count = iprc->thumbs_list.thumbcount; + if (count <= 0) { + set_error(error, "no thumbnails found"); + return FALSE; + } + + // The old libraw_unpack_thumb() goes for the largest thumbnail, + // but we currently want the smallest usable thumbnail. Order them. + libraw_thumbnail_item_t **sorted = g_malloc_n(count, sizeof *sorted); + for (int i = 0; i < count; i++) + sorted[i] = &iprc->thumbs_list.thumblist[i]; + qsort(sorted, count, sizeof *sorted, extract_libraw_compare); + + // With the raw.pixls.us database, zero dimensions occur in two cases: + // - when thumbcount should really be 0, + // - with the last, huge JPEG thumbnail in CR3 raws. + // The maintainer refuses to change anything about it (#589). + int i = 0; + while (i < count && (!sorted[i]->twidth || !sorted[i]->theight)) + i++; + + // Ignore thumbnails whose decoding is likely to be a waste of time. + // XXX: This primarily targets the TIFF/EP shortcut code, + // because decoding a thumbnail will always be /much/ quicker than a render. + // TODO(p): Maybe don't mark raw image thumbnails as low-quality + // if they're the right aspect ratio, and of sufficiently large size. + // And I still worry about tflip. + float output_pixels = (float) iprc->sizes.iwidth * iprc->sizes.iheight; + // Note that the ratio may even be larger than 1, as seen with CR2 files. + while (i < count && + (float) sorted[count - 1]->twidth * sorted[count - 1]->theight > + output_pixels * 0.75) + count--; + + // The smallest size thumbnail is very often forced to be 4:3, + // and the remaining space is filled with black, looking quite wrong. + // It isn't really possible to strip those borders, because many are JPEGs. + // + // Another reason to skip thumbnails of mismatching aspect ratios is + // to avoid browser items from jumping around when low-quality thumbnails + // get replaced with their final versions. + // + // Note that some of them actually have borders on all four sides + // (Nikon/D50/DSC_5155.NEF, Nikon/D70/20170902_0047.NEF, + // Nikon/D70s/RAW_NIKON_D70S.NEF), or even on just one side + // (Leica/LEICA M MONOCHROM (Typ 246), Leica/M (Typ 240)). + // Another interesting possibility is Sony/DSC-HX99/DSC00001.ARW, + // where the correct-ratio thumbnail has borders but the main image doesn't. + // + // The problematic thumbnail is usually, but not always, sized 160x120, + // and some of them may actually be fine. + float output_ratio = (float) iprc->sizes.iwidth / iprc->sizes.iheight; + while (i < count) { + // XXX: tflip is less reliable than libraw_dcraw_make_mem_thumb() + // and reading out Orientation from the resulting Exif. + float ratio = sorted[i]->tflip == 5 || sorted[i]->tflip == 6 + ? (float) sorted[i]->theight / sorted[i]->twidth + : (float) sorted[i]->twidth / sorted[i]->theight; + if (fabsf(ratio - output_ratio) < 0.05) + break; + i++; + } + + // Avoid pink-tinted readouts of CR2 IFD2 (#590). + // + // This thumbnail can also have a black stripe on the left and the top, + // which we should remove if using fixed LibRaw > 0.21.1. + if (i < count && iprc->idata.maker_index == LIBRAW_CAMERAMAKER_Canon && + sorted[i]->tformat == LIBRAW_INTERNAL_THUMBNAIL_KODAK_THUMB) + i++; + + if (i < count) + i = sorted[i] - iprc->thumbs_list.thumblist; + + g_free(sorted); + if (i == count) { + set_error(error, "no suitable thumbnails found"); + return FALSE; + } + + int err = 0; + if ((err = libraw_unpack_thumb_ex(iprc, i))) { + set_error(error, libraw_strerror(err)); + return FALSE; + } + *flip = iprc->thumbs_list.thumblist[i].tflip; + return TRUE; +} + +#else // LIBRAW_VERSION < LIBRAW_MAKE_VERSION(0, 21, 0) + +static gboolean +extract_libraw_unpack(libraw_data_t *iprc, int *flip, GError **error) +{ + int err = 0; + if ((err = libraw_unpack_thumb(iprc))) { + set_error(error, libraw_strerror(err)); + return FALSE; + } + + // The main image's "flip" often matches up, but sometimes doesn't, e.g.: + // - Phase One/H 25/H25_Outdoor_.IIQ + // - Phase One/H 25/H25_IT8.7-2_Card.TIF + // - Leaf/Aptus 22/L_003172.mos (JPEG) + *flip = iprc->sizes.flip + return TRUE; +} + +#endif // LIBRAW_VERSION < LIBRAW_MAKE_VERSION(0, 21, 0) + +// LibRaw does a weird permutation here, so follow the documentation, +// which assumes that mirrored orientations never happen. +static FivIoOrientation +extract_libraw_unflip(int flip) +{ + switch (flip) { + break; case 0: + return FivIoOrientation0; + break; case 3: + return FivIoOrientation180; + break; case 5: + return FivIoOrientation270; + break; case 6: + return FivIoOrientation90; + break; default: + return FivIoOrientationUnknown; + } +} static cairo_surface_t * -extract_libraw_bitmap( - libraw_data_t *iprc, libraw_processed_image_t *image, GError **error) +extract_libraw_bitmap(libraw_processed_image_t *image, int flip, GError **error) { // Anything else is extremely rare. if (image->colors != 3 || image->bits != 8) { @@ -264,19 +406,7 @@ extract_libraw_bitmap( out[i++] = in[0] << 16 | in[1] << 8 | in[2]; cairo_surface_mark_dirty(surface); - // LibRaw actually turns an 8 to 5, so follow the documentation. - FivIoOrientation orient = FivIoOrientationUnknown; - switch (iprc->sizes.flip) { - break; case 3: - orient = FivIoOrientation180; - break; case 5: - orient = FivIoOrientation270; - break; case 6: - orient = FivIoOrientation90; - break; default: - return surface; - } - + FivIoOrientation orient = extract_libraw_unflip(flip); cairo_surface_set_user_data( surface, &fiv_io_key_orientation, (void *) (intptr_t) orient, NULL); return surface; @@ -299,37 +429,14 @@ extract_libraw(GFile *target, GMappedFile *mf, GError **error) set_error(error, libraw_strerror(err)); goto fail; } - -#if LIBRAW_VERSION >= LIBRAW_MAKE_VERSION(0, 21, 0) - if (!iprc->thumbs_list.thumbcount) { - set_error(error, "no thumbnails found"); - goto fail; - } - - // The old libraw_unpack_thumb() goes for the largest thumbnail, - // but we currently want the smallest thumbnail. - // TODO(p): To handle the ugly IFD0 thumbnail of NEF, - // try to go for the second smallest size. Remember to reflect tflip. - int best_index = 0; - float best_pixels = INFINITY; - for (int i = 0; i < iprc->thumbs_list.thumbcount; i++) { - float pixels = (float) iprc->thumbs_list.thumblist[i].twidth * - (float) iprc->thumbs_list.thumblist[i].theight; - if (pixels && pixels < best_pixels) { - best_index = i; - best_pixels = pixels; - } - } - if ((err = libraw_unpack_thumb_ex(iprc, best_index))) { + if ((err = libraw_adjust_sizes_info_only(iprc))) { set_error(error, libraw_strerror(err)); goto fail; } -#else // LIBRAW_VERSION < LIBRAW_MAKE_VERSION(0, 21, 0) - if ((err = libraw_unpack_thumb(iprc))) { - set_error(error, libraw_strerror(err)); + + int flip = 0; + if (!extract_libraw_unpack(iprc, &flip, error)) goto fail; - } -#endif // LIBRAW_VERSION < LIBRAW_MAKE_VERSION(0, 21, 0) libraw_processed_image_t *image = libraw_dcraw_make_mem_thumb(iprc, &err); if (!image) { @@ -340,10 +447,6 @@ extract_libraw(GFile *target, GMappedFile *mf, GError **error) // Bitmap thumbnails generally need rotating, e.g.: // - Hasselblad/H4D-50/2-9-2017_street_0012.fff // - OnePlus/One/IMG_20150729_201116.dng (and more DNGs in general) - // Though it's apparent LibRaw doesn't adjust the thumbnails to match - // the main image's "flip" field (it just happens to match up often), e.g.: - // - Phase One/H 25/H25_Outdoor_.IIQ (correct Orientation in IFD0) - // - Phase One/H 25/H25_IT8.7-2_Card.TIF (correctly missing in IFD0) // // JPEG thumbnails generally have the right rotation in their Exif, e.g.: // - Canon/EOS-1Ds Mark II/RAW_CANON_1DSM2.CR2 @@ -353,10 +456,10 @@ extract_libraw(GFile *target, GMappedFile *mf, GError **error) // - Panasonic/DMC-FZ70/P1000836.RW2 // - Samsung/NX200/2013-05-08-194524__sam6589.srw // - Sony/DSC-HX95/DSC00018.ARW + // Note that LibRaw inserts its own Exif segment if it doesn't find one, + // and this may differ from flip. // // Some files are problematic and we won't bother with special-casing: - // - Leaf/Aptus 22/L_003172.mos (JPEG)'s thumbnail wrongly contains - // Exif Orientation 6, and sizes.flip also contains 6. // - Nokia/Lumia 1020/RAW_NOKIA_LUMIA_1020.DNG (bitmap) has wrong color. // - Ricoh/GXR/R0017428.DNG (JPEG) seems to be plainly invalid. switch (image->type) { @@ -366,7 +469,7 @@ extract_libraw(GFile *target, GMappedFile *mf, GError **error) target, g_bytes_new(image->data, image->data_size), &dummy, error); break; case LIBRAW_IMAGE_BITMAP: - surface = extract_libraw_bitmap(iprc, image, error); + surface = extract_libraw_bitmap(image, flip, error); break; default: set_error(error, "unsupported embedded thumbnail"); -- cgit v1.2.3