Save thumbnails lossily, with metadata
This commit is contained in:
parent
aaa7cb93c3
commit
2d86ffed34
150
fiv-io.c
150
fiv-io.c
|
@ -2440,8 +2440,9 @@ fiv_io_open_from_data(const char *data, size_t len, const gchar *path,
|
||||||
|
|
||||||
// --- Export ------------------------------------------------------------------
|
// --- Export ------------------------------------------------------------------
|
||||||
|
|
||||||
static WebPData
|
unsigned char *
|
||||||
encode_lossless_webp(cairo_surface_t *surface)
|
fiv_io_encode_webp(
|
||||||
|
cairo_surface_t *surface, const WebPConfig *config, size_t *len)
|
||||||
{
|
{
|
||||||
cairo_format_t format = cairo_image_surface_get_format(surface);
|
cairo_format_t format = cairo_image_surface_get_format(surface);
|
||||||
int w = cairo_image_surface_get_width(surface);
|
int w = cairo_image_surface_get_width(surface);
|
||||||
|
@ -2460,15 +2461,10 @@ encode_lossless_webp(cairo_surface_t *surface)
|
||||||
surface = cairo_surface_reference(surface);
|
surface = cairo_surface_reference(surface);
|
||||||
}
|
}
|
||||||
|
|
||||||
WebPConfig config = {};
|
WebPMemoryWriter writer = {};
|
||||||
|
WebPMemoryWriterInit(&writer);
|
||||||
WebPPicture picture = {};
|
WebPPicture picture = {};
|
||||||
if (!WebPConfigInit(&config) ||
|
if (!WebPPictureInit(&picture))
|
||||||
!WebPConfigLosslessPreset(&config, 6) ||
|
|
||||||
!WebPPictureInit(&picture))
|
|
||||||
goto fail;
|
|
||||||
|
|
||||||
config.thread_level = true;
|
|
||||||
if (!WebPValidateConfig(&config))
|
|
||||||
goto fail;
|
goto fail;
|
||||||
|
|
||||||
picture.use_argb = true;
|
picture.use_argb = true;
|
||||||
|
@ -2495,18 +2491,33 @@ encode_lossless_webp(cairo_surface_t *surface)
|
||||||
for (int i = h * picture.argb_stride; i-- > 0; argb++)
|
for (int i = h * picture.argb_stride; i-- > 0; argb++)
|
||||||
*argb |= 0xFF000000;
|
*argb |= 0xFF000000;
|
||||||
|
|
||||||
WebPMemoryWriter writer = {};
|
|
||||||
WebPMemoryWriterInit(&writer);
|
|
||||||
picture.writer = WebPMemoryWrite;
|
picture.writer = WebPMemoryWrite;
|
||||||
picture.custom_ptr = &writer;
|
picture.custom_ptr = &writer;
|
||||||
if (!WebPEncode(&config, &picture))
|
if (!WebPEncode(config, &picture))
|
||||||
g_debug("WebPEncode: %d\n", picture.error_code);
|
g_debug("WebPEncode: %d\n", picture.error_code);
|
||||||
|
|
||||||
fail_compatibility:
|
fail_compatibility:
|
||||||
WebPPictureFree(&picture);
|
WebPPictureFree(&picture);
|
||||||
fail:
|
fail:
|
||||||
cairo_surface_destroy(surface);
|
cairo_surface_destroy(surface);
|
||||||
return (WebPData) {.bytes = writer.mem, .size = writer.size};
|
*len = writer.size;
|
||||||
|
return writer.mem;
|
||||||
|
}
|
||||||
|
|
||||||
|
static WebPData
|
||||||
|
encode_lossless_webp(cairo_surface_t *surface)
|
||||||
|
{
|
||||||
|
WebPData bitstream = {};
|
||||||
|
WebPConfig config = {};
|
||||||
|
if (!WebPConfigInit(&config) || !WebPConfigLosslessPreset(&config, 6))
|
||||||
|
return bitstream;
|
||||||
|
|
||||||
|
config.thread_level = true;
|
||||||
|
if (!WebPValidateConfig(&config))
|
||||||
|
return bitstream;
|
||||||
|
|
||||||
|
bitstream.bytes = fiv_io_encode_webp(surface, &config, &bitstream.size);
|
||||||
|
return bitstream;
|
||||||
}
|
}
|
||||||
|
|
||||||
static gboolean
|
static gboolean
|
||||||
|
@ -2825,6 +2836,68 @@ rescale_thumbnail(cairo_surface_t *thumbnail, double row_height)
|
||||||
return scaled;
|
return scaled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static WebPData
|
||||||
|
encode_thumbnail(cairo_surface_t *surface)
|
||||||
|
{
|
||||||
|
WebPData bitstream = {};
|
||||||
|
WebPConfig config = {};
|
||||||
|
if (!WebPConfigInit(&config) || !WebPConfigLosslessPreset(&config, 6))
|
||||||
|
return bitstream;
|
||||||
|
|
||||||
|
config.near_lossless = 95;
|
||||||
|
config.thread_level = true;
|
||||||
|
if (!WebPValidateConfig(&config))
|
||||||
|
return bitstream;
|
||||||
|
|
||||||
|
bitstream.bytes = fiv_io_encode_webp(surface, &config, &bitstream.size);
|
||||||
|
return bitstream;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
save_thumbnail(cairo_surface_t *thumbnail, const char *path, GString *thum)
|
||||||
|
{
|
||||||
|
WebPMux *mux = WebPMuxNew();
|
||||||
|
WebPData bitstream = encode_thumbnail(thumbnail);
|
||||||
|
gboolean ok = WebPMuxSetImage(mux, &bitstream, true) == WEBP_MUX_OK;
|
||||||
|
WebPDataClear(&bitstream);
|
||||||
|
|
||||||
|
WebPData data = {.bytes = (const uint8_t *) thum->str, .size = thum->len};
|
||||||
|
ok = ok && WebPMuxSetChunk(mux, "THUM", &data, false) == WEBP_MUX_OK;
|
||||||
|
|
||||||
|
WebPData assembled = {};
|
||||||
|
WebPDataInit(&assembled);
|
||||||
|
ok = ok && WebPMuxAssemble(mux, &assembled) == WEBP_MUX_OK;
|
||||||
|
WebPMuxDelete(mux);
|
||||||
|
if (!ok) {
|
||||||
|
g_warning("thumbnail encoding failed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GError *e = NULL;
|
||||||
|
while (!g_file_set_contents(
|
||||||
|
path, (const gchar *) assembled.bytes, assembled.size, &e)) {
|
||||||
|
bool missing_parents =
|
||||||
|
e->domain == G_FILE_ERROR && e->code == G_FILE_ERROR_NOENT;
|
||||||
|
g_debug("%s: %s", path, e->message);
|
||||||
|
g_clear_error(&e);
|
||||||
|
if (!missing_parents)
|
||||||
|
break;
|
||||||
|
|
||||||
|
gchar *dirname = g_path_get_dirname(path);
|
||||||
|
int err = g_mkdir_with_parents(dirname, 0755);
|
||||||
|
if (err)
|
||||||
|
g_debug("%s: %s", dirname, g_strerror(errno));
|
||||||
|
|
||||||
|
g_free(dirname);
|
||||||
|
if (err)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// It would be possible to create square thumbnails as well,
|
||||||
|
// but it seems like wasted effort.
|
||||||
|
WebPDataClear(&assembled);
|
||||||
|
}
|
||||||
|
|
||||||
gboolean
|
gboolean
|
||||||
fiv_io_produce_thumbnail(GFile *target, FivIoThumbnailSize size, GError **error)
|
fiv_io_produce_thumbnail(GFile *target, FivIoThumbnailSize size, GError **error)
|
||||||
{
|
{
|
||||||
|
@ -2851,9 +2924,9 @@ fiv_io_produce_thumbnail(GFile *target, FivIoThumbnailSize size, GError **error)
|
||||||
|
|
||||||
// TODO(p): Add a flag to avoid loading all pages and frames.
|
// TODO(p): Add a flag to avoid loading all pages and frames.
|
||||||
FivIoProfile sRGB = fiv_io_profile_new_sRGB();
|
FivIoProfile sRGB = fiv_io_profile_new_sRGB();
|
||||||
cairo_surface_t *surface =
|
gsize filesize = g_mapped_file_get_length(mf);
|
||||||
fiv_io_open_from_data(g_mapped_file_get_contents(mf),
|
cairo_surface_t *surface = fiv_io_open_from_data(
|
||||||
g_mapped_file_get_length(mf), path, sRGB, FALSE, error);
|
g_mapped_file_get_contents(mf), filesize, path, sRGB, FALSE, error);
|
||||||
|
|
||||||
g_free(path);
|
g_free(path);
|
||||||
g_mapped_file_unref(mf);
|
g_mapped_file_unref(mf);
|
||||||
|
@ -2867,37 +2940,36 @@ fiv_io_produce_thumbnail(GFile *target, FivIoThumbnailSize size, GError **error)
|
||||||
gchar *sum = g_compute_checksum_for_string(G_CHECKSUM_MD5, uri, -1);
|
gchar *sum = g_compute_checksum_for_string(G_CHECKSUM_MD5, uri, -1);
|
||||||
gchar *thumbnails_dir = fiv_io_get_thumbnail_root();
|
gchar *thumbnails_dir = fiv_io_get_thumbnail_root();
|
||||||
|
|
||||||
|
GString *thum = g_string_new("");
|
||||||
|
g_string_append_printf(
|
||||||
|
thum, "%s%c%s%c", "Thumb::URI", 0, uri, 0);
|
||||||
|
g_string_append_printf(
|
||||||
|
thum, "%s%c%ld%c", "Thumb::Mtime", 0, (long) st.st_mtim.tv_sec, 0);
|
||||||
|
g_string_append_printf(
|
||||||
|
thum, "%s%c%ld%c", "Thumb::Size", 0, (long) filesize, 0);
|
||||||
|
g_string_append_printf(thum, "%s%c%d%c", "Thumb::Image::Width", 0,
|
||||||
|
cairo_image_surface_get_width(surface), 0);
|
||||||
|
g_string_append_printf(thum, "%s%c%d%c", "Thumb::Image::Height", 0,
|
||||||
|
cairo_image_surface_get_height(surface), 0);
|
||||||
|
|
||||||
|
// Without a CMM, no conversion is attempted.
|
||||||
|
if (sRGB) {
|
||||||
|
g_string_append_printf(
|
||||||
|
thum, "%s%c%s%c", "Thumb::ColorSpace", 0, "sRGB", 0);
|
||||||
|
}
|
||||||
|
|
||||||
for (int use = size; use >= FIV_IO_THUMBNAIL_SIZE_MIN; use--) {
|
for (int use = size; use >= FIV_IO_THUMBNAIL_SIZE_MIN; use--) {
|
||||||
cairo_surface_t *scaled =
|
cairo_surface_t *scaled =
|
||||||
rescale_thumbnail(surface, fiv_io_thumbnail_sizes[use].size);
|
rescale_thumbnail(surface, fiv_io_thumbnail_sizes[use].size);
|
||||||
gchar *path = g_strdup_printf("%s/wide-%s/%s.webp", thumbnails_dir,
|
gchar *path = g_strdup_printf("%s/wide-%s/%s.webp", thumbnails_dir,
|
||||||
fiv_io_thumbnail_sizes[use].thumbnail_spec_name, sum);
|
fiv_io_thumbnail_sizes[use].thumbnail_spec_name, sum);
|
||||||
|
save_thumbnail(scaled, path, thum);
|
||||||
GError *e = NULL;
|
|
||||||
while (!fiv_io_save(scaled, scaled, NULL, path, &e)) {
|
|
||||||
bool missing_parents =
|
|
||||||
e->domain == G_FILE_ERROR && e->code == G_FILE_ERROR_NOENT;
|
|
||||||
g_debug("%s: %s", path, e->message);
|
|
||||||
g_clear_error(&e);
|
|
||||||
if (!missing_parents)
|
|
||||||
break;
|
|
||||||
|
|
||||||
gchar *dirname = g_path_get_dirname(path);
|
|
||||||
int err = g_mkdir_with_parents(dirname, 0755);
|
|
||||||
if (err)
|
|
||||||
g_debug("%s: %s", dirname, g_strerror(errno));
|
|
||||||
|
|
||||||
g_free(dirname);
|
|
||||||
if (err)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// It would be possible to create square thumbnails as well,
|
|
||||||
// but it seems like wasted effort.
|
|
||||||
cairo_surface_destroy(scaled);
|
cairo_surface_destroy(scaled);
|
||||||
g_free(path);
|
g_free(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
g_string_free(thum, TRUE);
|
||||||
|
|
||||||
g_free(thumbnails_dir);
|
g_free(thumbnails_dir);
|
||||||
g_free(sum);
|
g_free(sum);
|
||||||
g_free(uri);
|
g_free(uri);
|
||||||
|
|
7
fiv-io.h
7
fiv-io.h
|
@ -79,6 +79,13 @@ int fiv_io_filecmp(GFile *f1, GFile *f2);
|
||||||
|
|
||||||
// --- Export ------------------------------------------------------------------
|
// --- Export ------------------------------------------------------------------
|
||||||
|
|
||||||
|
typedef struct WebPConfig WebPConfig;
|
||||||
|
|
||||||
|
/// Encodes a Cairo surface as a WebP bitstream, following the configuration.
|
||||||
|
/// The result needs to be freed using WebPFree/WebPDataClear().
|
||||||
|
unsigned char *fiv_io_encode_webp(
|
||||||
|
cairo_surface_t *surface, const WebPConfig *config, size_t *len);
|
||||||
|
|
||||||
/// Saves the page as a lossless WebP still picture or animation.
|
/// Saves the page as a lossless WebP still picture or animation.
|
||||||
/// If no exact frame is specified, this potentially creates an animation.
|
/// If no exact frame is specified, this potentially creates an animation.
|
||||||
gboolean fiv_io_save(cairo_surface_t *page, cairo_surface_t *frame,
|
gboolean fiv_io_save(cairo_surface_t *page, cairo_surface_t *frame,
|
||||||
|
|
Loading…
Reference in New Issue