// load a full-res thumbnail: int dt_imageio_large_thumbnail(const char *filename, uint8_t **buffer, int32_t *width, int32_t *height, int32_t *orientation) { int ret = 0; int res = 1; // raw image thumbnail libraw_data_t *raw = libraw_init(0); libraw_processed_image_t *image = NULL; ret = libraw_open_file(raw, filename); if(ret) goto libraw_fail; ret = libraw_unpack_thumb(raw); if(ret) goto libraw_fail; ret = libraw_adjust_sizes_info_only(raw); if(ret) goto libraw_fail; image = libraw_dcraw_make_mem_thumb(raw, &ret); if(!image || ret) goto libraw_fail; *orientation = raw->sizes.flip; if(image->type == LIBRAW_IMAGE_JPEG) { dt_imageio_jpeg_t jpg; if(dt_imageio_jpeg_decompress_header(image->data, image->data_size, &jpg)) goto libraw_fail; *buffer = (uint8_t *)malloc((size_t)sizeof(uint8_t)*jpg.width*jpg.height*4); if(!*buffer) goto libraw_fail; *width = jpg.width; *height = jpg.height; if(dt_imageio_jpeg_decompress(&jpg, *buffer)) { free(*buffer); *buffer = 0; goto libraw_fail; } res = 0; } // clean up raw stuff. libraw_recycle(raw); libraw_close(raw); free(image); if(0) { libraw_fail: // fprintf(stderr,"[imageio] %s: %s\n", filename, libraw_strerror(ret)); libraw_close(raw); res = 1; } return res; }
// load a full-res thumbnail: int dt_imageio_large_thumbnail(const char *filename, uint8_t **buffer, int32_t *width, int32_t *height, dt_colorspaces_color_profile_type_t *color_space) { int res = 1; uint8_t *buf = NULL; char *mime_type = NULL; size_t bufsize; // get the biggest thumb from exif if(dt_exif_get_thumbnail(filename, &buf, &bufsize, &mime_type)) goto error; if(strcmp(mime_type, "image/jpeg") == 0) { // Decompress the JPG into our own memory format dt_imageio_jpeg_t jpg; if(dt_imageio_jpeg_decompress_header(buf, bufsize, &jpg)) goto error; *buffer = (uint8_t *)malloc((size_t)sizeof(uint8_t) * jpg.width * jpg.height * 4); if(!*buffer) goto error; *width = jpg.width; *height = jpg.height; // TODO: check if the embedded thumbs have a color space set! currently we assume that it's always sRGB *color_space = DT_COLORSPACE_SRGB; if(dt_imageio_jpeg_decompress(&jpg, *buffer)) { free(*buffer); *buffer = NULL; goto error; } res = 0; } else { #ifdef HAVE_GRAPHICSMAGICK ExceptionInfo exception; Image *image = NULL; ImageInfo *image_info = NULL; GetExceptionInfo(&exception); image_info = CloneImageInfo((ImageInfo *)NULL); image = BlobToImage(image_info, buf, bufsize, &exception); if(exception.severity != UndefinedException) CatchException(&exception); if(!image) { fprintf(stderr, "[dt_imageio_large_thumbnail GM] thumbnail not found?\n"); goto error_gm; } *width = image->columns; *height = image->rows; *color_space = DT_COLORSPACE_SRGB; // FIXME: this assumes that embedded thumbnails are always srgb *buffer = (uint8_t *)malloc((size_t)sizeof(uint8_t) * image->columns * image->rows * 4); if(!*buffer) goto error_gm; for(uint32_t row = 0; row < image->rows; row++) { uint8_t *bufprt = *buffer + (size_t)4 * row * image->columns; int gm_ret = DispatchImage(image, 0, row, image->columns, 1, "RGBP", CharPixel, bufprt, &exception); if(exception.severity != UndefinedException) CatchException(&exception); if(gm_ret != MagickPass) { fprintf(stderr, "[dt_imageio_large_thumbnail GM] error_gm reading thumbnail\n"); free(*buffer); *buffer = NULL; goto error_gm; } } // fprintf(stderr, "[dt_imageio_large_thumbnail GM] successfully decoded thumbnail\n"); res = 0; error_gm: if(image) DestroyImage(image); if(image_info) DestroyImageInfo(image_info); DestroyExceptionInfo(&exception); if(res) goto error; #else fprintf(stderr, "[dt_imageio_large_thumbnail] error: The thumbnail image is not in JPEG format, but DT " "was built without GraphicsMagick. Please rebuild DT with GraphicsMagick support " "enabled.\n"); #endif } if(res) { fprintf( stderr, "[dt_imageio_large_thumbnail] error: Not a supported thumbnail image format or broken thumbnail: %s\n", mime_type); goto error; } error: free(mime_type); free(buf); return res; }
static int dt_mipmap_cache_deserialize(dt_mipmap_cache_t *cache) { int32_t rd = 0; const dt_mipmap_size_t mip = DT_MIPMAP_2; uint8_t *blob = NULL; FILE *f = NULL; int file_width[mip+1], file_height[mip+1]; gchar dbfilename[DT_MAX_PATH_LEN]; if (dt_mipmap_cache_get_filename(dbfilename, sizeof(dbfilename))) { fprintf(stderr, "[mipmap_cache] could not retrieve cache filename; not deserializing\n"); return 1; } if (!strcmp(dbfilename, ":memory:")) { // fprintf(stderr, "[mipmap_cache] library is in memory; not deserializing\n"); return 0; } // drop any old cache if the database is new. in that case newly imported images will probably mapped to old thumbnails if(dt_database_is_new(darktable.db) && g_file_test(dbfilename, G_FILE_TEST_IS_REGULAR)) { fprintf(stderr, "[mipmap_cache] database is new, dropping old cache `%s'\n", dbfilename); goto read_finalize; } f = fopen(dbfilename, "rb"); if(!f) { if (errno == ENOENT) { fprintf(stderr, "[mipmap_cache] cache is empty, file `%s' doesn't exist\n", dbfilename); } else { fprintf(stderr, "[mipmap_cache] failed to open the cache from `%s'\n", dbfilename); } goto read_finalize; } // read version info: const int32_t magic = DT_MIPMAP_CACHE_FILE_MAGIC + DT_MIPMAP_CACHE_FILE_VERSION; int32_t magic_file = 0; rd = fread(&magic_file, sizeof(int32_t), 1, f); if(rd != 1) goto read_error; if(magic_file == DT_MIPMAP_CACHE_FILE_MAGIC + 22) { // same format, but compression was broken in 22 and below } else if(magic_file != magic) { if(magic_file > DT_MIPMAP_CACHE_FILE_MAGIC && magic_file < magic) fprintf(stderr, "[mipmap_cache] cache version too old, dropping `%s' cache\n", dbfilename); else fprintf(stderr, "[mipmap_cache] invalid cache file, dropping `%s' cache\n", dbfilename); goto read_finalize; } // also read compression type and yell out on missmatch. int32_t compression = -1; rd = fread(&compression, sizeof(int32_t), 1, f); if(rd != 1) goto read_error; if(compression != cache->compression_type) { fprintf(stderr, "[mipmap_cache] cache is %s, but settings say we should use %s, dropping `%s' cache\n", compression == 0 ? "uncompressed" : (compression == 1 ? "low quality compressed" : "high quality compressed"), cache->compression_type == 0 ? "no compression" : (cache->compression_type == 1 ? "low quality compression" : "high quality compression"), dbfilename); goto read_finalize; } if(compression && (magic_file == DT_MIPMAP_CACHE_FILE_MAGIC + 22)) { // compression is enabled and we have the affected version. can't read that. fprintf(stderr, "[mipmap_cache] dropping compressed cache v22 to regenerate mips without artifacts.\n"); goto read_finalize; } for (int i=0; i<=mip; i++) { rd = fread(&file_width[i], sizeof(int32_t), 1, f); if(rd != 1) goto read_error; rd = fread(&file_height[i], sizeof(int32_t), 1, f); if(rd != 1) goto read_error; if(file_width[i] != cache->mip[i].max_width || file_height[i] != cache->mip[i].max_height) { fprintf(stderr, "[mipmap_cache] cache settings changed, dropping `%s' cache\n", dbfilename); goto read_finalize; } } if(cache->compression_type) blob = NULL; else blob = malloc(sizeof(uint32_t)*file_width[mip]*file_height[mip]); while(!feof(f)) { int level = 0; rd = fread(&level, sizeof(int), 1, f); if (level > mip) break; int32_t key = 0; rd = fread(&key, sizeof(int32_t), 1, f); if(rd != 1) break; // first value is break only, goes to eof. int32_t length = 0; rd = fread(&length, sizeof(int32_t), 1, f); if(rd != 1) goto read_error; uint8_t *data = (uint8_t *)dt_cache_read_get(&cache->mip[level].cache, key); struct dt_mipmap_buffer_dsc* dsc = (struct dt_mipmap_buffer_dsc*)data; if(dsc->flags & DT_MIPMAP_BUFFER_DSC_FLAG_GENERATE) { if(cache->compression_type) { int32_t wd, ht; rd = fread(&wd, sizeof(int32_t), 1, f); if(rd != 1) goto read_error; rd = fread(&ht, sizeof(int32_t), 1, f); if(rd != 1) goto read_error; dsc->width = wd; dsc->height = ht; if(length != compressed_buffer_size(cache->compression_type, wd, ht)) goto read_error; // directly read from disk into cache: rd = fread(data + sizeof(*dsc), 1, length, f); if(rd != length) goto read_error; } else { // jpg too large? if(length > sizeof(uint32_t)*file_width[mip]*file_height[mip]) goto read_error; rd = fread(blob, sizeof(uint8_t), length, f); if(rd != length) goto read_error; // no compression, the image is still compressed on disk, as jpg dt_imageio_jpeg_t jpg; if(dt_imageio_jpeg_decompress_header(blob, length, &jpg) || (jpg.width > file_width[level] || jpg.height > file_height[level]) || dt_imageio_jpeg_decompress(&jpg, data+sizeof(*dsc))) { fprintf(stderr, "[mipmap_cache] failed to decompress thumbnail for image %d!\n", get_imgid(key)); } dsc->width = jpg.width; dsc->height = jpg.height; } dsc->flags &= ~DT_MIPMAP_BUFFER_DSC_FLAG_GENERATE; // these come write locked in case idata[3] == 1, so release that! dt_cache_write_release(&cache->mip[level].cache, key); } dt_cache_read_release(&cache->mip[level].cache, key); } fclose(f); free(blob); return 0; read_error: fprintf(stderr, "[mipmap_cache] failed to recover the cache from `%s'\n", dbfilename); read_finalize: if(f) fclose(f); free(blob); g_unlink(dbfilename); return 1; }
// TODO: use orientation to correctly rotate the image. // maybe this should be (partly) in common/imageio.[c|h]? static void _lib_import_update_preview(GtkFileChooser *file_chooser, gpointer data) { GtkWidget *preview; char *filename; GdkPixbuf *pixbuf = NULL; gboolean have_preview = FALSE; preview = GTK_WIDGET(data); filename = gtk_file_chooser_get_preview_filename(file_chooser); if(!g_file_test(filename, G_FILE_TEST_IS_REGULAR)) goto no_preview_fallback; // don't create dng thumbnails to avoid crashes in libtiff when these are hdr: char *c = filename + strlen(filename); while(c > filename && *c != '.') c--; if(!strcasecmp(c, ".dng")) goto no_preview_fallback; pixbuf = gdk_pixbuf_new_from_file_at_size(filename, 128, 128, NULL); have_preview = (pixbuf != NULL); if(!have_preview) { // raw image thumbnail int ret; libraw_data_t *raw = libraw_init(0); libraw_processed_image_t *image = NULL; ret = libraw_open_file(raw, filename); if(ret) goto libraw_fail; ret = libraw_unpack_thumb(raw); if(ret) goto libraw_fail; ret = libraw_adjust_sizes_info_only(raw); if(ret) goto libraw_fail; image = libraw_dcraw_make_mem_thumb(raw, &ret); if(!image || ret) goto libraw_fail; // const int orientation = raw->sizes.flip; GdkPixbuf *tmp; GdkPixbufLoader *loader = gdk_pixbuf_loader_new(); have_preview = gdk_pixbuf_loader_write(loader, image->data, image->data_size, NULL); tmp = gdk_pixbuf_loader_get_pixbuf(loader); gdk_pixbuf_loader_close(loader, NULL); float ratio; if(image->type == LIBRAW_IMAGE_JPEG) { // jpeg dt_imageio_jpeg_t jpg; if(dt_imageio_jpeg_decompress_header(image->data, image->data_size, &jpg)) goto libraw_fail; ratio = 1.0*jpg.height/jpg.width; } else { // bmp -- totally untested ratio = 1.0*image->height/image->width; } int width = 128, height = 128*ratio; pixbuf = gdk_pixbuf_scale_simple(tmp, width, height, GDK_INTERP_BILINEAR); if(loader) g_object_unref(loader); // clean up raw stuff. libraw_recycle(raw); libraw_close(raw); free(image); if(0) { libraw_fail: // fprintf(stderr,"[imageio] %s: %s\n", filename, libraw_strerror(ret)); libraw_close(raw); have_preview = FALSE; } } if(!have_preview) { no_preview_fallback: pixbuf = gdk_pixbuf_new_from_inline(-1, dt_logo_128x128, FALSE, NULL); have_preview = TRUE; } if(have_preview) gtk_image_set_from_pixbuf(GTK_IMAGE(preview), pixbuf); if(pixbuf) g_object_unref(pixbuf); g_free(filename); gtk_file_chooser_set_preview_widget_active(file_chooser, have_preview); }
static void _init_8( uint8_t *buf, uint32_t *width, uint32_t *height, const uint32_t imgid, const dt_mipmap_size_t size) { const uint32_t wd = *width, ht = *height; char filename[DT_MAX_PATH_LEN] = {0}; /* do not even try to process file if it isnt available */ dt_image_full_path(imgid, filename, DT_MAX_PATH_LEN); if (strlen(filename) == 0 || !g_file_test(filename, G_FILE_TEST_EXISTS)) { *width = *height = 0; return; } const int altered = dt_image_altered(imgid); int res = 1; const dt_image_t *cimg = dt_image_cache_read_get(darktable.image_cache, imgid); const int orientation = dt_image_orientation(cimg); // the orientation for this camera is not read correctly from exiv2, so we need // to go the full libraw path (as the thumbnail will be flipped the wrong way round) const int incompatible = !strncmp(cimg->exif_maker, "Phase One", 9); dt_image_cache_read_release(darktable.image_cache, cimg); // first try exif thumbnail, that's smaller and thus faster to load: if(!altered && !dt_conf_get_bool("never_use_embedded_thumb") && !dt_exif_thumbnail(filename, buf, wd, ht, orientation, width, height)) { res = 0; } else if(!altered && !dt_conf_get_bool("never_use_embedded_thumb") && !incompatible) { // try to load the embedded thumbnail in raw int ret; memset(filename, 0, DT_MAX_PATH_LEN); dt_image_full_path(imgid, filename, DT_MAX_PATH_LEN); const char *c = filename + strlen(filename); while(*c != '.' && c > filename) c--; if(!strcasecmp(c, ".jpg")) { // try to load jpg dt_imageio_jpeg_t jpg; if(!dt_imageio_jpeg_read_header(filename, &jpg)) { uint8_t *tmp = (uint8_t *)malloc(sizeof(uint8_t)*jpg.width*jpg.height*4); if(!dt_imageio_jpeg_read(&jpg, tmp)) { // scale to fit dt_iop_flip_and_zoom_8(tmp, jpg.width, jpg.height, buf, wd, ht, orientation, width, height); res = 0; } free(tmp); } } else { // raw image thumbnail libraw_data_t *raw = libraw_init(0); libraw_processed_image_t *image = NULL; ret = libraw_open_file(raw, filename); if(ret) goto libraw_fail; ret = libraw_unpack_thumb(raw); if(ret) goto libraw_fail; ret = libraw_adjust_sizes_info_only(raw); if(ret) goto libraw_fail; image = libraw_dcraw_make_mem_thumb(raw, &ret); if(!image || ret) goto libraw_fail; const int orientation = raw->sizes.flip; if(image->type == LIBRAW_IMAGE_JPEG) { // JPEG: decode (directly rescaled to mip4) dt_imageio_jpeg_t jpg; if(dt_imageio_jpeg_decompress_header(image->data, image->data_size, &jpg)) goto libraw_fail; uint8_t *tmp = (uint8_t *)malloc(sizeof(uint8_t)*jpg.width*jpg.height*4); if(dt_imageio_jpeg_decompress(&jpg, tmp)) { free(tmp); goto libraw_fail; } // scale to fit dt_iop_flip_and_zoom_8(tmp, jpg.width, jpg.height, buf, wd, ht, orientation, width, height); free(tmp); res = 0; } // clean up raw stuff. libraw_recycle(raw); libraw_close(raw); free(image); if(0) { libraw_fail: // fprintf(stderr,"[imageio] %s: %s\n", filename, libraw_strerror(ret)); libraw_close(raw); res = 1; } } } if(res) { // try the real thing: rawspeed + pixelpipe dt_imageio_module_format_t format; _dummy_data_t dat; format.bpp = _bpp; format.write_image = _write_image; format.levels = _levels; dat.head.max_width = wd; dat.head.max_height = ht; dat.buf = buf; // export with flags: ignore exif (don't load from disk), don't swap byte order, don't do hq processing, and signal we want thumbnail export res = dt_imageio_export_with_flags(imgid, "unused", &format, (dt_imageio_module_data_t *)&dat, 1, 1, 0, 1, NULL); if(!res) { // might be smaller, or have a different aspect than what we got as input. *width = dat.head.width; *height = dat.head.height; } } // fprintf(stderr, "[mipmap init 8] export image %u finished (sizes %d %d => %d %d)!\n", imgid, wd, ht, dat.head.width, dat.head.height); // any errors? if(res) { // fprintf(stderr, "[mipmap_cache] could not process thumbnail!\n"); *width = *height = 0; return; } // TODO: various speed optimizations: // TODO: also init all smaller mips! // TODO: use mipf, but: // TODO: if output is cropped, don't use mipf! }
// callback for the cache backend to initialize payload pointers void dt_mipmap_cache_allocate_dynamic(void *data, dt_cache_entry_t *entry) { dt_mipmap_cache_t *cache = (dt_mipmap_cache_t *)data; // for full image buffers struct dt_mipmap_buffer_dsc *dsc = entry->data; const dt_mipmap_size_t mip = get_size(entry->key); // alloc mere minimum for the header + broken image buffer: if(!dsc) { if(mip <= DT_MIPMAP_F) { // these are fixed-size: entry->data = dt_alloc_align(16, cache->buffer_size[mip]); } else { entry->data = dt_alloc_align(16, sizeof(*dsc) + sizeof(float) * 4 * 64); } // fprintf(stderr, "[mipmap cache] alloc dynamic for key %u %p\n", key, *buf); if(!(entry->data)) { fprintf(stderr, "[mipmap cache] memory allocation failed!\n"); exit(1); } dsc = entry->data; if(mip <= DT_MIPMAP_F) { dsc->width = cache->max_width[mip]; dsc->height = cache->max_height[mip]; dsc->size = cache->buffer_size[mip]; dsc->color_space = DT_COLORSPACE_NONE; } else { dsc->width = 0; dsc->height = 0; dsc->color_space = DT_COLORSPACE_NONE; dsc->size = sizeof(*dsc) + sizeof(float) * 4 * 64; } } assert(dsc->size >= sizeof(*dsc)); int loaded_from_disk = 0; if(mip < DT_MIPMAP_F) { if(cache->cachedir[0] && dt_conf_get_bool("cache_disk_backend")) { // try and load from disk, if successful set flag char filename[PATH_MAX] = {0}; snprintf(filename, sizeof(filename), "%s.d/%d/%d.jpg", cache->cachedir, mip, get_imgid(entry->key)); FILE *f = fopen(filename, "rb"); if(f) { long len = 0; uint8_t *blob = 0; fseek(f, 0, SEEK_END); len = ftell(f); if(len <= 0) goto read_error; // coverity madness blob = (uint8_t *)malloc(len); if(!blob) goto read_error; fseek(f, 0, SEEK_SET); int rd = fread(blob, sizeof(uint8_t), len, f); if(rd != len) goto read_error; dt_colorspaces_color_profile_type_t color_space; dt_imageio_jpeg_t jpg; if(dt_imageio_jpeg_decompress_header(blob, len, &jpg) || (jpg.width > cache->max_width[mip] || jpg.height > cache->max_height[mip]) || ((color_space = dt_imageio_jpeg_read_color_space(&jpg)) == DT_COLORSPACE_NONE) // pointless test to keep it in the if clause || dt_imageio_jpeg_decompress(&jpg, entry->data + sizeof(*dsc))) { fprintf(stderr, "[mipmap_cache] failed to decompress thumbnail for image %d from `%s'!\n", get_imgid(entry->key), filename); goto read_error; } dsc->width = jpg.width; dsc->height = jpg.height; dsc->color_space = color_space; loaded_from_disk = 1; if(0) { read_error: g_unlink(filename); } free(blob); fclose(f); } } } if(!loaded_from_disk) dsc->flags = DT_MIPMAP_BUFFER_DSC_FLAG_GENERATE; else dsc->flags = 0; // cost is just flat one for the buffer, as the buffers might have different sizes, // to make sure quota is meaningful. if(mip >= DT_MIPMAP_F) entry->cost = 1; else entry->cost = cache->buffer_size[mip]; }