summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorTomas Bzatek <tbzatek@users.sourceforge.net>2016-10-02 16:58:27 +0200
committerTomas Bzatek <tbzatek@users.sourceforge.net>2016-10-02 17:32:43 +0200
commitddd9556689af055355a07cf2766fe95eaed4e38e (patch)
treeb6de3e57a5cfbd0d855277c3b8c93f16549f0bf2 /src
parent56ff7bc45505b3e39b2f9be70e7bee3f80ec4f70 (diff)
downloadcataract-ddd9556689af055355a07cf2766fe95eaed4e38e.tar.xz
Add support for HiDPI images
This works by creating corresponding hidpi image sizes on startup and letting the machinery generate high resolution images from the source images (no way to use supplied images). However since browsers expect exact image dimension multiples for the particular scale factor, a reference image size (scale factor 1.0x) must be read first, then cropped to match reference aspect ratio and resized to exact dimensions. That way pixel-perfect results can be achieved for the chosen scale factor. TODO: the CSS background-image: image-set() tags are not supported on Firefox. TODO: try the 1.5x scale factor
Diffstat (limited to 'src')
-rw-r--r--src/generators.c194
-rw-r--r--src/jpeg-utils.cpp28
-rw-r--r--src/jpeg-utils.h1
-rw-r--r--src/setup.c70
-rw-r--r--src/setup.h14
5 files changed, 236 insertions, 71 deletions
diff --git a/src/generators.c b/src/generators.c
index 72c51fb..d6984be 100644
--- a/src/generators.c
+++ b/src/generators.c
@@ -21,6 +21,7 @@
#include <errno.h>
#include <sys/stat.h>
#include <unistd.h>
+#include <math.h>
#include <glib.h>
#include <glib/gstdio.h>
@@ -207,7 +208,7 @@ get_image_source_path (TGallerySetup *setup,
if (image_size->is_thumbnail && items->type == GALLERY_TYPE_INDEX)
s = item->thumbnail;
- if (s == NULL && item->image_sizes != NULL)
+ if (s == NULL && ! image_size->is_hidpi && item->image_sizes != NULL)
s = g_hash_table_lookup (item->image_sizes, image_size->name);
}
@@ -239,6 +240,8 @@ get_image_dest_path (TGallerySetup *setup,
g_assert (image_size != NULL);
src = get_image_source_path (setup, items, item, path_info, image_size);
+ if (src == NULL)
+ return NULL;
target_image_dir = g_strdup_printf ("%s%s", TARGET_IMAGE_DIR_PREFIX, image_size->name);
s = g_path_get_basename (src);
@@ -275,7 +278,7 @@ get_image_dest_path_with_fallback (TGallerySetup *setup,
tmp_image_size = image_size;
while ((dst = get_image_dest_path (setup, items, item, path_info, tmp_image_size, FALSE)),
- tmp_image_size->fallback_size && g_access (dst, R_OK) != 0) {
+ tmp_image_size->fallback_size && dst != NULL && g_access (dst, R_OK) != 0) {
g_free (dst);
tmp_image_size = lookup_image_size_for_name (setup, tmp_image_size->fallback_size);
}
@@ -431,63 +434,87 @@ have_album_image_size_cb (gchar **args, gpointer user_data)
}
-/*
- * generate_image: generate needed image sizes
- */
-gboolean
-generate_image (TGallerySetup *setup,
- TAlbum *items,
- TIndexItem *item,
- TPathInfo *path_info,
- gboolean query_update)
+static gboolean
+generate_image_for_size (TGallerySetup *setup,
+ TAlbum *items,
+ TIndexItem *item,
+ TPathInfo *path_info,
+ TImageSize *image_size,
+ gboolean query_update)
{
gboolean res;
gchar *img_src;
gchar *img_dst;
+ gchar *img_ref_dst;
unsigned long img_w, img_h;
unsigned long src_img_w, src_img_h;
unsigned long tmpw, tmph;
+ unsigned long ref_img_w, ref_img_h;
int src_img_quality, img_quality;
- GList *l;
- TImageSize *image_size;
ExifData *exif_data;
-
res = ! query_update;
- for (l = g_list_first (setup->design->image_sizes); l; l = g_list_next (l)) {
- image_size = l->data;
- img_src = get_image_source_path (setup, items, item, path_info, image_size);
- if (img_src == NULL)
- continue;
- if (g_access (img_src, R_OK) != 0) {
- log_error (" Error opening image %s for size \"%s\": %s\n", img_src, image_size->name, g_strerror (errno));
- g_free (img_src);
- res = TRUE;
- continue;
- }
- img_dst = get_image_dest_path (setup, items, item, path_info, image_size, FALSE);
- if (img_dst == NULL) {
- g_free (img_src);
- continue;
- }
+ img_src = get_image_source_path (setup, items, item, path_info, image_size);
+ if (img_src == NULL)
+ return res;
+ if (g_access (img_src, R_OK) != 0) {
+ log_error (" Error opening image %s for size \"%s\": %s\n", img_src, image_size->name, g_strerror (errno));
+ g_free (img_src);
+ return query_update;
+ }
+ img_dst = get_image_dest_path (setup, items, item, path_info, image_size, FALSE);
+ if (img_dst == NULL) {
+ g_free (img_src);
+ return res;
+ }
- exif_data = exif_data_new_empty ();
- metadata_apply_overrides (exif_data, setup, path_info, items, item, image_size);
+ exif_data = exif_data_new_empty ();
+ metadata_apply_overrides (exif_data, setup, path_info, items, item, image_size);
- /* Do something when required */
- res = res || needs_update (img_src, img_dst);
- if (! query_update) {
- /* Always copy supplied image size */
- if (! image_size->is_thumbnail && item->image_sizes && g_hash_table_lookup (item->image_sizes, image_size->name)) {
- if (! copy_file (img_src, img_dst))
- log_error (" Error copying image %s to %s\n", img_src, img_dst);
- }
- /* Resize image */
- else {
- get_image_sizes (img_src, &src_img_w, &src_img_h, &src_img_quality, setup->autorotate);
- if (src_img_w > 0 && src_img_h > 0) {
- img_quality = image_size->quality;
+ /* Do something when required */
+ res = res || needs_update (img_src, img_dst);
+ if (! query_update) {
+ /* Always copy supplied image size */
+ if (! image_size->is_thumbnail && ! image_size->is_hidpi && item->image_sizes && g_hash_table_lookup (item->image_sizes, image_size->name)) {
+ if (! copy_file (img_src, img_dst))
+ log_error (" Error copying image %s to %s\n", img_src, img_dst);
+ }
+ /* Resize image */
+ else {
+ get_image_sizes (img_src, &src_img_w, &src_img_h, &src_img_quality, setup->autorotate);
+ if (src_img_w > 0 && src_img_h > 0) {
+ img_quality = image_size->quality;
+ if (image_size->is_hidpi) {
+ /* Get reference regular size image dimensions */
+ img_ref_dst = get_image_dest_path (setup, items, item, path_info, image_size->hidpi_ref_size, FALSE);
+ if (! img_ref_dst) {
+ g_free (img_src);
+ g_free (img_dst);
+ return res;
+ }
+ ref_img_w = ref_img_h = 0;
+ get_image_sizes (img_ref_dst, &ref_img_w, &ref_img_h, NULL, FALSE);
+ g_free (img_ref_dst);
+ if (ref_img_w <= 0 || ref_img_h <= 0) {
+ g_free (img_src);
+ g_free (img_dst);
+ return res;
+ }
+ /* Browsers need exactly n-factor of the original size */
+ img_w = lround ((gdouble) ref_img_w * image_size->hidpi_scale_factor);
+ img_h = lround ((gdouble) ref_img_h * image_size->hidpi_scale_factor);
+ if ((gdouble) img_w * (100 - setup->design->hidpi_upscale_threshold) / 100 > src_img_w || (gdouble) img_h * (100 - setup->design->hidpi_upscale_threshold) / 100 > src_img_h) {
+/* g_print (" Warning: source image %s (%lux%lu) is not large enough for the \"%s\" image size (need %lux%lu)\n", img_src, src_img_w, src_img_h, image_size->name, img_w, img_h); */
+ g_free (img_src);
+ g_free (img_dst);
+ return res;
+ }
+ if (setup->warn_resize && (img_w > src_img_w || img_h > src_img_h))
+ printf (" Warning: upscaling image %s from %lux%lu to %lux%lu\n", img_src, src_img_w, src_img_h, img_w, img_h);
+
+ } else {
+ /* Not a HiDPI image size */
if (image_size->is_thumbnail && image_size->thumb_crop_style != CROP_STYLE_NORMAL) {
switch (image_size->thumb_crop_style) {
case CROP_STYLE_SQUARED:
@@ -517,7 +544,7 @@ generate_image (TGallerySetup *setup,
/* printf (" Warning: source image %s (%lux%lu) is not large enough for the \"%s\" image size (need %lux%lu)\n", img_src, src_img_w, src_img_h, image_size->name, tmpw, tmph); */
g_free (img_src);
g_free (img_dst);
- continue;
+ return res;
}
/* Calculate dimensions */
if (src_img_w < tmpw + image_size->no_resize_threshold && src_img_h < tmph + image_size->no_resize_threshold) {
@@ -537,29 +564,66 @@ generate_image (TGallerySetup *setup,
img_quality = src_img_quality;
}
}
+ }
- /* Perform resize and strip */
- stats_images_inc ();
- if (src_img_w == img_w && src_img_h == img_h && src_img_quality == img_quality) {
- if (! copy_file (img_src, img_dst))
- log_error (" Error copying image %s to %s\n", img_src, img_dst);
- } else {
- if (! resize_image (img_src, img_dst, img_w, img_h, img_quality, image_size->is_thumbnail, setup->autorotate, exif_data,
- image_size->is_thumbnail ? setup->design->imgmagick_thumb_opts : setup->design->imgmagick_resize_opts))
- log_error (" Error resizing image %s\n", img_src);
- }
+
+ /* Perform resize and strip */
+ stats_images_inc ();
+ if (src_img_w == img_w && src_img_h == img_h && src_img_quality == img_quality) {
+ if (! copy_file (img_src, img_dst))
+ log_error (" Error copying image %s to %s\n", img_src, img_dst);
} else {
- log_error ("generate_image: image %s sizes are %lux%lu\n", img_src, src_img_w, src_img_h);
+ if (! resize_image (img_src, img_dst, img_w, img_h, img_quality, image_size->is_thumbnail, setup->autorotate, image_size->is_hidpi, exif_data,
+ image_size->is_thumbnail ? setup->design->imgmagick_thumb_opts : setup->design->imgmagick_resize_opts))
+ log_error (" Error resizing image %s\n", img_src);
+ }
+
+ if (setup->warn_resize && image_size->is_hidpi) {
+ get_image_sizes (img_dst, &tmpw, &tmph, NULL, FALSE);
+ if (img_w != tmpw || img_h != tmph)
+ g_print (" Warning: generated image '%s' doesn't have required dimensions: need %lux%lu, got %lux%lu\n", img_dst, img_w, img_h, tmpw, tmph);
}
+ } else {
+ log_error ("generate_image: image %s sizes are %lux%lu\n", img_src, src_img_w, src_img_h);
}
}
- if (! image_size->is_thumbnail) {
- modify_exif (img_dst, exif_data, setup->erase_exif_thumbnail, setup->strip_xmp);
- }
+ }
+ if (! image_size->is_thumbnail) {
+ modify_exif (img_dst, exif_data, setup->erase_exif_thumbnail, setup->strip_xmp);
+ }
- exif_data_free (exif_data);
- g_free (img_src);
- g_free (img_dst);
+ exif_data_free (exif_data);
+ g_free (img_src);
+ g_free (img_dst);
+
+ return res;
+}
+
+/*
+ * generate_image: generate needed image sizes
+ */
+gboolean
+generate_image (TGallerySetup *setup,
+ TAlbum *items,
+ TIndexItem *item,
+ TPathInfo *path_info,
+ gboolean query_update)
+{
+ gboolean res;
+ GList *l;
+ TImageSize *image_size;
+ int i;
+
+ res = ! query_update;
+ for (i = 0; i < 2; i++) {
+ /* process regular sizes first and then hidpi sizes in the second round */
+ for (l = g_list_first (setup->design->image_sizes); l; l = g_list_next (l)) {
+ image_size = l->data;
+ if ((i == 0 && image_size->is_hidpi) || (i == 1 && ! image_size->is_hidpi))
+ continue;
+
+ res = (query_update && res) || generate_image_for_size (setup, items, item, path_info, image_size, query_update);
+ }
}
return res;
}
@@ -872,9 +936,9 @@ process_img_item (TGallerySetup *setup,
struct HaveImageSizeData *img_size_data;
GList *l;
+ album_protected = FALSE;
if (list_mode) {
/* Index stuff */
- album_protected = FALSE;
if (items->type == GALLERY_TYPE_INDEX) {
album_objects_count = 0;
s1 = g_build_filename (path_info->src_dir, item->path, "index.xml", NULL);
@@ -934,7 +998,7 @@ process_img_item (TGallerySetup *setup,
tmp_image_size = l->data;
if (! all_image_sizes && tmp_image_size != image_size)
continue;
- if (tmp_image_size->is_thumbnail)
+ if ((items->type == GALLERY_TYPE_ALBUM && tmp_image_size->is_thumbnail) || (items->type == GALLERY_TYPE_INDEX && ! tmp_image_size->is_thumbnail) || album_protected)
continue;
/* First calculate image paths */
if (all_image_sizes) {
diff --git a/src/jpeg-utils.cpp b/src/jpeg-utils.cpp
index aaf59fc..d7134c2 100644
--- a/src/jpeg-utils.cpp
+++ b/src/jpeg-utils.cpp
@@ -507,6 +507,7 @@ resize_image (const gchar *src, const gchar *dst,
int quality,
gboolean thumbnail,
gboolean autorotate,
+ gboolean hidpi_strict_dimensions,
ExifData *exif,
gchar *resize_opts)
{
@@ -539,7 +540,7 @@ resize_image (const gchar *src, const gchar *dst,
autorotate_image (magick_wand);
/* Don't resize if smaller than desired size */
- if (MagickGetImageWidth (magick_wand) > size_x || MagickGetImageHeight (magick_wand) > size_y)
+ if (hidpi_strict_dimensions || MagickGetImageWidth (magick_wand) > size_x || MagickGetImageHeight (magick_wand) > size_y)
{
/* Prepare image before resizing */
if (thumbnail) {
@@ -587,8 +588,29 @@ resize_image (const gchar *src, const gchar *dst,
}
}
+ /* Shave the source image to match exact dimensions after resize */
+ if (hidpi_strict_dimensions && (! thumbnail || exif->thumbnail_crop_style == CROP_STYLE_NORMAL)) {
+ w = MagickGetImageWidth (magick_wand);
+ h = MagickGetImageHeight (magick_wand);
+ source_aspect = (double) w / (double) h;
+ target_aspect = (double) size_x / (double) size_y;
+ if (source_aspect != target_aspect) {
+ if (target_aspect >= source_aspect) {
+ new_w = w;
+ new_h = lround ((double) w / target_aspect);
+ } else {
+ new_w = lround ((double) h * target_aspect);
+ new_h = h;
+ }
+ MagickCropImage (magick_wand, new_w, new_h, (w - new_w) / 2, (h - new_h) / 2);
+ g_warn_if_fail (MagickGetImageWidth (magick_wand) == new_w);
+ g_warn_if_fail (MagickGetImageHeight (magick_wand) == new_h);
+ }
+ }
+
if (resize_opts == NULL) {
/* Perform internal resizing */
+ /* Note: MagickResizeImage() does no aspect correction, stretching the image to the required dimensions */
if (thumbnail) {
MagickThumbnailImage (magick_wand, size_x, size_y);
} else {
@@ -682,8 +704,8 @@ get_image_sizes (const gchar *img,
ExceptionType severity;
gchar *description;
- *width = -1;
- *height = -1;
+ *width = 0;
+ *height = 0;
if (quality)
*quality = -1;
diff --git a/src/jpeg-utils.h b/src/jpeg-utils.h
index e23dee7..d0664c9 100644
--- a/src/jpeg-utils.h
+++ b/src/jpeg-utils.h
@@ -100,6 +100,7 @@ gboolean resize_image (const gchar *src, const gchar *dst,
int quality,
gboolean thumbnail,
gboolean autorotate,
+ gboolean hidpi_strict_dimensions,
ExifData *exif,
gchar *resize_opts);
diff --git a/src/setup.c b/src/setup.c
index 9780807..cf23044 100644
--- a/src/setup.c
+++ b/src/setup.c
@@ -155,6 +155,35 @@ image_sizes_compare_func (TImageSize *a, TImageSize *b)
return sa - sb;
}
+static gdouble *
+parse_hidpi_sizes (const gchar *s)
+{
+ gchar **split;
+ GArray *arr;
+ gchar *s2;
+ int i;
+ gdouble f;
+
+ if (! s || strlen (s) == 0)
+ return NULL;
+
+ arr = g_array_new (TRUE, TRUE, sizeof (gdouble));
+ split = g_strsplit (s, ",", -1);
+
+ for (i = 0; split[i] != NULL; i++) {
+ s2 = g_strstrip (split[i]);
+ if (s2[strlen(s2) - 1] != 'x')
+ continue;
+ s2[strlen(s2) - 1] = '\0';
+ f = g_ascii_strtod (s2, NULL);
+ g_array_append_val (arr, f);
+ }
+ g_strfreev (split);
+
+ return (gdouble *) g_array_free (arr, FALSE);
+}
+
+
/*
* parse_design_setup_xml: XML parser for design.xml file
*/
@@ -166,7 +195,7 @@ parse_design_setup_xml (const gchar *filename)
gchar *s2;
gchar *s3;
TGalleryDesign *design;
- TImageSize *image_size;
+ TImageSize *image_size, *hidpi_image_size;
TGalleryDesignTheme *theme;
int count, c;
int i, j;
@@ -202,6 +231,17 @@ parse_design_setup_xml (const gchar *filename)
}
+ /* hidpi section */
+ if (xml_file_get_node_attribute_boolean_with_default (xml, "/design_setup/hidpi", "enabled", FALSE)) {
+ design->hidpi_upscale_threshold = xml_file_get_node_attribute_long_with_default (xml, "/design_setup/hidpi/threshold", "upscale", DEFAULT_UPSCALE_THRESHOLD);
+ s = xml_file_get_node_value (xml, "/design_setup/hidpi/sizes/text()");
+ design->hidpi_sizes = parse_hidpi_sizes (s);
+ g_free (s);
+ design->hidpi_quality = xml_file_get_node_attribute_long_with_default (xml, "/design_setup/hidpi/quality", "value", -1);
+ design->hidpi_thumbnail_quality = xml_file_get_node_attribute_long_with_default (xml, "/design_setup/hidpi/quality", "thumbnail", -1);
+ }
+
+
/* image_sizes section */
count = xml_file_node_get_children_count (xml, "/design_setup/image_sizes/size");
for (i = 0; i < count; i++) {
@@ -268,6 +308,33 @@ parse_design_setup_xml (const gchar *filename)
image_size->quality_threshold = xml_file_get_node_attribute_long_with_default (xml, s, "quality", DEFAULT_QUALITY_THRESHOLD);
g_free (s);
+ /* Generate HiDPI sizes */
+ if (design->hidpi_sizes)
+ for (j = 0; design->hidpi_sizes[j] != 0; j++ ) {
+ hidpi_image_size = g_malloc0 (sizeof (TImageSize));
+ memcpy (hidpi_image_size, image_size, sizeof (TImageSize));
+ hidpi_image_size->is_hidpi = TRUE;
+ hidpi_image_size->hidpi_scale_factor = design->hidpi_sizes[j];
+ hidpi_image_size->hidpi_ref_size = image_size;
+ hidpi_image_size->tagname = NULL;
+ hidpi_image_size->fallback_size = NULL;
+ hidpi_image_size->quality_threshold = 0;
+ hidpi_image_size->no_resize_threshold = 0;
+ hidpi_image_size->availability_threshold = 0;
+ hidpi_image_size->name = g_strdup_printf (HIDPI_NAME_FORMAT, image_size->name, hidpi_image_size->hidpi_scale_factor);
+ hidpi_image_size->landscape_width = lround ((gdouble) image_size->landscape_width * hidpi_image_size->hidpi_scale_factor);
+ hidpi_image_size->landscape_height = lround ((gdouble) image_size->landscape_height * hidpi_image_size->hidpi_scale_factor);
+ hidpi_image_size->portrait_width = lround ((gdouble) image_size->portrait_width * hidpi_image_size->hidpi_scale_factor);
+ hidpi_image_size->portrait_height = lround ((gdouble) image_size->portrait_height * hidpi_image_size->hidpi_scale_factor);
+ hidpi_image_size->square_size = lround ((gdouble) image_size->square_size * hidpi_image_size->hidpi_scale_factor);
+ hidpi_image_size->crop_width = lround ((gdouble) image_size->crop_width * hidpi_image_size->hidpi_scale_factor);
+ hidpi_image_size->crop_height = lround ((gdouble) image_size->crop_height * hidpi_image_size->hidpi_scale_factor);
+ if (! hidpi_image_size->is_thumbnail && design->hidpi_quality > 0)
+ hidpi_image_size->quality = design->hidpi_quality;
+ if (hidpi_image_size->is_thumbnail && design->hidpi_thumbnail_quality > 0)
+ hidpi_image_size->quality = design->hidpi_thumbnail_quality;
+ design->image_sizes = g_list_append (design->image_sizes, hidpi_image_size);
+ }
}
design->image_sizes = g_list_sort (design->image_sizes, (GCompareFunc) image_sizes_compare_func);
@@ -676,6 +743,7 @@ free_design_setup_data (TGalleryDesign *design)
g_strfreev (design->supplemental_files);
g_free (design->imgmagick_resize_opts);
g_free (design->imgmagick_thumb_opts);
+ g_free (design->hidpi_sizes);
g_free (design);
}
}
diff --git a/src/setup.h b/src/setup.h
index c11efed..730c035 100644
--- a/src/setup.h
+++ b/src/setup.h
@@ -25,11 +25,13 @@ G_BEGIN_DECLS
#define DEFAULT_INDEX_FILENAME "index.html"
#define THUMBNAIL_NAME_FORMAT "%.3d_%s"
+#define HIDPI_NAME_FORMAT "%s_hidpi_%gx"
#define TARGET_IMAGE_DIR_PREFIX "_"
#define DEFAULT_NO_RESIZE_THRESHOLD 1
#define DEFAULT_AVAILABILITY_THRESHOLD 1
#define DEFAULT_QUALITY_THRESHOLD 5
+#define DEFAULT_UPSCALE_THRESHOLD 33
#define SETUP_XML "setup.xml"
#define SETUP_V2_XML "setup2.xml"
@@ -38,6 +40,7 @@ G_BEGIN_DECLS
/* forward declaration */
typedef struct TGalleryDesign TGalleryDesign;
+typedef struct TImageSize TImageSize;
/* Global gallery setup */
typedef struct {
@@ -96,7 +99,7 @@ typedef enum {
CROP_STYLE_FIXED
} TCropStyle;
-typedef struct {
+struct TImageSize {
gchar *name;
gchar *tagname;
gboolean is_thumbnail;
@@ -112,8 +115,11 @@ typedef struct {
int no_resize_threshold;
int availability_threshold;
int quality_threshold;
+ gboolean is_hidpi;
+ gdouble hidpi_scale_factor;
+ TImageSize *hidpi_ref_size;
TCropStyle thumb_crop_style;
-} TImageSize;
+};
typedef struct {
gboolean enabled;
@@ -139,6 +145,10 @@ struct TGalleryDesign {
gchar **supplemental_files;
gchar *imgmagick_resize_opts;
gchar *imgmagick_thumb_opts;
+ int hidpi_upscale_threshold;
+ gdouble *hidpi_sizes;
+ int hidpi_quality;
+ int hidpi_thumbnail_quality;
};