/* Cataract - Static web photo gallery generator * Copyright (C) 2008 Tomas Bzatek * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include "jpeg-utils.h" #include "gallery-utils.h" struct ExifData { Exiv2::Image::AutoPtr image; }; /* * EXIF and IPTC info retrieval, keeps the source file open until freed */ ExifData * read_exif (const gchar *filename) { ExifData *data; data = (ExifData*) g_malloc0 (sizeof (ExifData)); try { data->image = Exiv2::ImageFactory::open (filename); g_assert (data->image.get() != 0); data->image->readMetadata(); } catch (Exiv2::AnyError& e) { log_error ("read_exif: Caught Exiv2 exception: '%s'\n", e.what()); exif_data_free (data); return NULL; } return data; } void exif_data_free (ExifData *data) { if (data) { /* FIXME: free data->image */ g_free (data); } } static const gchar * get_real_key_name (const gchar *key) { struct StrKeyPair { const gchar *from; const gchar *to; }; static const struct StrKeyPair conv[] = { { EXIF_CANON_CAMERA_TEMP, "Exif.CanonSi.0x000c" }, }; guint i; for (i = 0; i < G_N_ELEMENTS (conv); i++) if (g_str_equal (conv[i].from, key)) return conv[i].to; return key; } /* * Retrieves value of the specified key or NULL if the key does not exist. * The key argument belongs to Exiv2 namespace - see http://exiv2.org/tags.html */ gchar * get_exif_data (ExifData *exif, const gchar *key) { g_return_val_if_fail (exif != NULL, NULL); g_return_val_if_fail (key != NULL, NULL); key = get_real_key_name (key); try { if (g_strcmp0 (key, JPEG_COMMENT) == 0) { return g_strdup (exif->image->comment().c_str()); } if (g_str_has_prefix (key, "Exif.")) { Exiv2::ExifData &exifData = exif->image->exifData(); if (! exifData.empty()) { return g_strdup (exifData[key].toString().c_str()); } } if (g_str_has_prefix (key, "Iptc.")) { Exiv2::IptcData &iptcData = exif->image->iptcData(); if (! iptcData.empty()) { return g_strdup (iptcData[key].toString().c_str()); } } return NULL; } catch (...) { return NULL; } } gchar * get_exif_data_fixed (ExifData *exif, const gchar *key) { g_return_val_if_fail (exif != NULL, NULL); g_return_val_if_fail (key != NULL, NULL); try { if (g_str_has_prefix (key, "Exif.")) { Exiv2::ExifData &exifData = exif->image->exifData(); if (exifData.empty()) return NULL; if (g_str_equal (key, EXIF_APERTURE)) { float val = exifData["Exif.Photo.FNumber"].toFloat(); if (val >= 0) return g_strdup_printf ("f/%.1f", val); } if (g_str_equal (key, EXIF_DATETIME)) { const char *val = NULL; try { val = exifData["Exif.Photo.DateTimeOriginal"].toString().c_str(); } catch (...) { } if (! val || strlen (val) == 0) try { val = exifData["Exif.Image.DateTime"].toString().c_str(); } catch (...) { } if (val && strlen (val) > 0) { struct tm tt; char conv[1024]; memset (&conv, 0, sizeof (conv)); memset (&tt, 0, sizeof (struct tm)); if (sscanf (val, "%d:%d:%d %d:%d:%d", &tt.tm_year, &tt.tm_mon, &tt.tm_mday, &tt.tm_hour, &tt.tm_min, &tt.tm_sec) == 6) { tt.tm_year -= 1900; tt.tm_mon--; mktime (&tt); if (strftime (&conv[0], sizeof (conv), "%c", &tt)) return g_strdup (&conv[0]); } } } if (g_str_equal (key, EXIF_EXPOSURE)) { float val = exifData["Exif.Photo.ExposureTime"].toFloat(); if (val > 0) { if (val < 0.5) return g_strdup_printf ("1/%.0f s", 1/val); else return g_strdup_printf ("%.1f s", val); } } if (g_str_equal (key, EXIF_FLASH)) { long int val = exifData["Exif.Photo.Flash"].toLong(); if (val > 0 && (val & 1) == 1) return g_strdup ("Flash fired"); else return g_strdup ("--"); } if (g_str_equal (key, EXIF_FOCAL_LENGTH)) { float val = exifData["Exif.Photo.FocalLength"].toFloat(); if (val >= 0) return g_strdup_printf ("%.0f mm", val); } if (g_str_equal (key, EXIF_ISO)) { long int val = exifData["Exif.Photo.ISOSpeedRatings"].toLong(); if (val > 0) return g_strdup_printf ("%ld", val); } if (g_str_equal (key, EXIF_CANON_CAMERA_TEMP)) { if (exifData["Exif.CanonSi.0x000c"].count() > 0) { int long val = exifData["Exif.CanonSi.0x000c"].toLong(); if (val > 0) return g_strdup_printf ("%ld °C", val - 128); } } } if (g_str_has_prefix (key, "Iptc.")) { Exiv2::IptcData &iptcData = exif->image->iptcData(); if (iptcData.empty()) return NULL; } } catch (...) { return NULL; } return get_exif_data (exif, key); } /* * Returns TRUE if the image contains the key specified */ gboolean exif_has_key (ExifData *exif, const gchar *key) { g_return_val_if_fail (exif != NULL, FALSE); g_return_val_if_fail (key != NULL, FALSE); key = get_real_key_name (key); try { if (g_strcmp0 (key, JPEG_COMMENT) == 0) { return (! exif->image->comment().empty()); } if (g_str_has_prefix (key, "Exif.")) { Exiv2::ExifData &exifData = exif->image->exifData(); return !exifData.empty() && exifData[key].count() > 0; } if (g_str_has_prefix (key, "Iptc.")) { Exiv2::IptcData &iptcData = exif->image->iptcData(); return !iptcData.empty() && iptcData[key].count() > 0; } return FALSE; } catch (...) { return FALSE; } } static void autorotate_image (MagickWand *magick_wand) { MagickBooleanType b; PixelWand *pixel_wand; ExceptionType severity; gchar *description; pixel_wand = NewPixelWand (); b = PixelSetColor (pixel_wand, "#000000"); if (b == MagickFalse) { description = MagickGetException (magick_wand, &severity); log_error ("autorotate_image: Error creating pixel wand: %s %s %ld %s\n", GetMagickModule(), description); MagickRelinquishMemory (description); } b = MagickTrue; switch (MagickGetImageOrientation (magick_wand)) { case TopRightOrientation: b = MagickFlopImage (magick_wand); break; case BottomRightOrientation: b = MagickRotateImage (magick_wand, pixel_wand, 180.0); break; case BottomLeftOrientation: b = MagickFlipImage (magick_wand); break; case LeftTopOrientation: b = MagickTransposeImage (magick_wand); break; case RightTopOrientation: b = MagickRotateImage (magick_wand, pixel_wand, 90.0); break; case RightBottomOrientation: b = MagickTransverseImage (magick_wand); break; case LeftBottomOrientation: b = MagickRotateImage (magick_wand, pixel_wand, 270.0); break; default: break; } if (b == MagickFalse) { description = MagickGetException (magick_wand, &severity); log_error ("autorotate_image: Error rotating image: %s %s %ld %s\n", GetMagickModule(), description); MagickRelinquishMemory (description); } b = MagickSetImageOrientation (magick_wand, TopLeftOrientation); if (b == MagickFalse) { description = MagickGetException (magick_wand, &severity); log_error ("autorotate_image: Error saving orientation: %s %s %ld %s\n", GetMagickModule(), description); MagickRelinquishMemory (description); } DestroyPixelWand (pixel_wand); } /* * resize_image: resize image pointed by src and save result to dst */ gboolean resize_image (const gchar *src, const gchar *dst, unsigned long size_x, unsigned long size_y, int quality, gboolean thumbnail, ThumbnailSquareType squared_thumbnail_type, gboolean autorotate) { MagickWand *magick_wand; ExceptionType severity; unsigned long w; unsigned long h; unsigned long amount; gchar *description; /* Read an image. */ magick_wand = NewMagickWand(); if (MagickReadImage (magick_wand, src) == MagickFalse) { description = MagickGetException (magick_wand, &severity); log_error ("Error reading image: %s %s %ld %s\n", GetMagickModule(), description); MagickRelinquishMemory (description); return FALSE; } if (autorotate) autorotate_image (magick_wand); /* Don't resize if smaller than desired size */ if (MagickGetImageWidth (magick_wand) > size_x || MagickGetImageHeight (magick_wand) > size_y) { /* Process thumbnail if required */ if (thumbnail) { switch (squared_thumbnail_type) { case THUMBNAIL_SQUARE_TYPE_SIMPLE: w = MagickGetImageWidth (magick_wand); h = MagickGetImageHeight (magick_wand); amount = MAX (w, h) * SQUARED_SIMPLE_SHAVE_AMOUNT / 100; amount = MIN (w - 2*amount, h - 2*amount); MagickCropImage (magick_wand, amount, amount, (w - amount) / 2, (h - amount) / 2); break; default: break; } MagickThumbnailImage (magick_wand, size_x, size_y); } else MagickResizeImage (magick_wand, size_x, size_y, LanczosFilter, 1.0); } MagickSetImageCompressionQuality (magick_wand, quality); /* Write the image and destroy it. */ if (MagickWriteImage (magick_wand, dst) == MagickFalse) { description = MagickGetException (magick_wand, &severity); log_error ("Error writing image: %s %s %ld %s\n", GetMagickModule(), description); MagickRelinquishMemory (description); return FALSE; } magick_wand = DestroyMagickWand (magick_wand); return TRUE; } /* * get_image_sizes: retrieve image dimensions */ void get_image_sizes (const gchar *img, unsigned long *width, unsigned long *height, gboolean autorotate) { MagickWand *magick_wand; MagickBooleanType b; ExceptionType severity; gchar *description; *width = -1; *height = -1; /* Read an image. */ magick_wand = NewMagickWand(); if (autorotate) b = MagickReadImage (magick_wand, img); else b = MagickPingImage (magick_wand, img); if (b == MagickFalse) { description = MagickGetException (magick_wand, &severity); log_error ("Error reading image info: %s %s %ld %s\n", GetMagickModule(), description); MagickRelinquishMemory(description); return; } if (autorotate) autorotate_image (magick_wand); *width = MagickGetImageWidth (magick_wand); *height = MagickGetImageHeight (magick_wand); magick_wand = DestroyMagickWand (magick_wand); } /* * calculate_sizes: calculate maximal image sizes within specified limits keeping aspect ratio */ void calculate_sizes (const unsigned long max_width, const unsigned long max_height, unsigned long *width, unsigned long *height) { if (max_width > *width && max_height > *height) return; double max_ratio = (double) max_width / (double) max_height; double real_ratio = (double) *width / (double) *height; if (*width > *height && max_ratio <= real_ratio) { *height = (unsigned long) (max_width / real_ratio); *width = max_width; } else { *width = (unsigned long) (max_height * real_ratio); *height = max_height; } } /* * modify_exif: - strip thumbnail stored in EXIF table * - add copyright to Exif::Image::Copyright and Iptc::Application2::Copyright */ void modify_exif (const gchar *filename, gboolean strip_thumbnail, const gchar *add_copyright) { gboolean modified; if (! strip_thumbnail && add_copyright == NULL) return; modified = FALSE; try { Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open (filename); g_assert (image.get() != 0); image->readMetadata(); Exiv2::ExifData &exifData = image->exifData(); if (add_copyright) { exifData["Exif.Image.Copyright"] = add_copyright; image->iptcData()["Iptc.Application2.Copyright"] = add_copyright; modified = TRUE; } if (strip_thumbnail && ! exifData.empty()) { #ifdef HAVE_EXIFTHUMB Exiv2::ExifThumb exifThumb(image->exifData()); std::string thumbExt = exifThumb.extension(); #else std::string thumbExt = exifData.thumbnailExtension(); #endif if (! thumbExt.empty()) { #ifdef HAVE_EXIFTHUMB exifThumb.erase(); #else exifData.eraseThumbnail(); #endif modified = TRUE; } } if (modified) image->writeMetadata(); } catch (Exiv2::AnyError& e) { log_error ("modify_exif: Caught Exiv2 exception: '%s'\n", e.what()); } }