/* * 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 "safegtk.h" #include "iptcpairs.h" #include "iccjpeg.h" #include "color.h" #include "jpeg.h" using namespace std; using namespace rtengine; using namespace rtengine::procparams; Glib::ustring safe_locale_to_utf8 (const std::string& src); 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!=NULL) { delete exifRoot; exifRoot = NULL; } if (eroot) { rtexif::TagDirectory* td = ((rtexif::TagDirectory*)eroot)->clone (NULL); // 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; /*unsigned int j=0; for (rtengine::procparams::ExifPairs::const_iterator i=exif.begin(); i!=exif.end(); i++) { exifChange.at(j).first = i->first; exifChange.at(j).second = i->second; j++; }*/ if (exifRoot!=NULL) { delete exifRoot; exifRoot = NULL; } if (eroot) exifRoot = ((rtexif::TagDirectory*)eroot)->clone (NULL); if (iptc!=NULL) { iptc_data_free (iptc); iptc = NULL; } // build iptc structures for libiptcdata if (iptcc.empty()) return; iptc = iptc_data_new (); 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; jsecond.size(); j++) { IptcDataSet * ds = iptc_dataset_new (); iptc_dataset_set_tag (ds, IPTC_RECORD_APP_2, IPTC_TAG_KEYWORDS); std::string loc = safe_locale_to_utf8(i->second.at(j)); iptc_dataset_set_data (ds, (unsigned char*)loc.c_str(), min(static_cast(64), loc.size()), 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; jsecond.size(); j++) { IptcDataSet * ds = iptc_dataset_new (); iptc_dataset_set_tag (ds, IPTC_RECORD_APP_2, IPTC_TAG_SUPPL_CATEGORY); std::string loc = safe_locale_to_utf8(i->second.at(j)); iptc_dataset_set_data (ds, (unsigned char*)loc.c_str(), min(static_cast(32), loc.size()), 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); std::string loc = safe_locale_to_utf8(i->second.at(0)); iptc_dataset_set_data (ds, (unsigned char*)loc.c_str(), min(strTags[j].size, loc.size()), IPTC_DONT_VALIDATE); iptc_data_add_dataset (iptc, ds); iptc_dataset_unref (ds); } } iptc_data_sort (iptc); } void ImageIO::setOutputProfile (char* pdata, int plen) { delete [] profileData; if (pdata) { profileData = new char [plen]; memcpy (profileData, pdata, plen); } else profileData = NULL; profileLength = plen; } ImageIO::~ImageIO () { if (embProfile) cmsCloseProfile(embProfile); delete [] loadedProfileData; 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 = safe_g_fopen (fname,"rb"); if (!file) return IMIO_CANNOTREADFILE; //reading PNG header unsigned char header[8]; fread (header, 1, 8, file); if (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, 0, 0, 0); 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 = safe_g_fopen (fname,"rb"); if (!file) return IMIO_CANNOTREADFILE; if (pl) { pl->setProgressStr ("PROGRESSBAR_LOADPNG"); pl->setProgress (0.0); } //reading PNG header unsigned char header[8]; fread (header, 1, 8, file); if (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, 0, 0, 0); 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); embProfile = NULL; //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); //converting to 32bpp format if (color_type==PNG_COLOR_TYPE_PALETTE) png_set_palette_to_rgb(png); 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 (interlace_type!=PNG_INTERLACE_NONE) { png_destroy_read_struct (&png, &info, &end_info); fclose (file); return IMIO_VARIANTNOTSUPPORTED; } if (color_type & PNG_COLOR_MASK_ALPHA) png_set_strip_alpha(png); //setting gamma double gamma; if (png_get_gAMA(png,info,&gamma)) png_set_gamma(png, 2.0, gamma); else png_set_gamma(png,2.0, 0.45455); // 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]; for (unsigned int i=0;isetProgress ((double)(i+1)/height); } png_read_end (png, 0); 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; } int ImageIO::loadJPEGFromMemory (const char* buffer, int bufsize) { jpeg_decompress_struct cinfo; jpeg_error_mgr jerr; cinfo.err = my_jpeg_std_error(&jerr); jpeg_create_decompress(&cinfo); jpeg_memory_src (&cinfo,(const JOCTET*)buffer,bufsize); 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_memory_src (&cinfo,buffer,bufsize); jpeg_read_header(&cinfo, TRUE); if( loadedProfileData ){ delete [] loadedProfileData; loadedProfileData = NULL; } bool hasprofile = read_icc_profile (&cinfo, (JOCTET**)&loadedProfileData, (unsigned int*)&loadedProfileLength); if (hasprofile) embProfile = cmsOpenProfileFromMem (loadedProfileData, loadedProfileLength); else embProfile = NULL; 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; } else { jpeg_destroy_decompress(&cinfo); return IMIO_READERROR; } } int ImageIO::loadJPEG (Glib::ustring fname) { FILE *file=safe_g_fopen(fname,"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 || cinfo.jpeg_color_space == JCS_GRAYSCALE) { jpeg_destroy_decompress(&cinfo); return IMIO_READERROR; } delete loadedProfileData; loadedProfileData = NULL; bool hasprofile = read_icc_profile (&cinfo, (JOCTET**)&loadedProfileData, (unsigned int*)&loadedProfileLength); if (hasprofile) embProfile = cmsOpenProfileFromMem (loadedProfileData, loadedProfileLength); else embProfile = NULL; 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 == NULL) 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) { if ((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 ((samplesperpixel==3 || samplesperpixel==4) && 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) { #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 == NULL) 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 * printf("Informations de \"%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("\n"); */ char* profdata; if( loadedProfileData ){ delete [] loadedProfileData; loadedProfileData = NULL; } if (TIFFGetField(in, TIFFTAG_ICCPROFILE, &loadedProfileLength, &profdata)) { embProfile = cmsOpenProfileFromMem (profdata, loadedProfileLength); // For 32 bits floating point images, gamma is forced to linear in embedded ICC profiles if ( sampleFormat&(IIOSF_LOGLUV24|IIOSF_LOGLUV32|IIOSF_FLOAT) ) { // Modifying the gammaTRG tags cmsWriteTag(embProfile, cmsSigGreenTRCTag, (void*)Color::linearGammaTRC ); cmsWriteTag(embProfile, cmsSigRedTRCTag, (void*)Color::linearGammaTRC ); cmsWriteTag(embProfile, cmsSigBlueTRCTag, (void*)Color::linearGammaTRC ); // Saving the profile in the memory cmsUInt32Number bytesNeeded = 0; cmsSaveProfileToMem(embProfile, 0, &bytesNeeded); if (bytesNeeded > 0) { loadedProfileData = new char[bytesNeeded+1]; cmsSaveProfileToMem(embProfile, loadedProfileData, &bytesNeeded); } loadedProfileLength = (int)bytesNeeded; } else { // Saving the profile in the memory as is loadedProfileData = new char [loadedProfileLength]; memcpy (loadedProfileData, profdata, loadedProfileLength); } } else embProfile = NULL; allocate (width, height); float minValue[3]={0.f, 0.f, 0.f}, maxValue[3]={0.f, 0.f, 0.f}; unsigned char* linebuffer = new unsigned char[TIFFScanlineSize(in)]; 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; isetProgress ((double)(row+1)/height); } if (sampleFormat & (IIOSF_FLOAT|IIOSF_LOGLUV24|IIOSF_LOGLUV32)) { #ifdef _DEBUG if (options.rtSettings.verbose) printf("Normalizing \"%s\" image \"%s\" whose mini/maxi values are:\n Red: minimum value=%0.5f / maximum value=%0.5f\n Green: minimum value=%0.5f / maximum value=%0.5f\n Blue: minimum value=%0.5f / maximum value=%0.5f\n", getType(), fname.c_str(), minValue[0], maxValue[0], minValue[1], maxValue[1], minValue[2], maxValue[2] ); #endif float minVal = min( min( minValue[0],minValue[1] ),minValue[2] ); float maxVal = max( max( maxValue[0],maxValue[1] ),maxValue[2] ); normalizeFloat(minVal, maxVal); } 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 ) { ::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; } int ImageIO::savePNG (Glib::ustring fname, int compression, volatile int bps) { FILE *file = safe_g_fopen_WriteBinLock (fname); if (!file) return IMIO_CANNOTREADFILE; if (pl) { pl->setProgressStr ("PROGRESSBAR_SAVEPNG"); pl->setProgress (0.0); } png_structp png = png_create_write_struct (PNG_LIBPNG_VER_STRING,0,0,0); if (!png) { fclose (file); return IMIO_HEADERERROR; } png_infop info = png_create_info_struct(png); if (!info) { png_destroy_write_struct (&png,0); fclose (file); return IMIO_HEADERERROR; } if (setjmp(png_jmpbuf(png))) { png_destroy_write_struct (&png,&info); fclose(file); return IMIO_READERROR; } png_set_write_fn (png, file, png_write_data, png_flush); png_set_compression_level(png,compression); int width = getW (); int height = getH (); 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); int rowlen = width*3*bps/8; unsigned char *row = new unsigned char [rowlen]; png_write_info(png,info); for (int i=0;isetProgress ((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) { jpeg_compress_struct cinfo; jpeg_error_mgr jerr; cinfo.err = jpeg_std_error (&jerr); jpeg_create_compress (&cinfo); FILE *file = safe_g_fopen_WriteBinLock (fname); if (!file) return IMIO_CANNOTREADFILE; if (pl) { pl->setProgressStr ("PROGRESSBAR_SAVEJPEG"); pl->setProgress (0.0); } jpeg_stdio_dest (&cinfo, file); int width = getW (); int height = getH (); 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:1:1) 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 (NULL, 0, iptcdata, size, buffer, 65532)) < 0) { if (iptcdata) iptc_data_free_buf (iptc, iptcdata); error = true; } if (!error) jpeg_write_marker(&cinfo, JPEG_APP0+13, buffer, bytes); } // 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]; while (cinfo.next_scanline < cinfo.image_height) { getScanline (cinfo.next_scanline, row, 8); if (jpeg_write_scanlines (&cinfo, &row, 1) < 1) { jpeg_finish_compress (&cinfo); jpeg_destroy_compress (&cinfo); fclose (file); return IMIO_READERROR; } 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; delete [] buffer; 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) { //TODO: Handling 32 bits floating point output images! int width = getW (); int height = getH (); if (bps<0) bps = getBPS (); int lineWidth = width*3*bps/8; unsigned char* linebuffer = new unsigned char[lineWidth]; // TODO the following needs to be looked into - do we really need two ways to write a Tiff file ? if (exifRoot && uncompressed) { FILE *file = safe_g_fopen_WriteBinLock (fname); if (!file) { delete [] linebuffer; return IMIO_CANNOTREADFILE; } if (pl) { pl->setProgressStr ("PROGRESSBAR_SAVETIFF"); pl->setProgress (0.0); } // buffer for the exif and iptc unsigned char* buffer = new unsigned char[165535]; //TODO: Is it really 165535... or 65535 ? unsigned char* iptcdata = NULL; unsigned int iptclen = 0; if (iptc && iptc_data_save (iptc, &iptcdata, &iptclen) && iptcdata) { iptc_data_free_buf (iptc, iptcdata); iptcdata = NULL; } int size = rtexif::ExifManager::createTIFFHeader (exifRoot, exifChange, width, height, bps, profileData, profileLength, (char*)iptcdata, iptclen, buffer); if (iptcdata) iptc_data_free_buf (iptc, iptcdata); // The maximum lenght is strangely not the same than for the JPEG file... // Which maximum length is the good one ? if (size>0 && size<165530) fwrite (buffer, size, 1, file); bool needsReverse = bps==16 && exifRoot->getOrder()==rtexif::MOTOROLA; for (int i=0; isetProgress ((double)(i+1)/height); } delete [] buffer; fclose (file); } else { // 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 wchar_t *wfilename = (wchar_t*)g_utf8_to_utf16 (fname.c_str(), -1, NULL, NULL, NULL); TIFF* out = TIFFOpenW (wfilename, mode); g_free (wfilename); #else TIFF* out = TIFFOpen(fname.c_str(), mode); #endif if (!out) { delete [] linebuffer; return IMIO_CANNOTREADFILE; } if (pl) { pl->setProgressStr ("PROGRESSBAR_SAVETIFF"); pl->setProgress (0.0); } if (exifRoot){ rtexif::Tag *tag = exifRoot->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 (TIFFFileno (out), 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 = exifRoot->getTag (TIFFTAG_MODEL)) != NULL) TIFFSetField (out, TIFFTAG_MODEL, tag->getValue()); if ((tag = exifRoot->getTag (TIFFTAG_MAKE)) != NULL) TIFFSetField (out, TIFFTAG_MAKE, tag->getValue()); if ((tag = exifRoot->getTag (TIFFTAG_DATETIME)) != NULL) TIFFSetField (out, TIFFTAG_DATETIME, tag->getValue()); if ((tag = exifRoot->getTag (TIFFTAG_ARTIST)) != NULL) TIFFSetField (out, TIFFTAG_ARTIST, tag->getValue()); if ((tag = exifRoot->getTag (TIFFTAG_COPYRIGHT)) != NULL) TIFFSetField (out, TIFFTAG_COPYRIGHT, tag->getValue()); } Glib::ustring rtVersion("RawTherapee "); rtVersion += VERSION; TIFFSetField (out, TIFFTAG_SOFTWARE, rtVersion.c_str()); 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_SAMPLEFORMAT, SAMPLEFORMAT_UINT); TIFFSetField (out, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB); TIFFSetField (out, TIFFTAG_COMPRESSION, uncompressed ? COMPRESSION_NONE : COMPRESSION_DEFLATE); if (!uncompressed) TIFFSetField (out, TIFFTAG_PREDICTOR, PREDICTOR_NONE); if (profileData) TIFFSetField (out, TIFFTAG_ICCPROFILE, profileLength, profileData); for (int row = 0; row < height; row++) { getScanline (row, linebuffer, bps); if (TIFFWriteScanline (out, linebuffer, row, 0) < 0) { TIFFClose (out); delete [] linebuffer; return IMIO_READERROR; } if (pl && !(row%100)) pl->setProgress ((double)(row+1)/height); } TIFFClose (out); } delete [] linebuffer; if (pl) { pl->setProgressStr ("PROGRESSBAR_READY"); pl->setProgress (1.0); } return IMIO_SUCCESS; } // 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 != NULL) fflush(io_ptr); } int ImageIO::load (Glib::ustring fname) { size_t lastdot = fname.find_last_of ('.'); if( Glib::ustring::npos == lastdot ) return IMIO_FILETYPENOTSUPPORTED; if (!fname.casefold().compare (lastdot, 4, ".png")) return loadPNG (fname); else if (!fname.casefold().compare (lastdot, 4, ".jpg") || !fname.casefold().compare (lastdot, 5, ".jpeg")) return loadJPEG (fname); else if (!fname.casefold().compare (lastdot, 4, ".tif") || !fname.casefold().compare (lastdot, 5, ".tiff")) return loadTIFF (fname); else return IMIO_FILETYPENOTSUPPORTED; } int ImageIO::save (Glib::ustring fname) { size_t lastdot = fname.find_last_of ('.'); if( Glib::ustring::npos == lastdot ) return IMIO_FILETYPENOTSUPPORTED; if (!fname.casefold().compare (lastdot, 4, ".png")) return savePNG (fname); else if (!fname.casefold().compare (lastdot, 4, ".jpg") || !fname.casefold().compare (lastdot, 5, ".jpeg")) return saveJPEG (fname); else if (!fname.casefold().compare (lastdot, 4, ".tif") || !fname.casefold().compare (lastdot, 5, ".tiff")) return saveTIFF (fname); else return IMIO_FILETYPENOTSUPPORTED; }