diff options
Diffstat (limited to 'src/extension/internal/image-resolution.cpp')
-rw-r--r-- | src/extension/internal/image-resolution.cpp | 447 |
1 files changed, 447 insertions, 0 deletions
diff --git a/src/extension/internal/image-resolution.cpp b/src/extension/internal/image-resolution.cpp new file mode 100644 index 0000000..3ca596c --- /dev/null +++ b/src/extension/internal/image-resolution.cpp @@ -0,0 +1,447 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Daniel Wagenaar <daw@caltech.edu> + * + * Copyright (C) 2012 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" // only include where actually required! +#endif + +#include "util/units.h" +#include "image-resolution.h" + +#define IR_TRY_PNG 1 +#include <png.h> + +#ifdef HAVE_EXIF +#include <math.h> +#include <libexif/exif-data.h> +#endif + +#define IR_TRY_EXIV 0 + +#ifdef HAVE_JPEG +#define IR_TRY_JFIF 1 +#include <jpeglib.h> +#include <csetjmp> +#endif + +#ifdef WITH_MAGICK +#include <Magick++.h> +#endif + +#define noIMAGE_RESOLUTION_DEBUG + +#ifdef IMAGE_RESOLUTION_DEBUG +# define debug(f, a...) { g_print("%s(%d) %s:", \ + __FILE__,__LINE__,__FUNCTION__); \ + g_print(f, ## a); \ + g_print("\n"); \ + } +#else +# define debug(f, a...) /* */ +#endif + +namespace Inkscape { +namespace Extension { +namespace Internal { + +ImageResolution::ImageResolution(char const *fn) { + ok_ = false; + + readpng(fn); + if (!ok_) { + readexiv(fn); + } + if (!ok_) { + readjfif(fn); + } + if (!ok_) { + readexif(fn); + } + if (!ok_) { + readmagick(fn); + } +} + +bool ImageResolution::ok() const { + return ok_; +} + +double ImageResolution::x() const { + return x_; +} + +double ImageResolution::y() const { + return y_; +} + + + +#if IR_TRY_PNG + +static bool haspngheader(FILE *fp) { + unsigned char header[8]; + if ( fread(header, 1, 8, fp) != 8 ) { + return false; + } + + fseek(fp, 0, SEEK_SET); + + if (png_sig_cmp(header, 0, 8)) { + return false; + } + + return true; +} + +// Implementation using libpng +void ImageResolution::readpng(char const *fn) { + FILE *fp = fopen(fn, "rb"); + if (!fp) + return; + + if (!haspngheader(fp)) { + fclose(fp); + return; + } + + png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); + if (!png_ptr) + return; + + png_infop info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) { + png_destroy_read_struct(&png_ptr, nullptr, nullptr); + return; + } + + if (setjmp(png_jmpbuf(png_ptr))) { + png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); + fclose(fp); + return; + } + + png_init_io(png_ptr, fp); + png_read_info(png_ptr, info_ptr); + + png_uint_32 res_x, res_y; +#ifdef PNG_INCH_CONVERSIONS_SUPPORTED + debug("PNG_INCH_CONVERSIONS_SUPPORTED"); + res_x = png_get_x_pixels_per_inch(png_ptr, info_ptr); + res_y = png_get_y_pixels_per_inch(png_ptr, info_ptr); + if (res_x != 0 && res_y != 0) { + ok_ = true; + x_ = res_x * 1.0; // FIXME: implicit conversion of png_uint_32 to double ok? + y_ = res_y * 1.0; // FIXME: implicit conversion of png_uint_32 to double ok? + } +#else + debug("PNG_RESOLUTION_METER"); + int unit_type; + // FIXME: png_get_pHYs() fails to return expected values + // with clang (based on LLVM 3.2svn) from Xcode 4.6.3 (OS X 10.7.5) + png_get_pHYs(png_ptr, info_ptr, &res_x, &res_y, &unit_type); + + if (unit_type == PNG_RESOLUTION_METER) { + ok_ = true; + x_ = res_x * 2.54 / 100; + y_ = res_y * 2.54 / 100; + } +#endif + + png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); + fclose(fp); + + if (ok_) { + debug("xdpi: %f", x_); + debug("ydpi: %f", y_); + } else { + debug("FAILED"); + } +} +#else + +// Dummy implementation +void ImageResolution::readpng(char const *) { +} + +#endif + +#if IR_TRY_EXIF + +static double exifDouble(ExifEntry *entry, ExifByteOrder byte_order) { + switch (entry->format) { + case EXIF_FORMAT_BYTE: { + return double(entry->data[0]); + } + case EXIF_FORMAT_SHORT: { + return double(exif_get_short(entry->data, byte_order)); + } + case EXIF_FORMAT_LONG: { + return double(exif_get_long(entry->data, byte_order)); + } + case EXIF_FORMAT_RATIONAL: { + ExifRational r = exif_get_rational(entry->data, byte_order); + return double(r.numerator) / double(r.denominator); + } + case EXIF_FORMAT_SBYTE: { + return double(*(signed char *)entry->data); + } + case EXIF_FORMAT_SSHORT: { + return double(exif_get_sshort(entry->data, byte_order)); + } + case EXIF_FORMAT_SLONG: { + return double(exif_get_slong(entry->data, byte_order)); + } + case EXIF_FORMAT_SRATIONAL: { + ExifSRational r = exif_get_srational(entry->data, byte_order); + return double(r.numerator) / double(r.denominator); + } + case EXIF_FORMAT_FLOAT: { + return double((reinterpret_cast<float *>(entry->data))[0]); + } + case EXIF_FORMAT_DOUBLE: { + return (reinterpret_cast<double *>(entry->data))[0]; + } + default: { + return nan(0); + } + } +} + +// Implementation using libexif +void ImageResolution::readexif(char const *fn) { + ExifData *ed; + ed = exif_data_new_from_file(fn); + if (!ed) + return; + + ExifByteOrder byte_order = exif_data_get_byte_order(ed); + + ExifEntry *xres = exif_content_get_entry(ed->ifd[EXIF_IFD_0], EXIF_TAG_X_RESOLUTION); + ExifEntry *yres = exif_content_get_entry(ed->ifd[EXIF_IFD_0], EXIF_TAG_Y_RESOLUTION); + ExifEntry *unit = exif_content_get_entry(ed->ifd[EXIF_IFD_0], EXIF_TAG_RESOLUTION_UNIT); + + if ( xres && yres ) { + x_ = exifDouble(xres, byte_order); + y_ = exifDouble(yres, byte_order); + if (unit) { + double u = exifDouble(unit, byte_order); + if ( u == 3 ) { + x_ *= 2.54; + y_ *= 2.54; + } + } + ok_ = true; + } + exif_data_free(ed); + + if (ok_) { + debug("xdpi: %f", x_); + debug("ydpi: %f", y_); + } else { + debug("FAILED"); + } +} + +#else + +// Dummy implementation +void ImageResolution::readexif(char const *) { +} + +#endif + +#if IR_TRY_EXIV + +void ImageResolution::readexiv(char const *fn) { + Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open(fn); + if (!image.get()) + return; + + image->readMetadata(); + Exiv2::ExifData &exifData = image->exifData(); + if (exifData.empty()) + return; + + Exiv2::ExifData::const_iterator end = exifData.end(); + bool havex = false; + bool havey = false; + bool haveunit = false; + int unit; + for (Exiv2::ExifData::const_iterator i = exifData.begin(); i != end; ++i) { + if (ok_) + break; + if ( i->tag() == 0x011a ) { + // X Resolution + x_ = i->toFloat(); + havex = true; + } else if ( i->tag() == 0x011b ) { + // Y Resolution + y_ = i->toFloat(); + havey = true; + } else if ( i->tag() == 0x0128 ) { + unit = i->toLong(); + } + ok_ = havex && havey && haveunit; + } + if (haveunit) { + if ( unit == 3 ) { + x_ *= 2.54; + y_ *= 2.54; + } + } + ok_ = havex && havey; + + if (ok_) { + debug("xdpi: %f", x_); + debug("ydpi: %f", y_); + } else { + debug("FAILED"); + } +} + +#else + +// Dummy implementation +void ImageResolution::readexiv(char const *) { +} + +#endif + +#if IR_TRY_JFIF + +static void irjfif_error_exit(j_common_ptr cinfo) { + longjmp(*(jmp_buf*)cinfo->client_data, 1); +} + +static void irjfif_emit_message(j_common_ptr, int) { +} + +static void irjfif_output_message(j_common_ptr) { +} + +static void irjfif_format_message(j_common_ptr, char *) { +} + +static void irjfif_reset(j_common_ptr) { +} + +void ImageResolution::readjfif(char const *fn) { + FILE *ifd = fopen(fn, "rb"); + if (!ifd) { + return; + } + + struct jpeg_decompress_struct cinfo; + jmp_buf jbuf; + struct jpeg_error_mgr jerr; + + if (setjmp(jbuf)) { + fclose(ifd); + jpeg_destroy_decompress(&cinfo); + return; + } + + cinfo.err = jpeg_std_error(&jerr); + jpeg_create_decompress(&cinfo); + jerr.error_exit = &irjfif_error_exit; + jerr.emit_message = &irjfif_emit_message; + jerr.output_message = &irjfif_output_message; + jerr.format_message = &irjfif_format_message; + jerr.reset_error_mgr = &irjfif_reset; + cinfo.client_data = (void*)&jbuf; + + jpeg_stdio_src(&cinfo, ifd); + jpeg_read_header(&cinfo, TRUE); + + debug("cinfo.[XY]_density"); + if (cinfo.saw_JFIF_marker) { // JFIF APP0 marker was seen + if ( cinfo.density_unit == 1 ) { // dots/inch + x_ = cinfo.X_density; + y_ = cinfo.Y_density; + ok_ = true; + } else if ( cinfo.density_unit == 2 ) { // dots/cm + x_ = cinfo.X_density * 2.54; + y_ = cinfo.Y_density * 2.54; + ok_ = true; + } + /* According to http://www.jpeg.org/public/jfif.pdf (page 7): + * "Xdensity and Ydensity should always be non-zero". + * but in some cases, they are (see LP bug #1275443) */ + if (x_ == 0 or y_ == 0) { + ok_ = false; + } + } + jpeg_destroy_decompress(&cinfo); + fclose(ifd); + + if (ok_) { + debug("xdpi: %f", x_); + debug("ydpi: %f", y_); + } else { + debug("FAILED"); + } +} + +#else + +// Dummy implementation +void ImageResolution::readjfif(char const *) { +} + +#endif + +#ifdef WITH_MAGICK +void ImageResolution::readmagick(char const *fn) { + Magick::Image image; + debug("Trying image.read"); + try { + image.read(fn); + } catch (Magick::Error & err) { + debug("ImageMagick error: %s", err.what()); + return; + } catch (std::exception & err) { + debug("ImageResolution::readmagick: %s", err.what()); + return; + } + debug("image.[xy]Resolution"); + std::string const type = image.magick(); + x_ = image.xResolution(); + y_ = image.yResolution(); + +// TODO: find out why the hell the following conversion is necessary + if (type == "BMP") { + x_ = Inkscape::Util::Quantity::convert(x_, "in", "cm"); + y_ = Inkscape::Util::Quantity::convert(y_, "in", "cm"); + } + + if (x_ != 0 && y_ != 0) { + ok_ = true; + } + + if (ok_) { + debug("xdpi: %f", x_); + debug("ydpi: %f", y_); + } else { + debug("FAILED"); + debug("Using default Inkscape import resolution"); + } +} + +#else + +// Dummy implementation +void ImageResolution::readmagick(char const *) { +} + +#endif /* WITH_MAGICK */ + +} +} +} |