/* * This file is part of RawTherapee. * * Copyright (c) 2004-2010 Gabor Horvath * Copyright (c) 2010 Oliver Duis * * RawTherapee 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 3 of the License, or * (at your option) any later version. * * RawTherapee 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 RawTherapee. If not, see . */ #include #include #include #include #include #include #include #include #include "rt_math.h" #include "../rtgui/options.h" #include "../rtgui/version.h" #ifdef WIN32 #include #else #include #endif #include "imageio.h" #include "iptcpairs.h" #include "iccjpeg.h" #include "color.h" #include "jpeg.h" using namespace std; using namespace rtengine; using namespace rtengine::procparams; namespace { // Opens a file for binary writing and request exclusive lock (cases were you need "wb" mode plus locking) FILE* g_fopen_withBinaryAndLock(const Glib::ustring& fname) { #ifdef WIN32 // Use native function to disallow sharing, i.e. lock the file for exclusive access. // This is important to e.g. prevent Windows Explorer from crashing RT due to concurrently scanning an image file. std::unique_ptr wfname (reinterpret_cast(g_utf8_to_utf16 (fname.c_str (), -1, NULL, NULL, NULL)), g_free); HANDLE hFile = CreateFileW ( wfname.get (), GENERIC_READ | GENERIC_WRITE, 0 /* no sharing allowed */, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); FILE* f = nullptr; if (hFile != INVALID_HANDLE_VALUE) { f = _fdopen (_open_osfhandle ((intptr_t)hFile, 0), "wb"); } #else FILE* f = ::g_fopen (fname.c_str (), "wb"); #endif return f; } } Glib::ustring ImageIO::errorMsg[6] = {"Success", "Cannot read file.", "Invalid header.", "Error while reading header.", "File reading error", "Image format not supported."}; // For only copying the raw input data void ImageIO::setMetadata (const rtexif::TagDirectory* eroot) { if (exifRoot != nullptr) { delete exifRoot; exifRoot = nullptr; } if (eroot) { rtexif::TagDirectory* td = ((rtexif::TagDirectory*)eroot)->clone (nullptr); // make IPTC and XMP pass through td->keepTag(0x83bb); // IPTC td->keepTag(0x02bc); // XMP exifRoot = td; } } // For merging with RT specific data void ImageIO::setMetadata (const rtexif::TagDirectory* eroot, const rtengine::procparams::ExifPairs& exif, const rtengine::procparams::IPTCPairs& iptcc) { // store exif info exifChange.clear(); exifChange = exif; if (exifRoot != nullptr) { delete exifRoot; exifRoot = nullptr; } if (eroot) { exifRoot = ((rtexif::TagDirectory*)eroot)->clone (nullptr); } if (iptc != nullptr) { iptc_data_free (iptc); iptc = nullptr; } // build iptc structures for libiptcdata if (iptcc.empty()) { return; } iptc = iptc_data_new (); const unsigned char utf8Esc[] = {0x1B, '%', 'G'}; IptcDataSet * ds = iptc_dataset_new (); iptc_dataset_set_tag (ds, IPTC_RECORD_OBJECT_ENV, IPTC_TAG_CHARACTER_SET); iptc_dataset_set_data (ds, utf8Esc, 3, IPTC_DONT_VALIDATE); iptc_data_add_dataset (iptc, ds); iptc_dataset_unref (ds); for (rtengine::procparams::IPTCPairs::const_iterator i = iptcc.begin(); i != iptcc.end(); ++i) { if (i->first == "Keywords" && !(i->second.empty())) { for (unsigned int j = 0; j < i->second.size(); j++) { IptcDataSet * ds = iptc_dataset_new (); iptc_dataset_set_tag (ds, IPTC_RECORD_APP_2, IPTC_TAG_KEYWORDS); iptc_dataset_set_data (ds, (unsigned char*)i->second.at(j).c_str(), min(static_cast(64), i->second.at(j).bytes()), IPTC_DONT_VALIDATE); iptc_data_add_dataset (iptc, ds); iptc_dataset_unref (ds); } continue; } else if (i->first == "SupplementalCategories" && !(i->second.empty())) { for (unsigned int j = 0; j < i->second.size(); j++) { IptcDataSet * ds = iptc_dataset_new (); iptc_dataset_set_tag (ds, IPTC_RECORD_APP_2, IPTC_TAG_SUPPL_CATEGORY); iptc_dataset_set_data (ds, (unsigned char*)i->second.at(j).c_str(), min(static_cast(32), i->second.at(j).bytes()), IPTC_DONT_VALIDATE); iptc_data_add_dataset (iptc, ds); iptc_dataset_unref (ds); } continue; } for (int j = 0; j < 16; j++) if (i->first == strTags[j].field && !(i->second.empty())) { IptcDataSet * ds = iptc_dataset_new (); iptc_dataset_set_tag (ds, IPTC_RECORD_APP_2, strTags[j].tag); iptc_dataset_set_data (ds, (unsigned char*)i->second.at(0).c_str(), min(strTags[j].size, i->second.at(0).bytes()), IPTC_DONT_VALIDATE); iptc_data_add_dataset (iptc, ds); iptc_dataset_unref (ds); } } iptc_data_sort (iptc); } void ImageIO::setOutputProfile (const char* pdata, int plen) { delete [] profileData; if (pdata) { profileData = new char [plen]; memcpy (profileData, pdata, plen); } else { profileData = nullptr; } profileLength = plen; } ImageIO::~ImageIO () { if (embProfile) { cmsCloseProfile(embProfile); } deleteLoadedProfileData(); delete exifRoot; delete [] profileData; } void png_read_data(png_struct_def *png_ptr, unsigned char *data, size_t length); void png_write_data(png_struct_def *png_ptr, unsigned char *data, size_t length); void png_flush(png_struct_def *png_ptr); int ImageIO::getPNGSampleFormat (Glib::ustring fname, IIOSampleFormat &sFormat, IIOSampleArrangement &sArrangement) { FILE *file = g_fopen (fname.c_str (), "rb"); if (!file) { return IMIO_CANNOTREADFILE; } //reading PNG header unsigned char header[8]; if (fread (header, 1, 8, file) != 8 || png_sig_cmp (header, 0, 8)) { fclose(file); return IMIO_HEADERERROR; } //initializing main structures png_structp png = png_create_read_struct (PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); if (!png) { fclose (file); return IMIO_HEADERERROR; } png_infop info = png_create_info_struct (png); png_infop end_info = png_create_info_struct (png); if (!end_info || !info) { png_destroy_read_struct (&png, &info, &end_info); fclose (file); return IMIO_HEADERERROR; } if (setjmp (png_jmpbuf(png))) { png_destroy_read_struct (&png, &info, &end_info); fclose (file); return IMIO_READERROR; } //set up png read png_set_read_fn (png, file, png_read_data); png_set_sig_bytes (png, 8); png_read_info(png, info); //retrieving image information png_uint_32 width, height; int bit_depth, color_type, interlace_type, compression_type, filter_method; png_get_IHDR(png, info, &width, &height, &bit_depth, &color_type, &interlace_type, &compression_type, &filter_method); png_destroy_read_struct (&png, &info, &end_info); fclose (file); if (interlace_type != PNG_INTERLACE_NONE) { return IMIO_VARIANTNOTSUPPORTED; } if (bit_depth == 8) { sArrangement = IIOSA_CHUNKY; sFormat = IIOSF_UNSIGNED_CHAR; return IMIO_SUCCESS; } else if (bit_depth == 16) { sArrangement = IIOSA_CHUNKY; sFormat = IIOSF_UNSIGNED_SHORT; return IMIO_SUCCESS; } else { sArrangement = IIOSA_UNKNOWN; sFormat = IIOSF_UNKNOWN; return IMIO_VARIANTNOTSUPPORTED; } } int ImageIO::loadPNG (Glib::ustring fname) { FILE *file = g_fopen (fname.c_str (), "rb"); if (!file) { return IMIO_CANNOTREADFILE; } if (pl) { pl->setProgressStr ("PROGRESSBAR_LOADPNG"); pl->setProgress (0.0); } //reading PNG header unsigned char header[8]; if (fread (header, 1, 8, file) != 8 || png_sig_cmp (header, 0, 8)) { fclose(file); return IMIO_HEADERERROR; } //initializing main structures png_structp png = png_create_read_struct (PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); if (!png) { fclose (file); return IMIO_HEADERERROR; } // silence the warning about "invalid" sRGB profiles -- see #4260 #if defined(PNG_SKIP_sRGB_CHECK_PROFILE) && defined(PNG_SET_OPTION_SUPPORTED) png_set_option(png, PNG_SKIP_sRGB_CHECK_PROFILE, PNG_OPTION_ON); #endif png_infop info = png_create_info_struct (png); png_infop end_info = png_create_info_struct (png); if (!end_info || !info) { png_destroy_read_struct (&png, &info, &end_info); fclose (file); return IMIO_HEADERERROR; } if (setjmp (png_jmpbuf(png))) { png_destroy_read_struct (&png, &info, &end_info); fclose (file); return IMIO_READERROR; } //set up png read png_set_read_fn (png, file, png_read_data); png_set_sig_bytes (png, 8); png_read_info(png, info); embProfile = nullptr; //retrieving image information png_uint_32 width, height; int bit_depth, color_type, interlace_type, compression_type, filter_method; png_get_IHDR(png, info, &width, &height, &bit_depth, &color_type, &interlace_type, &compression_type, &filter_method); if (color_type == PNG_COLOR_TYPE_PALETTE || interlace_type != PNG_INTERLACE_NONE ) { // we don't support interlaced png or png with palette png_destroy_read_struct (&png, &info, &end_info); fclose (file); printf("%s uses an unsupported feature: . Skipping.\n", fname.data()); return IMIO_VARIANTNOTSUPPORTED; } if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) { png_set_gray_to_rgb(png); } if (png_get_valid(png, info, PNG_INFO_tRNS)) { png_set_tRNS_to_alpha(png); } if (color_type & PNG_COLOR_MASK_ALPHA) { png_set_strip_alpha(png); } // reading the embedded ICC profile if any if (png_get_valid(png, info, PNG_INFO_iCCP)) { png_charp name; int compression_type; #if PNG_LIBPNG_VER < 10500 png_charp profdata; #else png_bytep profdata; #endif png_uint_32 proflen; png_get_iCCP(png, info, &name, &compression_type, &profdata, &proflen); embProfile = cmsOpenProfileFromMem(profdata, proflen); loadedProfileData = new char[proflen]; memcpy(loadedProfileData, profdata, proflen); } //setting gamma double gamma; if (png_get_gAMA(png, info, &gamma)) { png_set_gamma(png, 1.0 / gamma, gamma); // use gamma from metadata } else { png_set_gamma(png, 2.2, 1.0 / 2.2); // no gamma in metadata, suppose gamma 2.2 } // if (bps==8 && bit_depth==16) png_set_strip_16(png); //updating png info struct png_read_update_info(png, info); png_get_IHDR(png, info, &width, &height, &bit_depth, &color_type, &interlace_type, &compression_type, &filter_method); allocate (width, height); int rowlen = width * 3 * bit_depth / 8; unsigned char *row = new unsigned char [rowlen]; // set a new jump point to avoid memory leak if (setjmp (png_jmpbuf(png))) { png_destroy_read_struct (&png, &info, &end_info); fclose (file); delete [] row; return IMIO_READERROR; } for (unsigned int i = 0; i < height; i++) { png_read_row (png, (png_byte*)row, nullptr); if (bit_depth == 16) { // convert scanline to host byte order unsigned short* srow = (unsigned short*)row; for (unsigned int j = 0; j < width * 3; j++) { srow[j] = ntohs (srow[j]); } } setScanline (i, row, bit_depth); if (pl && !(i % 100)) { pl->setProgress ((double)(i + 1) / height); } } png_read_end (png, nullptr); png_destroy_read_struct (&png, &info, &end_info); delete [] row; fclose(file); if (pl) { pl->setProgressStr ("PROGRESSBAR_READY"); pl->setProgress (1.0); } return IMIO_SUCCESS; } typedef struct { struct jpeg_error_mgr pub; /* "public" fields */ jmp_buf setjmp_buffer; /* for return to caller */ } my_error_mgr; void my_error_exit (j_common_ptr cinfo) { /* cinfo->err really points to a my_error_mgr struct, so coerce pointer */ my_error_mgr *myerr = (my_error_mgr*) cinfo->err; /* Always display the message. */ /* We could postpone this until after returning, if we chose. */ (*cinfo->err->output_message) (cinfo); /* Return control to the setjmp point */ #if defined( WIN32 ) && defined( __x86_64__ ) __builtin_longjmp(myerr->setjmp_buffer, 1); #else longjmp(myerr->setjmp_buffer, 1); #endif } int ImageIO::loadJPEGFromMemory (const char* buffer, int bufsize) { jpeg_decompress_struct cinfo; jpeg_create_decompress(&cinfo); jpeg_memory_src (&cinfo, (const JOCTET*)buffer, bufsize); /* We use our private extension JPEG error handler. Note that this struct must live as long as the main JPEG parameter struct, to avoid dangling-pointer problems. */ my_error_mgr jerr; /* We set up the normal JPEG error routines, then override error_exit. */ cinfo.err = jpeg_std_error(&jerr.pub); jerr.pub.error_exit = my_error_exit; /* Establish the setjmp return context for my_error_exit to use. */ #if defined( WIN32 ) && defined( __x86_64__ ) if (__builtin_setjmp(jerr.setjmp_buffer)) { #else if (setjmp(jerr.setjmp_buffer)) { #endif /* If we get here, the JPEG code has signaled an error. We need to clean up the JPEG object and return. */ jpeg_destroy_decompress(&cinfo); return IMIO_READERROR; } if (pl) { pl->setProgressStr ("PROGRESSBAR_LOADJPEG"); pl->setProgress (0.0); } setup_read_icc_profile (&cinfo); jpeg_read_header(&cinfo, TRUE); deleteLoadedProfileData(); loadedProfileDataJpg = true; bool hasprofile = read_icc_profile (&cinfo, (JOCTET**)&loadedProfileData, (unsigned int*)&loadedProfileLength); if (hasprofile) { embProfile = cmsOpenProfileFromMem (loadedProfileData, loadedProfileLength); } else { embProfile = nullptr; } jpeg_start_decompress(&cinfo); unsigned int width = cinfo.output_width; unsigned int height = cinfo.output_height; allocate (width, height); unsigned char *row = new unsigned char[width * 3]; while (cinfo.output_scanline < height) { if (jpeg_read_scanlines(&cinfo, &row, 1) < 1) { jpeg_finish_decompress(&cinfo); jpeg_destroy_decompress(&cinfo); delete [] row; return IMIO_READERROR; } setScanline (cinfo.output_scanline - 1, row, 8); if (pl && !(cinfo.output_scanline % 100)) { pl->setProgress ((double)(cinfo.output_scanline) / cinfo.output_height); } } delete [] row; jpeg_finish_decompress(&cinfo); jpeg_destroy_decompress(&cinfo); if (pl) { pl->setProgressStr ("PROGRESSBAR_READY"); pl->setProgress (1.0); } return IMIO_SUCCESS; } int ImageIO::loadJPEG (Glib::ustring fname) { FILE *file = g_fopen(fname.c_str (), "rb"); if (!file) { return IMIO_CANNOTREADFILE; } jpeg_decompress_struct cinfo; jpeg_error_mgr jerr; cinfo.err = my_jpeg_std_error(&jerr); jpeg_create_decompress(&cinfo); my_jpeg_stdio_src (&cinfo, file); if ( setjmp((reinterpret_cast(cinfo.src))->error_jmp_buf) == 0 ) { if (pl) { pl->setProgressStr ("PROGRESSBAR_LOADJPEG"); pl->setProgress (0.0); } setup_read_icc_profile (&cinfo); //jpeg_stdio_src(&cinfo,file); jpeg_read_header(&cinfo, TRUE); //if JPEG is CMYK, then abort reading if (cinfo.jpeg_color_space == JCS_CMYK || cinfo.jpeg_color_space == JCS_YCCK) { jpeg_destroy_decompress(&cinfo); return IMIO_READERROR; } cinfo.out_color_space = JCS_RGB; deleteLoadedProfileData(); loadedProfileDataJpg = true; bool hasprofile = read_icc_profile (&cinfo, (JOCTET**)&loadedProfileData, (unsigned int*)&loadedProfileLength); if (hasprofile) { embProfile = cmsOpenProfileFromMem (loadedProfileData, loadedProfileLength); } else { embProfile = nullptr; } jpeg_start_decompress(&cinfo); unsigned int width = cinfo.output_width; unsigned int height = cinfo.output_height; allocate (width, height); unsigned char *row = new unsigned char[width * 3]; while (cinfo.output_scanline < height) { if (jpeg_read_scanlines(&cinfo, &row, 1) < 1) { jpeg_finish_decompress(&cinfo); jpeg_destroy_decompress(&cinfo); delete [] row; return IMIO_READERROR; } setScanline (cinfo.output_scanline - 1, row, 8); if (pl && !(cinfo.output_scanline % 100)) { pl->setProgress ((double)(cinfo.output_scanline) / cinfo.output_height); } } delete [] row; jpeg_finish_decompress(&cinfo); jpeg_destroy_decompress(&cinfo); fclose(file); if (pl) { pl->setProgressStr ("PROGRESSBAR_READY"); pl->setProgress (1.0); } return IMIO_SUCCESS; } else { jpeg_destroy_decompress(&cinfo); return IMIO_READERROR; } } int ImageIO::getTIFFSampleFormat (Glib::ustring fname, IIOSampleFormat &sFormat, IIOSampleArrangement &sArrangement) { #ifdef WIN32 wchar_t *wfilename = (wchar_t*)g_utf8_to_utf16 (fname.c_str(), -1, NULL, NULL, NULL); TIFF* in = TIFFOpenW (wfilename, "r"); g_free (wfilename); #else TIFF* in = TIFFOpen(fname.c_str(), "r"); #endif if (in == nullptr) { return IMIO_CANNOTREADFILE; } uint16 bitspersample = 0, samplesperpixel = 0, sampleformat = 0; int hasTag = TIFFGetField(in, TIFFTAG_BITSPERSAMPLE, &bitspersample); hasTag &= TIFFGetField(in, TIFFTAG_SAMPLESPERPIXEL, &samplesperpixel); if (!hasTag) { // These are needed TIFFClose(in); sFormat = IIOSF_UNKNOWN; return IMIO_VARIANTNOTSUPPORTED; } if (!TIFFGetField(in, TIFFTAG_SAMPLEFORMAT, &sampleformat)) /* * WARNING: This is a dirty hack! * We assume that files which doesn't contain the TIFFTAG_SAMPLEFORMAT tag * (which is the case with uncompressed TIFFs produced by RT!) are RGB files, * but that may be not true. --- Hombre */ { sampleformat = SAMPLEFORMAT_UINT; } uint16 config; TIFFGetField(in, TIFFTAG_PLANARCONFIG, &config); if (config == PLANARCONFIG_CONTIG) { sArrangement = IIOSA_CHUNKY; } else { sFormat = IIOSF_UNKNOWN; sArrangement = IIOSA_UNKNOWN; TIFFClose(in); return IMIO_VARIANTNOTSUPPORTED; } uint16 photometric; if (!TIFFGetField(in, TIFFTAG_PHOTOMETRIC, &photometric)) { TIFFClose(in); return IMIO_VARIANTNOTSUPPORTED; } uint16 compression; if (photometric == PHOTOMETRIC_LOGLUV) if (!TIFFGetField(in, TIFFTAG_COMPRESSION, &compression)) { compression = COMPRESSION_NONE; } TIFFClose(in); if (photometric == PHOTOMETRIC_RGB || photometric == PHOTOMETRIC_MINISBLACK) { if ((samplesperpixel == 1 || samplesperpixel == 3 || samplesperpixel == 4) && sampleformat == SAMPLEFORMAT_UINT) { if (bitspersample == 8) { sFormat = IIOSF_UNSIGNED_CHAR; return IMIO_SUCCESS; } if (bitspersample == 16) { sFormat = IIOSF_UNSIGNED_SHORT; return IMIO_SUCCESS; } } else if (samplesperpixel == 3 && sampleformat == SAMPLEFORMAT_IEEEFP) { /* * Not yet supported * if (bitspersample==16) { sFormat = IIOSF_HALF; return IMIO_SUCCESS; }*/ if (bitspersample == 32) { sFormat = IIOSF_FLOAT; return IMIO_SUCCESS; } } } else if (samplesperpixel == 3 && photometric == PHOTOMETRIC_LOGLUV) { if (compression == COMPRESSION_SGILOG24) { sFormat = IIOSF_LOGLUV24; return IMIO_SUCCESS; } else if (compression == COMPRESSION_SGILOG) { sFormat = IIOSF_LOGLUV32; return IMIO_SUCCESS; } } return IMIO_VARIANTNOTSUPPORTED; } int ImageIO::loadTIFF (Glib::ustring fname) { static MyMutex thumbMutex; MyMutex::MyLock lock(thumbMutex); if(!options.serializeTiffRead) { lock.release(); } #ifdef WIN32 wchar_t *wfilename = (wchar_t*)g_utf8_to_utf16 (fname.c_str(), -1, NULL, NULL, NULL); TIFF* in = TIFFOpenW (wfilename, "r"); g_free (wfilename); #else TIFF* in = TIFFOpen(fname.c_str(), "r"); #endif if (in == nullptr) { return IMIO_CANNOTREADFILE; } if (pl) { pl->setProgressStr ("PROGRESSBAR_LOADTIFF"); pl->setProgress (0.0); } int width, height; TIFFGetField(in, TIFFTAG_IMAGEWIDTH, &width); TIFFGetField(in, TIFFTAG_IMAGELENGTH, &height); uint16 bitspersample, samplesperpixel; int hasTag = TIFFGetField(in, TIFFTAG_BITSPERSAMPLE, &bitspersample); hasTag &= TIFFGetField(in, TIFFTAG_SAMPLESPERPIXEL, &samplesperpixel); if (!hasTag) { // These are needed TIFFClose(in); return IMIO_VARIANTNOTSUPPORTED; } uint16 config; TIFFGetField(in, TIFFTAG_PLANARCONFIG, &config); if (config != PLANARCONFIG_CONTIG) { TIFFClose(in); return IMIO_VARIANTNOTSUPPORTED; } if (sampleFormat & (IIOSF_LOGLUV24 | IIOSF_LOGLUV32)) { TIFFSetField(in, TIFFTAG_SGILOGDATAFMT, SGILOGDATAFMT_FLOAT); } /* * We could use the min/max values set in TIFFTAG_SMINSAMPLEVALUE and * TIFFTAG_SMAXSAMPLEVALUE, but for now, we normalize the image to the * effective minimum and maximum values */ if (options.rtSettings.verbose) { printf("Information of \"%s\":\n", fname.c_str()); uint16 tiffDefaultScale, tiffBaselineExposure, tiffLinearResponseLimit; if (TIFFGetField(in, TIFFTAG_DEFAULTSCALE, &tiffDefaultScale)) { printf(" DefaultScale: %d\n", tiffDefaultScale); } else printf(" No DefaultScale value!\n"); if (TIFFGetField(in, TIFFTAG_BASELINEEXPOSURE, &tiffBaselineExposure)) { printf(" BaselineExposure: %d\n", tiffBaselineExposure); } else printf(" No BaselineExposure value!\n"); if (TIFFGetField(in, TIFFTAG_LINEARRESPONSELIMIT, &tiffLinearResponseLimit)) { printf(" LinearResponseLimit: %d\n", tiffLinearResponseLimit); } else printf(" No LinearResponseLimit value!\n"); uint16 tiffMinValue, tiffMaxValue; if (TIFFGetField(in, TIFFTAG_SMINSAMPLEVALUE, &tiffMinValue)) { printf(" MinValue: %d\n", tiffMinValue); } else printf(" No minimum value!\n"); if (TIFFGetField(in, TIFFTAG_SMAXSAMPLEVALUE, &tiffMaxValue)) { printf(" MaxValue: %d\n\n", tiffMaxValue); } else printf(" No maximum value!\n\n"); printf(" Those values are not taken into account, the image data are normalized to a [0;1] range\n\n"); } char* profdata; deleteLoadedProfileData(); loadedProfileDataJpg = false; if (TIFFGetField(in, TIFFTAG_ICCPROFILE, &loadedProfileLength, &profdata)) { embProfile = cmsOpenProfileFromMem (profdata, loadedProfileLength); loadedProfileData = new char [loadedProfileLength]; memcpy (loadedProfileData, profdata, loadedProfileLength); } else { embProfile = nullptr; } allocate (width, height); unsigned char* linebuffer = new unsigned char[TIFFScanlineSize(in) * (samplesperpixel == 1 ? 3 : 1)]; for (int row = 0; row < height; row++) { if (TIFFReadScanline(in, linebuffer, row, 0) < 0) { TIFFClose(in); delete [] linebuffer; return IMIO_READERROR; } if (samplesperpixel > 3) { for (int i = 0; i < width; i++) { memcpy (linebuffer + i * 3 * bitspersample / 8, linebuffer + i * samplesperpixel * bitspersample / 8, 3 * bitspersample / 8); } } else if (samplesperpixel == 1) { const size_t bytes = bitspersample / 8; for (int i = width - 1; i >= 0; --i) { const unsigned char* const src = linebuffer + i * bytes; unsigned char* const dest = linebuffer + i * 3 * bytes; memcpy(dest + 2 * bytes, src, bytes); memcpy(dest + 1 * bytes, src, bytes); memcpy(dest + 0 * bytes, src, bytes); } } setScanline (row, linebuffer, bitspersample, nullptr, nullptr); if (pl && !(row % 100)) { pl->setProgress ((double)(row + 1) / height); } } TIFFClose(in); delete [] linebuffer; if (pl) { pl->setProgressStr ("PROGRESSBAR_READY"); pl->setProgress (1.0); } return IMIO_SUCCESS; } int ImageIO::loadPPMFromMemory(const char* buffer, int width, int height, bool swap, int bps) { allocate (width, height); int line_length(width * 3 * (bps / 8)); if ( swap && bps > 8 ) { char swapped[line_length]; for ( int row = 0; row < height; ++row ) { ::rtengine::swab(((char*)buffer) + (row * line_length), swapped, line_length); setScanline(row, (unsigned char*)&swapped[0], bps); } } else { for ( int row = 0; row < height; ++row ) { setScanline(row, ((unsigned char*)buffer) + (row * line_length), bps); } } return IMIO_SUCCESS; } namespace { // Taken from Darktable -- src/imageio/format/png.c // /* Write EXIF data to PNG file. * Code copied from DigiKam's libs/dimg/loaders/pngloader.cpp. * The EXIF embedding is defined by ImageMagicK. * It is documented in the ExifTool page: * http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/PNG.html * * ..and in turn copied from ufraw. thanks to udi and colleagues * for making useful code much more readable and discoverable ;) */ void PNGwriteRawProfile(png_struct *ping, png_info *ping_info, const char *profile_type, guint8 *profile_data, png_uint_32 length) { png_textp text; long i; guint8 *sp; png_charp dp; png_uint_32 allocated_length, description_length; const guint8 hex[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; text = static_cast(png_malloc(ping, sizeof(png_text))); description_length = strlen(profile_type); allocated_length = length * 2 + (length >> 5) + 20 + description_length; text[0].text = static_cast(png_malloc(ping, allocated_length)); text[0].key = static_cast(png_malloc(ping, 80)); text[0].key[0] = '\0'; g_strlcat(text[0].key, "Raw profile type ", 80); g_strlcat(text[0].key, profile_type, 80); sp = profile_data; dp = text[0].text; *dp++ = '\n'; g_strlcpy(dp, profile_type, allocated_length); dp += description_length; *dp++ = '\n'; *dp = '\0'; g_snprintf(dp, allocated_length - strlen(text[0].text), "%8lu ", static_cast(length)); dp += 8; for(i = 0; i < long(length); i++) { if(i % 36 == 0) *dp++ = '\n'; *(dp++) = hex[((*sp >> 4) & 0x0f)]; *(dp++) = hex[((*sp++) & 0x0f)]; } *dp++ = '\n'; *dp = '\0'; text[0].text_length = (dp - text[0].text); text[0].compression = -1; if(text[0].text_length <= allocated_length) png_set_text(ping, ping_info, text, 1); png_free(ping, text[0].text); png_free(ping, text[0].key); png_free(ping, text); } } // namespace int ImageIO::savePNG (Glib::ustring fname, volatile int bps) { if (getWidth() < 1 || getHeight() < 1) { return IMIO_HEADERERROR; } FILE* const file = g_fopen_withBinaryAndLock (fname); if (!file) { return IMIO_CANNOTWRITEFILE; } if (pl) { pl->setProgressStr ("PROGRESSBAR_SAVEPNG"); pl->setProgress (0.0); } png_structp png = png_create_write_struct (PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); if (!png) { fclose (file); return IMIO_HEADERERROR; } // silence the warning about "invalid" sRGB profiles -- see #4260 #if defined(PNG_SKIP_sRGB_CHECK_PROFILE) && defined(PNG_SET_OPTION_SUPPORTED) png_set_option(png, PNG_SKIP_sRGB_CHECK_PROFILE, PNG_OPTION_ON); #endif png_infop info = png_create_info_struct(png); if (!info) { png_destroy_write_struct (&png, nullptr); fclose (file); return IMIO_HEADERERROR; } if (setjmp(png_jmpbuf(png))) { png_destroy_write_struct (&png, &info); fclose(file); return IMIO_CANNOTWRITEFILE; } png_set_write_fn (png, file, png_write_data, png_flush); png_set_filter(png, 0, PNG_FILTER_PAETH); png_set_compression_level(png, 6); png_set_compression_strategy(png, 3); int width = getWidth (); int height = getHeight (); if (bps < 0) { bps = getBPS (); } png_set_IHDR(png, info, width, height, bps, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_BASE); if (profileData) { #if PNG_LIBPNG_VER < 10500 png_charp profdata = reinterpret_cast(profileData); #else png_bytep profdata = reinterpret_cast(profileData); #endif png_set_iCCP(png, info, const_cast("icc"), 0, profdata, profileLength); } { // buffer for the exif and iptc unsigned int bufferSize; unsigned char* buffer = nullptr; // buffer will be allocated in createTIFFHeader unsigned char* iptcdata = nullptr; unsigned int iptclen = 0; if (iptc && iptc_data_save (iptc, &iptcdata, &iptclen) && iptcdata) { iptc_data_free_buf (iptc, iptcdata); iptcdata = nullptr; } int size = rtexif::ExifManager::createPNGMarker(exifRoot, exifChange, width, height, bps, (char*)iptcdata, iptclen, buffer, bufferSize); if (iptcdata) { iptc_data_free_buf (iptc, iptcdata); } if (buffer && size) { PNGwriteRawProfile(png, info, "exif", buffer, size); delete[] buffer; } } int rowlen = width * 3 * bps / 8; unsigned char *row = new unsigned char [rowlen]; png_write_info(png, info); for (int i = 0; i < height; i++) { getScanline (i, row, bps); if (bps == 16) { // convert to network byte order #if __BYTE_ORDER__==__ORDER_LITTLE_ENDIAN__ for (int j = 0; j < width * 6; j += 2) { unsigned char tmp = row[j]; row[j] = row[j + 1]; row[j + 1] = tmp; } #endif } png_write_row (png, (png_byte*)row); if (pl && !(i % 100)) { pl->setProgress ((double)(i + 1) / height); } } png_write_end(png, info); png_destroy_write_struct(&png, &info); delete [] row; fclose (file); if (pl) { pl->setProgressStr ("PROGRESSBAR_READY"); pl->setProgress (1.0); } return IMIO_SUCCESS; } // Quality 0..100, subsampling: 1=low quality, 2=medium, 3=high int ImageIO::saveJPEG (Glib::ustring fname, int quality, int subSamp) { if (getWidth() < 1 || getHeight() < 1) { return IMIO_HEADERERROR; } FILE* const file = g_fopen_withBinaryAndLock (fname); if (!file) { return IMIO_CANNOTWRITEFILE; } jpeg_compress_struct cinfo; /* We use our private extension JPEG error handler. Note that this struct must live as long as the main JPEG parameter struct, to avoid dangling-pointer problems. */ my_error_mgr jerr; /* We set up the normal JPEG error routines, then override error_exit. */ cinfo.err = jpeg_std_error(&jerr.pub); jerr.pub.error_exit = my_error_exit; /* Establish the setjmp return context for my_error_exit to use. */ #if defined( WIN32 ) && defined( __x86_64__ ) if (__builtin_setjmp(jerr.setjmp_buffer)) { #else if (setjmp(jerr.setjmp_buffer)) { #endif /* If we get here, the JPEG code has signaled an error. We need to clean up the JPEG object, close the file, remove the already saved part of the file and return. */ jpeg_destroy_compress(&cinfo); fclose(file); g_remove (fname.c_str()); return IMIO_CANNOTWRITEFILE; } jpeg_create_compress (&cinfo); if (pl) { pl->setProgressStr ("PROGRESSBAR_SAVEJPEG"); pl->setProgress (0.0); } jpeg_stdio_dest (&cinfo, file); int width = getWidth (); int height = getHeight (); cinfo.image_width = width; cinfo.image_height = height; cinfo.in_color_space = JCS_RGB; cinfo.input_components = 3; jpeg_set_defaults (&cinfo); cinfo.write_JFIF_header = FALSE; // compute optimal Huffman coding tables for the image. Bit slower to generate, but size of result image is a bit less (default was FALSE) cinfo.optimize_coding = TRUE; // Since math coprocessors are common these days, FLOAT should be a bit more accurate AND fast (default is ISLOW) // (machine dependency is not really an issue, since we all run on x86 and having exactly the same file is not a requirement) cinfo.dct_method = JDCT_FLOAT; if (quality >= 0 && quality <= 100) { jpeg_set_quality (&cinfo, quality, true); } cinfo.comp_info[1].h_samp_factor = cinfo.comp_info[1].v_samp_factor = 1; cinfo.comp_info[2].h_samp_factor = cinfo.comp_info[2].v_samp_factor = 1; if (subSamp == 1) { // Best compression, default of the JPEG library: 2x2, 1x1, 1x1 (4:2:0) cinfo.comp_info[0].h_samp_factor = cinfo.comp_info[0].v_samp_factor = 2; } else if (subSamp == 2) { // Widely used normal ratio 2x1, 1x1, 1x1 (4:2:2) cinfo.comp_info[0].h_samp_factor = 2; cinfo.comp_info[0].v_samp_factor = 1; } else if (subSamp == 3) { // Best quality 1x1 1x1 1x1 (4:4:4) cinfo.comp_info[0].h_samp_factor = cinfo.comp_info[0].v_samp_factor = 1; } jpeg_start_compress(&cinfo, TRUE); // buffer for exif and iptc markers unsigned char* buffer = new unsigned char[165535]; //FIXME: no buffer size check so it can be overflowed in createJPEGMarker() for large tags, and then software will crash unsigned int size; // assemble and write exif marker if (exifRoot) { int size = rtexif::ExifManager::createJPEGMarker (exifRoot, exifChange, cinfo.image_width, cinfo.image_height, buffer); if (size > 0 && size < 65530) { jpeg_write_marker(&cinfo, JPEG_APP0 + 1, buffer, size); } } // assemble and write iptc marker if (iptc) { unsigned char* iptcdata; bool error = false; if (iptc_data_save (iptc, &iptcdata, &size)) { if (iptcdata) { iptc_data_free_buf (iptc, iptcdata); } error = true; } int bytes = 0; if (!error && (bytes = iptc_jpeg_ps3_save_iptc (nullptr, 0, iptcdata, size, buffer, 65532)) < 0) { error = true; } if (iptcdata) { iptc_data_free_buf (iptc, iptcdata); } if (!error) { jpeg_write_marker(&cinfo, JPEG_APP0 + 13, buffer, bytes); } } delete [] buffer; // write icc profile to the output if (profileData) { write_icc_profile (&cinfo, (JOCTET*)profileData, profileLength); } // write image data int rowlen = width * 3; unsigned char *row = new unsigned char [rowlen]; /* To avoid memory leaks we establish a new setjmp return context for my_error_exit to use. */ #if defined( WIN32 ) && defined( __x86_64__ ) if (__builtin_setjmp(jerr.setjmp_buffer)) { #else if (setjmp(jerr.setjmp_buffer)) { #endif /* If we get here, the JPEG code has signaled an error. We need to clean up the JPEG object, close the file, remove the already saved part of the file and return. */ delete [] row; jpeg_destroy_compress(&cinfo); fclose(file); g_remove (fname.c_str()); return IMIO_CANNOTWRITEFILE; } while (cinfo.next_scanline < cinfo.image_height) { getScanline (cinfo.next_scanline, row, 8); if (jpeg_write_scanlines (&cinfo, &row, 1) < 1) { jpeg_destroy_compress (&cinfo); delete [] row; fclose (file); g_remove (fname.c_str()); return IMIO_CANNOTWRITEFILE; } if (pl && !(cinfo.next_scanline % 100)) { pl->setProgress ((double)(cinfo.next_scanline) / cinfo.image_height); } } jpeg_finish_compress (&cinfo); jpeg_destroy_compress (&cinfo); delete [] row; fclose (file); if (pl) { pl->setProgressStr ("PROGRESSBAR_READY"); pl->setProgress (1.0); } return IMIO_SUCCESS; } int ImageIO::saveTIFF (Glib::ustring fname, int bps, bool uncompressed) { if (getWidth() < 1 || getHeight() < 1) { return IMIO_HEADERERROR; } //TODO: Handling 32 bits floating point output images! bool writeOk = true; int width = getWidth (); int height = getHeight (); if (bps < 0) { bps = getBPS (); } int lineWidth = width * 3 * bps / 8; unsigned char* linebuffer = new unsigned char[lineWidth]; // little hack to get libTiff to use proper byte order (see TIFFClienOpen()): const char *mode = !exifRoot ? "w" : (exifRoot->getOrder() == rtexif::INTEL ? "wl" : "wb"); #ifdef WIN32 FILE *file = g_fopen_withBinaryAndLock (fname); int fileno = _fileno(file); int osfileno = _get_osfhandle(fileno); TIFF* out = TIFFFdOpen (osfileno, fname.c_str(), mode); #else TIFF* out = TIFFOpen(fname.c_str(), mode); int fileno = TIFFFileno (out); #endif if (!out) { delete [] linebuffer; return IMIO_CANNOTWRITEFILE; } if (pl) { pl->setProgressStr ("PROGRESSBAR_SAVETIFF"); pl->setProgress (0.0); } if (exifRoot) { rtexif::TagDirectory* cl = (const_cast (exifRoot))->clone (nullptr); // ------------------ remove some unknown top level tags which produce warnings when opening a tiff (might be useless) ----------------- rtexif::Tag *removeTag = cl->getTag (0x9003); if (removeTag) { removeTag->setKeep (false); } removeTag = cl->getTag (0x9211); if (removeTag) { removeTag->setKeep (false); } // ------------------ Apply list of change ----------------- for (auto currExifChange : exifChange) { cl->applyChange (currExifChange.first, currExifChange.second); } rtexif::Tag *tag = cl->getTag (TIFFTAG_EXIFIFD); if (tag && tag->isDirectory()) { rtexif::TagDirectory *exif = tag->getDirectory(); if (exif) { int exif_size = exif->calculateSize(); unsigned char *buffer = new unsigned char[exif_size + 8]; // TIFFOpen writes out the header and sets file pointer at position 8 exif->write (8, buffer); write (fileno, buffer + 8, exif_size); delete [] buffer; // let libtiff know that scanlines or any other following stuff should go // at a different offset: TIFFSetWriteOffset (out, exif_size + 8); TIFFSetField (out, TIFFTAG_EXIFIFD, 8); } } //TODO Even though we are saving EXIF IFD - MakerNote still comes out screwed. if ((tag = cl->getTag (TIFFTAG_MODEL)) != nullptr) { TIFFSetField (out, TIFFTAG_MODEL, tag->getValue()); } if ((tag = cl->getTag (TIFFTAG_MAKE)) != nullptr) { TIFFSetField (out, TIFFTAG_MAKE, tag->getValue()); } if ((tag = cl->getTag (TIFFTAG_DATETIME)) != nullptr) { TIFFSetField (out, TIFFTAG_DATETIME, tag->getValue()); } if ((tag = cl->getTag (TIFFTAG_ARTIST)) != nullptr) { TIFFSetField (out, TIFFTAG_ARTIST, tag->getValue()); } if ((tag = cl->getTag (TIFFTAG_COPYRIGHT)) != nullptr) { TIFFSetField (out, TIFFTAG_COPYRIGHT, tag->getValue()); } delete cl; } unsigned char* iptcdata = nullptr; unsigned int iptclen = 0; if (iptc && iptc_data_save (iptc, &iptcdata, &iptclen)) { if (iptcdata) { iptc_data_free_buf (iptc, iptcdata); iptcdata = nullptr; } } #if __BYTE_ORDER__==__ORDER_LITTLE_ENDIAN__ bool needsReverse = exifRoot && exifRoot->getOrder() == rtexif::MOTOROLA; #else bool needsReverse = exifRoot && exifRoot->getOrder() == rtexif::INTEL; #endif if (iptcdata) { rtexif::Tag iptcTag(nullptr, rtexif::lookupAttrib (rtexif::ifdAttribs, "IPTCData")); iptcTag.initLongArray((char*)iptcdata, iptclen); if (needsReverse) { unsigned char *ptr = iptcTag.getValue(); for (int a = 0; a < iptcTag.getCount(); ++a) { unsigned char cc; cc = ptr[3]; ptr[3] = ptr[0]; ptr[0] = cc; cc = ptr[2]; ptr[2] = ptr[1]; ptr[1] = cc; ptr += 4; } } TIFFSetField (out, TIFFTAG_RICHTIFFIPTC, iptcTag.getCount(), (long*)iptcTag.getValue()); iptc_data_free_buf (iptc, iptcdata); } TIFFSetField (out, TIFFTAG_SOFTWARE, "RawTherapee " RTVERSION); TIFFSetField (out, TIFFTAG_IMAGEWIDTH, width); TIFFSetField (out, TIFFTAG_IMAGELENGTH, height); TIFFSetField (out, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT); TIFFSetField (out, TIFFTAG_SAMPLESPERPIXEL, 3); TIFFSetField (out, TIFFTAG_ROWSPERSTRIP, height); TIFFSetField (out, TIFFTAG_BITSPERSAMPLE, bps); TIFFSetField (out, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); TIFFSetField (out, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB); TIFFSetField (out, TIFFTAG_COMPRESSION, uncompressed ? COMPRESSION_NONE : COMPRESSION_ADOBE_DEFLATE); TIFFSetField (out, TIFFTAG_SAMPLEFORMAT, bps == 32 ? SAMPLEFORMAT_IEEEFP : SAMPLEFORMAT_UINT); if (!uncompressed) { TIFFSetField (out, TIFFTAG_PREDICTOR, bps == 32 ? PREDICTOR_FLOATINGPOINT : PREDICTOR_HORIZONTAL); } if (profileData) { TIFFSetField (out, TIFFTAG_ICCPROFILE, profileLength, profileData); } for (int row = 0; row < height; row++) { getScanline (row, linebuffer, bps); if(needsReverse && !uncompressed && bps == 32) { for(int i = 0; i < lineWidth; i += 4) { char temp = linebuffer[i]; linebuffer[i] = linebuffer[i + 3]; linebuffer[i + 3] = temp; temp = linebuffer[i + 1]; linebuffer[i + 1] = linebuffer[i + 2]; linebuffer[i + 2] = temp; } } if (TIFFWriteScanline (out, linebuffer, row, 0) < 0) { TIFFClose (out); delete [] linebuffer; return IMIO_CANNOTWRITEFILE; } if (pl && !(row % 100)) { pl->setProgress ((double)(row + 1) / height); } } if (TIFFFlush(out) != 1) { writeOk = false; } TIFFClose (out); #ifdef WIN32 fclose (file); #endif delete [] linebuffer; if (pl) { pl->setProgressStr ("PROGRESSBAR_READY"); pl->setProgress (1.0); } if(writeOk) { return IMIO_SUCCESS; } else { g_remove (fname.c_str()); return IMIO_CANNOTWRITEFILE; } } // PNG read and write routines: void png_read_data(png_structp png_ptr, png_bytep data, png_size_t length) { png_size_t check; /* fread() returns 0 on error, so it is OK to store this in a png_size_t * instead of an int, which is what fread() actually returns. */ check = (png_size_t)fread(data, (png_size_t)1, length, (FILE *)png_get_io_ptr(png_ptr)); if (check != length) { png_error(png_ptr, "Read Error"); } } void png_write_data(png_structp png_ptr, png_bytep data, png_size_t length) { png_uint_32 check; check = fwrite(data, 1, length, (FILE *)png_get_io_ptr(png_ptr)); if (check != length) { png_error(png_ptr, "Write Error"); } } void png_flush(png_structp png_ptr) { FILE *io_ptr; io_ptr = (FILE *)(png_get_io_ptr(png_ptr)); if (io_ptr != nullptr) { fflush(io_ptr); } } int ImageIO::load (Glib::ustring fname) { if (hasPngExtension(fname)) { return loadPNG (fname); } else if (hasJpegExtension(fname)) { return loadJPEG (fname); } else if (hasTiffExtension(fname)) { return loadTIFF (fname); } else { return IMIO_FILETYPENOTSUPPORTED; } } int ImageIO::save (Glib::ustring fname) { if (hasPngExtension(fname)) { return savePNG (fname); } else if (hasJpegExtension(fname)) { return saveJPEG (fname); } else if (hasTiffExtension(fname)) { return saveTIFF (fname); } else { return IMIO_FILETYPENOTSUPPORTED; } }