// SPDX-License-Identifier: GPL-2.0-or-later /* * Authors: * Daniel Wagenaar * * 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 #ifdef HAVE_EXIF #include #include #endif #define IR_TRY_EXIV 0 #ifdef HAVE_JPEG #define IR_TRY_JFIF 1 #include #include #endif #ifdef WITH_MAGICK #include #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(entry->data))[0]); } case EXIF_FORMAT_DOUBLE: { return (reinterpret_cast(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 */ } } }