From 5ac11ef1b95aec7d02ef3fbca4845682d5734425 Mon Sep 17 00:00:00 2001 From: Hombre Date: Sun, 6 May 2018 18:50:42 +0200 Subject: [PATCH 1/4] Enable saving 32 bits floating point TIFF image with rt-cli (see #2357) --- rtengine/imageio.cc | 3 +++ rtgui/main-cli.cc | 10 ++++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/rtengine/imageio.cc b/rtengine/imageio.cc index 1d07fbc84..cc126a3b3 100644 --- a/rtengine/imageio.cc +++ b/rtengine/imageio.cc @@ -1026,6 +1026,9 @@ int ImageIO::savePNG (Glib::ustring fname, volatile int bps) if (bps < 0) { bps = getBPS (); } + if (bps > 16) { + bps = 16; + } png_set_IHDR(png, info, width, height, bps, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_BASE); diff --git a/rtgui/main-cli.cc b/rtgui/main-cli.cc index 6643b0913..daa709696 100644 --- a/rtgui/main-cli.cc +++ b/rtgui/main-cli.cc @@ -391,8 +391,8 @@ int processLineParams ( int argc, char **argv ) case 'b': bits = atoi (currParam.substr (2).c_str()); - if (bits != 8 && bits != 16) { - std::cerr << "Error: specify -b8 for 8-bit or -b16 for 16-bit output." << std::endl; + if (bits != 8 && bits != 16 && bits != 32) { + std::cerr << "Error: specify -b8 for 8-bit/integer or -b16 for 16-bit/integer or -b32 for 32-bit/float output." << std::endl; deleteProcParams (processingParams); return -3; } @@ -550,8 +550,10 @@ int processLineParams ( int argc, char **argv ) std::cout << " Chroma halved horizontally." << std::endl; std::cout << " 3 = Best quality: 1x1, 1x1, 1x1 (4:4:4)" << std::endl; std::cout << " No chroma subsampling." << std::endl; - std::cout << " -b<8|16> Specify bit depth per channel (default value: 16 for TIFF, 8 for PNG)." << std::endl; - std::cout << " Only applies to TIFF and PNG output, JPEG is always 8." << std::endl; + std::cout << " -b<8|16|32> Specify bit depth per channel (default value: 16 for TIFF, 8 for PNG)." << std::endl; + std::cout << " TIFF can be 8-bit/int, 16-bit/int or 32-bit/float" << std::endl; + std::cout << " PNG can be 8-bit/int or 16-bit/int." << std::endl; + std::cout << " JPEG is only 8-bit/int." << std::endl; std::cout << " -t[z] Specify output to be TIFF." << std::endl; std::cout << " Uncompressed by default, or deflate compression with 'z'." << std::endl; std::cout << " -n Specify output to be compressed PNG." << std::endl; From 30efa5930da20985d47c6706f2801245f68798af Mon Sep 17 00:00:00 2001 From: Hombre Date: Mon, 7 May 2018 09:57:53 +0200 Subject: [PATCH 2/4] 16-bit floating-point support for TIFF output image (see #2357) --- rtdata/languages/Francais | 1 + rtdata/languages/default | 1 + rtengine/iimage.h | 3 ++- rtengine/image16.h | 4 ++-- rtengine/image8.h | 4 ++-- rtengine/imagefloat.h | 4 ++-- rtengine/imageio.cc | 33 +++++++++++++++++++++------------ rtengine/imageio.h | 2 +- rtgui/batchqueue.cc | 8 +++++--- rtgui/batchqueueentry.cc | 5 +++-- rtgui/editorpanel.cc | 9 +++++---- rtgui/main-cli.cc | 17 +++++++++++------ rtgui/options.cc | 12 ++++++++++++ rtgui/options.h | 2 ++ rtgui/saveformatpanel.cc | 18 ++++++++++++------ rtgui/saveformatpanel.h | 2 +- 16 files changed, 83 insertions(+), 42 deletions(-) diff --git a/rtdata/languages/Francais b/rtdata/languages/Francais index 1553c0347..b28545648 100644 --- a/rtdata/languages/Francais +++ b/rtdata/languages/Francais @@ -1216,6 +1216,7 @@ SAMPLEFORMAT_8;LogLuv 32 bits SAMPLEFORMAT_16;32 bits à virgule flottante SAVEDLG_AUTOSUFFIX;Ajouter automatiquement un suffixe si le fichier existe déjà SAVEDLG_FILEFORMAT;Format de fichier +SAVEDLG_FILEFORMAT_FLOAT; virgule flottante SAVEDLG_FORCEFORMATOPTS;Forcer les options d'enregistrement SAVEDLG_JPEGQUAL;Qualité JPEG SAVEDLG_PNGCOMPR;Compression PNG diff --git a/rtdata/languages/default b/rtdata/languages/default index 2e1b6cca8..2a5f53505 100644 --- a/rtdata/languages/default +++ b/rtdata/languages/default @@ -1219,6 +1219,7 @@ SAMPLEFORMAT_8;LogLuv 32 bits SAMPLEFORMAT_16;32 bits floating point SAVEDLG_AUTOSUFFIX;Automatically add a suffix if the file already exists SAVEDLG_FILEFORMAT;File format +SAVEDLG_FILEFORMAT_FLOAT; floating-point SAVEDLG_FORCEFORMATOPTS;Force saving options SAVEDLG_JPEGQUAL;JPEG quality SAVEDLG_PUTTOQUEUE;Put into processing queue diff --git a/rtengine/iimage.h b/rtengine/iimage.h index d09f46a3a..cd22a2559 100644 --- a/rtengine/iimage.h +++ b/rtengine/iimage.h @@ -1765,8 +1765,9 @@ public: /** @brief Saves the image to file in a tif format. * @param fname is the name of the file * @param bps can be 8 or 16 depending on the bits per pixels the output file will have + * @param isFloat is true for saving float images. Will be ignored by file format not supporting float data @return the error code, 0 if none */ - virtual int saveAsTIFF (Glib::ustring fname, int bps = -1, bool uncompressed = false) = 0; + virtual int saveAsTIFF (Glib::ustring fname, int bps = -1, float isFloat = false, bool uncompressed = false) = 0; /** @brief Sets the progress listener if you want to follow the progress of the image saving operations (optional). * @param pl is the pointer to the class implementing the ProgressListener interface */ virtual void setSaveProgressListener (ProgressListener* pl) = 0; diff --git a/rtengine/image16.h b/rtengine/image16.h index af5642638..33e74f146 100644 --- a/rtengine/image16.h +++ b/rtengine/image16.h @@ -83,9 +83,9 @@ public: { return saveJPEG (fname, quality, subSamp); } - virtual int saveAsTIFF (Glib::ustring fname, int bps = -1, bool uncompressed = false) + virtual int saveAsTIFF (Glib::ustring fname, int bps = -1, float isFloat = false, bool uncompressed = false) { - return saveTIFF (fname, bps, uncompressed); + return saveTIFF (fname, bps, isFloat, uncompressed); } virtual void setSaveProgressListener (ProgressListener* pl) { diff --git a/rtengine/image8.h b/rtengine/image8.h index eafaa2d46..6444499d9 100644 --- a/rtengine/image8.h +++ b/rtengine/image8.h @@ -78,9 +78,9 @@ public: { return saveJPEG (fname, quality, subSamp); } - virtual int saveAsTIFF (Glib::ustring fname, int bps = -1, bool uncompressed = false) + virtual int saveAsTIFF (Glib::ustring fname, int bps = -1, float isFloat = false, bool uncompressed = false) { - return saveTIFF (fname, bps, uncompressed); + return saveTIFF (fname, bps, isFloat, uncompressed); } virtual void setSaveProgressListener (ProgressListener* pl) { diff --git a/rtengine/imagefloat.h b/rtengine/imagefloat.h index 5246a2f6e..abb74bc8e 100644 --- a/rtengine/imagefloat.h +++ b/rtengine/imagefloat.h @@ -87,9 +87,9 @@ public: { return saveJPEG (fname, quality, subSamp); } - virtual int saveAsTIFF (Glib::ustring fname, int bps = -1, bool uncompressed = false) + virtual int saveAsTIFF (Glib::ustring fname, int bps = -1, float isFloat = false, bool uncompressed = false) { - return saveTIFF (fname, bps, uncompressed); + return saveTIFF (fname, bps, isFloat, uncompressed); } virtual void setSaveProgressListener (ProgressListener* pl) { diff --git a/rtengine/imageio.cc b/rtengine/imageio.cc index cc126a3b3..c965d182a 100644 --- a/rtengine/imageio.cc +++ b/rtengine/imageio.cc @@ -1300,13 +1300,12 @@ int ImageIO::saveJPEG (Glib::ustring fname, int quality, int subSamp) return IMIO_SUCCESS; } -int ImageIO::saveTIFF (Glib::ustring fname, int bps, bool uncompressed) +int ImageIO::saveTIFF (Glib::ustring fname, int bps, float isFloat, 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 (); @@ -1458,10 +1457,10 @@ int ImageIO::saveTIFF (Glib::ustring fname, int bps, bool uncompressed) 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); + TIFFSetField (out, TIFFTAG_SAMPLEFORMAT, (bps == 16 || bps == 32) && isFloat ? SAMPLEFORMAT_IEEEFP : SAMPLEFORMAT_UINT); if (!uncompressed) { - TIFFSetField (out, TIFFTAG_PREDICTOR, bps == 32 ? PREDICTOR_FLOATINGPOINT : PREDICTOR_HORIZONTAL); + TIFFSetField (out, TIFFTAG_PREDICTOR, (bps == 16 || bps == 32) && isFloat ? PREDICTOR_FLOATINGPOINT : PREDICTOR_HORIZONTAL); } if (profileData) { TIFFSetField (out, TIFFTAG_ICCPROFILE, profileLength, profileData); @@ -1470,14 +1469,24 @@ int ImageIO::saveTIFF (Glib::ustring fname, int bps, bool uncompressed) 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 (bps == 16) { + if(needsReverse && !uncompressed) { + for(int i = 0; i < lineWidth; i += 2) { + char temp = linebuffer[i]; + linebuffer[i] = linebuffer[i + 1]; + linebuffer[i + 1] = temp; + } + } + } else */ if (bps == 32) { + if(needsReverse && !uncompressed) { + 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; + } } } diff --git a/rtengine/imageio.h b/rtengine/imageio.h index 294a3d476..135927bbf 100644 --- a/rtengine/imageio.h +++ b/rtengine/imageio.h @@ -138,7 +138,7 @@ public: int savePNG (Glib::ustring fname, volatile int bps = -1); int saveJPEG (Glib::ustring fname, int quality = 100, int subSamp = 3); - int saveTIFF (Glib::ustring fname, int bps = -1, bool uncompressed = false); + int saveTIFF (Glib::ustring fname, int bps = -1, float isFloat = false, bool uncompressed = false); cmsHPROFILE getEmbeddedProfile () { diff --git a/rtgui/batchqueue.cc b/rtgui/batchqueue.cc index 2f3505a08..10105e117 100644 --- a/rtgui/batchqueue.cc +++ b/rtgui/batchqueue.cc @@ -228,7 +228,7 @@ bool BatchQueue::saveBatchQueue () // The column's header is mandatory (the first line will be skipped when loaded) file << "input image full path|param file full path|output image full path|file format|jpeg quality|jpeg subsampling|" - << "png bit depth|png compression|tiff bit depth|uncompressed tiff|save output params|force format options|fast export|" + << "png bit depth|png compression|tiff bit depth|tiff is float|uncompressed tiff|save output params|force format options|fast export|" << std::endl; // method is already running with entryLock, so no need to lock again @@ -246,7 +246,7 @@ bool BatchQueue::saveBatchQueue () #endif << saveFormat.jpegQuality << '|' << saveFormat.jpegSubSamp << '|' << saveFormat.pngBits << '|' - << saveFormat.tiffBits << '|' << saveFormat.tiffUncompressed << '|' + << saveFormat.tiffBits << '|' << (saveFormat.tiffFloat ? 1 : 0) << '|' << saveFormat.tiffUncompressed << '|' << saveFormat.saveParams << '|' << entry->forceFormatOpts << '|' << entry->fast_pipeline << '|' << std::endl; @@ -311,6 +311,7 @@ bool BatchQueue::loadBatchQueue () const auto jpegSubSamp = nextIntOr (options.saveFormat.jpegSubSamp); const auto pngBits = nextIntOr (options.saveFormat.pngBits); const auto tiffBits = nextIntOr (options.saveFormat.tiffBits); + const auto tiffFloat = nextIntOr (options.saveFormat.tiffFloat); const auto tiffUncompressed = nextIntOr (options.saveFormat.tiffUncompressed); const auto saveParams = nextIntOr (options.saveFormat.saveParams); const auto forceFormatOpts = nextIntOr (options.forceFormatOpts); @@ -352,6 +353,7 @@ bool BatchQueue::loadBatchQueue () saveFormat.jpegSubSamp = jpegSubSamp; saveFormat.pngBits = pngBits; saveFormat.tiffBits = tiffBits; + saveFormat.tiffFloat = tiffFloat == 1; saveFormat.tiffUncompressed = tiffUncompressed != 0; saveFormat.saveParams = saveParams != 0; entry->forceFormatOpts = forceFormatOpts != 0; @@ -608,7 +610,7 @@ rtengine::ProcessingJob* BatchQueue::imageReady (rtengine::IImagefloat* img) int err = 0; if (saveFormat.format == "tif") { - err = img->saveAsTIFF (fname, saveFormat.tiffBits, saveFormat.tiffUncompressed); + err = img->saveAsTIFF (fname, saveFormat.tiffBits, saveFormat.tiffFloat, saveFormat.tiffUncompressed); } else if (saveFormat.format == "png") { err = img->saveAsPNG (fname, saveFormat.pngBits); } else if (saveFormat.format == "jpg") { diff --git a/rtgui/batchqueueentry.cc b/rtgui/batchqueueentry.cc index 3092d6db4..d886abac6 100644 --- a/rtgui/batchqueueentry.cc +++ b/rtgui/batchqueueentry.cc @@ -175,9 +175,10 @@ Glib::ustring BatchQueueEntry::getToolTip (int x, int y) tooltip += Glib::ustring::compose("\n\n%1: %2", M("BATCHQUEUE_DESTFILENAME"), outFileName); if (forceFormatOpts) { - tooltip += Glib::ustring::compose("\n\n%1: %2 (%3 bits)", M("SAVEDLG_FILEFORMAT"), saveFormat.format, + tooltip += Glib::ustring::compose("\n\n%1: %2 (%3-bits%4)", M("SAVEDLG_FILEFORMAT"), saveFormat.format, saveFormat.format == "png" ? saveFormat.pngBits : - saveFormat.format == "tif" ? saveFormat.tiffBits : 8); + saveFormat.format == "tif" ? saveFormat.tiffBits : 8, + saveFormat.format == "tif" && saveFormat.tiffFloat ? M("SAVEDLG_FILEFORMAT_FLOAT") : ""); if (saveFormat.format == "jpg") { tooltip += Glib::ustring::compose("\n%1: %2\n%3: %4", diff --git a/rtgui/editorpanel.cc b/rtgui/editorpanel.cc index 76bf2794a..30c8b4c59 100644 --- a/rtgui/editorpanel.cc +++ b/rtgui/editorpanel.cc @@ -1763,7 +1763,7 @@ bool EditorPanel::idle_saveImage (ProgressConnector *pc, img->setSaveProgressListener (parent->getProgressListener()); if (sf.format == "tif") - ld->startFunc (sigc::bind (sigc::mem_fun (img, &rtengine::IImagefloat::saveAsTIFF), fname, sf.tiffBits, sf.tiffUncompressed), + ld->startFunc (sigc::bind (sigc::mem_fun (img, &rtengine::IImagefloat::saveAsTIFF), fname, sf.tiffBits, sf.tiffFloat, sf.tiffUncompressed), sigc::bind (sigc::mem_fun (*this, &EditorPanel::idle_imageSaved), ld, img, fname, sf, pparams)); else if (sf.format == "png") ld->startFunc (sigc::bind (sigc::mem_fun (img, &rtengine::IImagefloat::saveAsPNG), fname, sf.pngBits), @@ -1980,9 +1980,9 @@ bool EditorPanel::saveImmediately (const Glib::ustring &filename, const SaveForm int err = 0; if (gimpPlugin) { - err = img->saveAsTIFF (filename, 32, true); + err = img->saveAsTIFF (filename, 32, true, true); } else if (sf.format == "tif") { - err = img->saveAsTIFF (filename, sf.tiffBits, sf.tiffUncompressed); + err = img->saveAsTIFF (filename, sf.tiffBits, sf.tiffFloat, sf.tiffUncompressed); } else if (sf.format == "png") { err = img->saveAsPNG (filename, sf.pngBits); } else if (sf.format == "jpg") { @@ -2038,6 +2038,7 @@ bool EditorPanel::idle_sendToGimp ( ProgressConnector *p SaveFormat sf; sf.format = "tif"; sf.tiffBits = 16; + sf.tiffFloat = false; sf.tiffUncompressed = true; sf.saveParams = true; @@ -2058,7 +2059,7 @@ bool EditorPanel::idle_sendToGimp ( ProgressConnector *p ProgressConnector *ld = new ProgressConnector(); img->setSaveProgressListener (parent->getProgressListener()); - ld->startFunc (sigc::bind (sigc::mem_fun (img, &rtengine::IImagefloat::saveAsTIFF), fileName, sf.tiffBits, sf.tiffUncompressed), + ld->startFunc (sigc::bind (sigc::mem_fun (img, &rtengine::IImagefloat::saveAsTIFF), fileName, sf.tiffBits, sf.tiffFloat, sf.tiffUncompressed), sigc::bind (sigc::mem_fun (*this, &EditorPanel::idle_sentToGimp), ld, img, fileName)); } else { Glib::ustring msg_ = Glib::ustring (" Error during image processing\n"); diff --git a/rtgui/main-cli.cc b/rtgui/main-cli.cc index daa709696..c480c7213 100644 --- a/rtgui/main-cli.cc +++ b/rtgui/main-cli.cc @@ -267,6 +267,7 @@ int processLineParams ( int argc, char **argv ) int compression = 92; int subsampling = 3; int bits = -1; + bool isFloat = false; std::string outputType = ""; unsigned errors = 0; @@ -392,11 +393,14 @@ int processLineParams ( int argc, char **argv ) bits = atoi (currParam.substr (2).c_str()); if (bits != 8 && bits != 16 && bits != 32) { - std::cerr << "Error: specify -b8 for 8-bit/integer or -b16 for 16-bit/integer or -b32 for 32-bit/float output." << std::endl; + std::cerr << "Error: specify -b8 for 8-bit/integer or -b16 for 16-bit/integer or -b16f for 16-bit/float or -b32 for 32-bit/float output." << std::endl; deleteProcParams (processingParams); return -3; } + isFloat = (bits == 16 && currParam.length() == 3 && currParam.at(2) == 'f') || bits == 32; + printf("Float output detected (%d bits)!\n", bits); + break; case 't': @@ -550,10 +554,11 @@ int processLineParams ( int argc, char **argv ) std::cout << " Chroma halved horizontally." << std::endl; std::cout << " 3 = Best quality: 1x1, 1x1, 1x1 (4:4:4)" << std::endl; std::cout << " No chroma subsampling." << std::endl; - std::cout << " -b<8|16|32> Specify bit depth per channel (default value: 16 for TIFF, 8 for PNG)." << std::endl; - std::cout << " TIFF can be 8-bit/int, 16-bit/int or 32-bit/float" << std::endl; - std::cout << " PNG can be 8-bit/int or 16-bit/int." << std::endl; - std::cout << " JPEG is only 8-bit/int." << std::endl; + std::cout << " -b<8|16|16f|32> Specify bit depth per channel (default value: 16 for TIFF, 8 for PNG and JPEG)." << std::endl; + std::cout << " 8 = 8 bits integer, applies to JPEG, PNG and TIFF" << std::endl; + std::cout << " 16 = 16 bits integer, applies to PNG and TIFF" << std::endl; + std::cout << " 16f = 16 bits float, applies to TIFF" << std::endl; + std::cout << " 32 = 32 bits float, applies to TIFF" << std::endl; std::cout << " -t[z] Specify output to be TIFF." << std::endl; std::cout << " Uncompressed by default, or deflate compression with 'z'." << std::endl; std::cout << " -n Specify output to be compressed PNG." << std::endl; @@ -781,7 +786,7 @@ int processLineParams ( int argc, char **argv ) if ( outputType == "jpg" ) { errorCode = resultImage->saveAsJPEG ( outputFile, compression, subsampling ); } else if ( outputType == "tif" ) { - errorCode = resultImage->saveAsTIFF ( outputFile, bits, compression == 0 ); + errorCode = resultImage->saveAsTIFF ( outputFile, bits, isFloat, compression == 0 ); } else if ( outputType == "png" ) { errorCode = resultImage->saveAsPNG ( outputFile, bits ); } else { diff --git a/rtgui/options.cc b/rtgui/options.cc index 1dbaa574e..0c95cc7ae 100644 --- a/rtgui/options.cc +++ b/rtgui/options.cc @@ -298,6 +298,7 @@ void Options::setDefaults () saveFormat.jpegSubSamp = 2; saveFormat.pngBits = 8; saveFormat.tiffBits = 16; + saveFormat.tiffFloat = false; saveFormat.tiffUncompressed = true; saveFormat.saveParams = true; @@ -306,6 +307,7 @@ void Options::setDefaults () saveFormatBatch.jpegSubSamp = 2; saveFormatBatch.pngBits = 8; saveFormatBatch.tiffBits = 16; + saveFormatBatch.tiffFloat = false; saveFormatBatch.tiffUncompressed = true; saveFormatBatch.saveParams = true; @@ -736,6 +738,10 @@ void Options::readFromFile (Glib::ustring fname) saveFormat.tiffBits = keyFile.get_integer ("Output", "TiffBps"); } + if (keyFile.has_key ("Output", "TiffFloat")) { + saveFormat.tiffFloat = keyFile.get_boolean ("Output", "TiffFloat"); + } + if (keyFile.has_key ("Output", "TiffUncompressed")) { saveFormat.tiffUncompressed = keyFile.get_boolean ("Output", "TiffUncompressed"); } @@ -765,6 +771,10 @@ void Options::readFromFile (Glib::ustring fname) saveFormatBatch.tiffBits = keyFile.get_integer ("Output", "TiffBpsBatch"); } + if (keyFile.has_key ("Output", "TiffFloatBatch")) { + saveFormatBatch.tiffFloat = keyFile.get_boolean ("Output", "TiffFloatBatch"); + } + if (keyFile.has_key ("Output", "TiffUncompressedBatch")) { saveFormatBatch.tiffUncompressed = keyFile.get_boolean ("Output", "TiffUncompressedBatch"); } @@ -1852,6 +1862,7 @@ void Options::saveToFile (Glib::ustring fname) keyFile.set_integer ("Output", "JpegSubSamp", saveFormat.jpegSubSamp); keyFile.set_integer ("Output", "PngBps", saveFormat.pngBits); keyFile.set_integer ("Output", "TiffBps", saveFormat.tiffBits); + keyFile.set_boolean ("Output", "TiffFloat", saveFormat.tiffFloat); keyFile.set_boolean ("Output", "TiffUncompressed", saveFormat.tiffUncompressed); keyFile.set_boolean ("Output", "SaveProcParams", saveFormat.saveParams); @@ -1860,6 +1871,7 @@ void Options::saveToFile (Glib::ustring fname) keyFile.set_integer ("Output", "JpegSubSampBatch", saveFormatBatch.jpegSubSamp); keyFile.set_integer ("Output", "PngBpsBatch", saveFormatBatch.pngBits); keyFile.set_integer ("Output", "TiffBpsBatch", saveFormatBatch.tiffBits); + keyFile.set_boolean ("Output", "TiffFloatBatch", saveFormatBatch.tiffFloat); keyFile.set_boolean ("Output", "TiffUncompressedBatch", saveFormatBatch.tiffUncompressed); keyFile.set_boolean ("Output", "SaveProcParamsBatch", saveFormatBatch.saveParams); diff --git a/rtgui/options.h b/rtgui/options.h index 25fa31b6d..4695b0443 100644 --- a/rtgui/options.h +++ b/rtgui/options.h @@ -50,6 +50,7 @@ struct SaveFormat { jpegQuality (90), jpegSubSamp (2), tiffBits (8), + tiffFloat(false), tiffUncompressed (true), saveParams (true) { @@ -60,6 +61,7 @@ struct SaveFormat { int jpegQuality; int jpegSubSamp; // 1=best compression, 3=best quality int tiffBits; + bool tiffFloat; bool tiffUncompressed; bool saveParams; }; diff --git a/rtgui/saveformatpanel.cc b/rtgui/saveformatpanel.cc index a14a7359c..2a6d5cad1 100644 --- a/rtgui/saveformatpanel.cc +++ b/rtgui/saveformatpanel.cc @@ -40,6 +40,7 @@ SaveFormatPanel::SaveFormatPanel () : listener (nullptr) format->append ("JPEG (8-bit)"); format->append ("TIFF (8-bit)"); format->append ("TIFF (16-bit)"); + format->append ("TIFF (16-bit float)"); format->append ("TIFF (32-bit float)"); format->append ("PNG (8-bit)"); format->append ("PNG (16-bit)"); @@ -48,8 +49,9 @@ SaveFormatPanel::SaveFormatPanel () : listener (nullptr) fstr[1] = "tif"; fstr[2] = "tif"; fstr[3] = "tif"; - fstr[4] = "png"; + fstr[4] = "tif"; fstr[5] = "png"; + fstr[6] = "png"; hb1->attach (*flab, 0, 0, 1, 1); hb1->attach (*format, 1, 0, 1, 1); @@ -123,10 +125,12 @@ void SaveFormatPanel::init (SaveFormat &sf) if (sf.format == "jpg") { format->set_active (0); } else if (sf.format == "png" && sf.pngBits == 16) { - format->set_active (5); + format->set_active (6); } else if (sf.format == "png" && sf.pngBits == 8) { - format->set_active (4); + format->set_active (5); } else if (sf.format == "tif" && sf.tiffBits == 32) { + format->set_active (4); + } else if (sf.format == "tif" && sf.tiffBits == 16 && sf.tiffFloat) { format->set_active (3); } else if (sf.format == "tif" && sf.tiffBits == 16) { format->set_active (2); @@ -150,20 +154,22 @@ SaveFormat SaveFormatPanel::getFormat () int sel = format->get_active_row_number(); sf.format = fstr[sel]; - if (sel == 5) { + if (sel == 6) { sf.pngBits = 16; } else { sf.pngBits = 8; } - if (sel == 2) { + if (sel == 2 || sel == 3) { sf.tiffBits = 16; - } else if (sel == 3) { + } else if (sel == 4) { sf.tiffBits = 32; } else { sf.tiffBits = 8; } + sf.tiffFloat = sel == 4 || sel == 3; + sf.jpegQuality = (int) jpegQual->getValue (); sf.jpegSubSamp = jpegSubSamp->get_active_row_number() + 1; sf.tiffUncompressed = tiffUncompressed->get_active(); diff --git a/rtgui/saveformatpanel.h b/rtgui/saveformatpanel.h index 8dc493051..788593d9e 100644 --- a/rtgui/saveformatpanel.h +++ b/rtgui/saveformatpanel.h @@ -44,7 +44,7 @@ protected: Gtk::Grid* jpegOpts; Gtk::Label* jpegSubSampLabel; FormatChangeListener* listener; - Glib::ustring fstr[6]; + Glib::ustring fstr[7]; Gtk::CheckButton* savesPP; From b356c7481318f9c0a53a28b68d3126efbe5ec7d1 Mon Sep 17 00:00:00 2001 From: Hombre Date: Thu, 10 May 2018 22:13:06 +0200 Subject: [PATCH 3/4] RT now generates valid 16-bit float TIFF output image (see #2357) ...however the "reader" of those files is broken and make RT crash. Thanks to @heckflosse for the "writer" patch. --- rtengine/image16.cc | 11 +--- rtengine/image16.h | 4 +- rtengine/image8.cc | 7 +-- rtengine/image8.h | 4 +- rtengine/imagefloat.cc | 108 +++++++++++++------------------------ rtengine/imagefloat.h | 118 ++++++++++++++++++++++++++++++++++++++++- rtengine/imageio.cc | 6 +-- rtengine/imageio.h | 4 +- 8 files changed, 166 insertions(+), 96 deletions(-) diff --git a/rtengine/image16.cc b/rtengine/image16.cc index 68dd4bb40..65d9fc5a7 100644 --- a/rtengine/image16.cc +++ b/rtengine/image16.cc @@ -60,7 +60,7 @@ Image16::~Image16 () { } -void Image16::getScanline (int row, unsigned char* buffer, int bps) +void Image16::getScanline (int row, unsigned char* buffer, int bps, bool isFloat) { if (data == nullptr) { @@ -74,20 +74,13 @@ void Image16::getScanline (int row, unsigned char* buffer, int bps) } } -/* - * void Image16::setScanline (int row, unsigned char* buffer, int bps, int minValue[3], int maxValue[3]); - * has not been implemented yet, because as of now, this method is called for IIOSF_FLOATxx sample format only - */ -void Image16::setScanline (int row, unsigned char* buffer, int bps, unsigned int numSamples, float *minValue, float *maxValue) +void Image16::setScanline (int row, unsigned char* buffer, int bps, unsigned int numSamples) { if (data == nullptr) { return; } - // For optimization purpose, we're assuming that this class never has to provide min/max bounds - assert(!minValue); - switch (sampleFormat) { case (IIOSF_UNSIGNED_CHAR): { int ix = 0; diff --git a/rtengine/image16.h b/rtengine/image16.h index 33e74f146..9764785a4 100644 --- a/rtengine/image16.h +++ b/rtengine/image16.h @@ -55,8 +55,8 @@ public: { return 8 * sizeof(unsigned short); } - virtual void getScanline (int row, unsigned char* buffer, int bps); - virtual void setScanline (int row, unsigned char* buffer, int bps, unsigned int numSamples, float *minValue = nullptr, float *maxValue = nullptr); + virtual void getScanline (int row, unsigned char* buffer, int bps, bool isFloat = false); + virtual void setScanline (int row, unsigned char* buffer, int bps, unsigned int numSamples); // functions inherited from IImage16: virtual MyMutex& getMutex () diff --git a/rtengine/image8.cc b/rtengine/image8.cc index 69066f2dd..ab7393100 100644 --- a/rtengine/image8.cc +++ b/rtengine/image8.cc @@ -37,7 +37,7 @@ Image8::~Image8 () { } -void Image8::getScanline (int row, unsigned char* buffer, int bps) +void Image8::getScanline (int row, unsigned char* buffer, int bps, bool isFloat) { if (data == nullptr) { @@ -55,16 +55,13 @@ void Image8::getScanline (int row, unsigned char* buffer, int bps) } } -void Image8::setScanline (int row, unsigned char* buffer, int bps, unsigned int numSamples, float *minValue, float *maxValue) +void Image8::setScanline (int row, unsigned char* buffer, int bps, unsigned int numSamples) { if (data == nullptr) { return; } - // For optimization purpose, we're assuming that this class never have to provide min/max bound - assert(!minValue); - switch (sampleFormat) { case (IIOSF_UNSIGNED_CHAR): if(numSamples == 1) { diff --git a/rtengine/image8.h b/rtengine/image8.h index 6444499d9..59d13c298 100644 --- a/rtengine/image8.h +++ b/rtengine/image8.h @@ -50,8 +50,8 @@ public: { return 8 * sizeof(unsigned char); } - virtual void getScanline (int row, unsigned char* buffer, int bps); - virtual void setScanline (int row, unsigned char* buffer, int bps, unsigned int numSamples, float *minValue = nullptr, float *maxValue = nullptr); + virtual void getScanline (int row, unsigned char* buffer, int bps, bool isFloat = false); + virtual void setScanline (int row, unsigned char* buffer, int bps, unsigned int numSamples); // functions inherited from IImage*: virtual MyMutex& getMutex () diff --git a/rtengine/imagefloat.cc b/rtengine/imagefloat.cc index e79678194..9ef6cc70b 100644 --- a/rtengine/imagefloat.cc +++ b/rtengine/imagefloat.cc @@ -44,7 +44,7 @@ Imagefloat::~Imagefloat () } // Call this method to handle floating points input values of different size -void Imagefloat::setScanline (int row, unsigned char* buffer, int bps, unsigned int numSamples, float *minValue, float *maxValue) +void Imagefloat::setScanline (int row, unsigned char* buffer, int bps, unsigned int numSamples) { if (data == nullptr) { @@ -55,45 +55,27 @@ void Imagefloat::setScanline (int row, unsigned char* buffer, int bps, unsigned // DNG_HalfToFloat and DNG_FP24ToFloat from dcraw.cc can be used to manually convert // from 16 and 24 bits to 32 bits float respectively switch (sampleFormat) { - case (IIOSF_FLOAT16): - case (IIOSF_FLOAT24): + case (IIOSF_FLOAT16): { + int ix = 0; + uint16_t* sbuffer = (uint16_t*) buffer; + + for (int i = 0; i < width; i++) { + r(row, i) = 65535.f * DNG_HalfToFloat(sbuffer[ix++]); + g(row, i) = 65535.f * DNG_HalfToFloat(sbuffer[ix++]); + b(row, i) = 65535.f * DNG_HalfToFloat(sbuffer[ix++]); + } + + break; + } + //case (IIOSF_FLOAT24): case (IIOSF_FLOAT32): { int ix = 0; float* sbuffer = (float*) buffer; for (int i = 0; i < width; i++) { - r(row, i) = 65535.f * sbuffer[ix]; - - if (minValue) { - if (sbuffer[ix] < minValue[0]) { - minValue[0] = sbuffer[ix]; - } else if (sbuffer[ix] > maxValue[0]) { - maxValue[0] = sbuffer[ix]; - } - } - ++ix; - - g(row, i) = 65535.f * sbuffer[ix]; - - if (minValue) { - if (sbuffer[ix] < minValue[1]) { - minValue[1] = sbuffer[ix]; - } else if (sbuffer[ix] > maxValue[1]) { - maxValue[1] = sbuffer[ix]; - } - } - ++ix; - - b(row, i) = 65535.f * sbuffer[ix]; - - if (minValue) { - if (sbuffer[ix] < minValue[2]) { - minValue[2] = sbuffer[ix]; - } else if (sbuffer[ix] > maxValue[2]) { - maxValue[2] = sbuffer[ix]; - } - } - ++ix; + r(row, i) = 65535.f * sbuffer[ix++]; + g(row, i) = 65535.f * sbuffer[ix++]; + b(row, i) = 65535.f * sbuffer[ix++]; } break; @@ -112,34 +94,8 @@ void Imagefloat::setScanline (int row, unsigned char* buffer, int bps, unsigned // TODO: we may have to handle other color space than sRGB! Color::xyz2srgb(xyzvalues[0], xyzvalues[1], xyzvalues[2], rgbvalues[0], rgbvalues[1], rgbvalues[2]); r(row, i) = rgbvalues[0]; - - if (minValue) { - if (rgbvalues[0] < minValue[0]) { - minValue[0] = rgbvalues[0]; - } else if (rgbvalues[0] > maxValue[0]) { - maxValue[0] = rgbvalues[0]; - } - } - g(row, i) = rgbvalues[1]; - - if (minValue) { - if (rgbvalues[1] < minValue[1]) { - minValue[1] = rgbvalues[1]; - } else if (rgbvalues[1] > maxValue[1]) { - maxValue[1] = rgbvalues[1]; - } - } - b(row, i) = rgbvalues[2]; - - if (minValue) { - if (rgbvalues[2] < minValue[2]) { - minValue[2] = rgbvalues[2]; - } else if (rgbvalues[2] > maxValue[2]) { - maxValue[2] = rgbvalues[2]; - } - } } break; @@ -154,22 +110,32 @@ void Imagefloat::setScanline (int row, unsigned char* buffer, int bps, unsigned namespace rtengine { extern void filmlike_clip(float *r, float *g, float *b); } -void Imagefloat::getScanline (int row, unsigned char* buffer, int bps) +void Imagefloat::getScanline (int row, unsigned char* buffer, int bps, bool isFloat) { if (data == nullptr) { return; } - if (bps == 32) { - int ix = 0; - float* sbuffer = (float*) buffer; - - // agriggio -- assume the image is normalized to [0, 65535] - for (int i = 0; i < width; i++) { - sbuffer[ix++] = r(row, i) / 65535.f; - sbuffer[ix++] = g(row, i) / 65535.f; - sbuffer[ix++] = b(row, i) / 65535.f; + if (isFloat) { + if (bps == 32) { + int ix = 0; + float* sbuffer = (float*) buffer; + // agriggio -- assume the image is normalized to [0, 65535] + for (int i = 0; i < width; i++) { + sbuffer[ix++] = r(row, i) / 65535.f; + sbuffer[ix++] = g(row, i) / 65535.f; + sbuffer[ix++] = b(row, i) / 65535.f; + } + } else if (bps == 16) { + int ix = 0; + uint16_t* sbuffer = (uint16_t*) buffer; + // agriggio -- assume the image is normalized to [0, 65535] + for (int i = 0; i < width; i++) { + sbuffer[ix++] = DNG_FloatToHalf(r(row, i) / 65535.f); + sbuffer[ix++] = DNG_FloatToHalf(g(row, i) / 65535.f); + sbuffer[ix++] = DNG_FloatToHalf(b(row, i) / 65535.f); + } } } else { unsigned short *sbuffer = (unsigned short *)buffer; diff --git a/rtengine/imagefloat.h b/rtengine/imagefloat.h index abb74bc8e..a705a45e7 100644 --- a/rtengine/imagefloat.h +++ b/rtengine/imagefloat.h @@ -59,8 +59,8 @@ public: { return 8 * sizeof(float); } - virtual void getScanline (int row, unsigned char* buffer, int bps); - virtual void setScanline (int row, unsigned char* buffer, int bps, unsigned int numSamples, float *minValue = nullptr, float *maxValue = nullptr); + virtual void getScanline (int row, unsigned char* buffer, int bps, bool isFloat = false); + virtual void setScanline (int row, unsigned char* buffer, int bps, unsigned int numSamples); // functions inherited from IImagefloat: virtual MyMutex& getMutex () @@ -100,6 +100,120 @@ public: delete this; } + inline uint16_t DNG_FloatToHalf(float f) + { + union { + float f; + uint32_t i; + } tmp; + + tmp.f = f; + int32_t sign = (tmp.i >> 16) & 0x00008000; + int32_t exponent = ((tmp.i >> 23) & 0x000000ff) - (127 - 15); + int32_t mantissa = tmp.i & 0x007fffff; + if (exponent <= 0) { + if (exponent < -10) { + return (uint16_t)sign; + } + mantissa = (mantissa | 0x00800000) >> (1 - exponent); + if (mantissa & 0x00001000) + mantissa += 0x00002000; + return (uint16_t)(sign | (mantissa >> 13)); + } else if (exponent == 0xff - (127 - 15)) { + if (mantissa == 0) { + return (uint16_t)(sign | 0x7c00); + } else { + return (uint16_t)(sign | 0x7c00 | (mantissa >> 13)); + } + } + if (mantissa & 0x00001000) { + mantissa += 0x00002000; + if (mantissa & 0x00800000) { + mantissa = 0; // overflow in significand, + exponent += 1; // adjust exponent + } + } + if (exponent > 30) { + return (uint16_t)(sign | 0x7c00); // infinity with the same sign as f. + } + return (uint16_t)(sign | (exponent << 10) | (mantissa >> 13)); + } + + // From DNG SDK dng_utils.h + inline float DNG_HalfToFloat(uint16_t halfValue) + { + union { + float f; + uint32_t i; + } tmp; + + int32_t sign = (halfValue >> 15) & 0x00000001; + int32_t exponent = (halfValue >> 10) & 0x0000001f; + int32_t mantissa = halfValue & 0x000003ff; + if (exponent == 0) { + if (mantissa == 0) { + // Plus or minus zero + return (uint32_t) (sign << 31); + } else { + // Denormalized number -- renormalize it + while (!(mantissa & 0x00000400)) { + mantissa <<= 1; + exponent -= 1; + } + exponent += 1; + mantissa &= ~0x00000400; + } + } else if (exponent == 31) { + if (mantissa == 0) { + // Positive or negative infinity, convert to maximum (16 bit) values. + return (uint32_t) ((sign << 31) | ((0x1eL + 127 - 15) << 23) | (0x3ffL << 13)); + } else { + // Nan -- Just set to zero. + return 0; + } + } + // Normalized number + exponent += (127 - 15); + mantissa <<= 13; + // Assemble sign, exponent and mantissa. + tmp.i = (uint32_t) ((sign << 31) | (exponent << 23) | mantissa); + return tmp.f; + } + + inline uint32_t DNG_FP24ToFloat(const uint8_t * input) + { + int32_t sign = (input [0] >> 7) & 0x01; + int32_t exponent = (input [0] ) & 0x7F; + int32_t mantissa = (((int32_t) input [1]) << 8) | input[2]; + if (exponent == 0) { + if (mantissa == 0) { + // Plus or minus zero + return (uint32_t) (sign << 31); + } else { + // Denormalized number -- renormalize it + while (!(mantissa & 0x00010000)) { + mantissa <<= 1; + exponent -= 1; + } + exponent += 1; + mantissa &= ~0x00010000; + } + } else if (exponent == 127) { + if (mantissa == 0) { + // Positive or negative infinity, convert to maximum (24 bit) values. + return (uint32_t) ((sign << 31) | ((0x7eL + 128 - 64) << 23) | (0xffffL << 7)); + } else { + // Nan -- Just set to zero. + return 0; + } + } + // Normalized number + exponent += (128 - 64); + mantissa <<= 7; + // Assemble sign, exponent and mantissa. + return (uint32_t) ((sign << 31) | (exponent << 23) | mantissa); + } + virtual void normalizeFloat(float srcMinVal, float srcMaxVal); void normalizeFloatTo1(); void normalizeFloatTo65535(); diff --git a/rtengine/imageio.cc b/rtengine/imageio.cc index d3e815027..8bfe81504 100644 --- a/rtengine/imageio.cc +++ b/rtengine/imageio.cc @@ -1468,9 +1468,9 @@ int ImageIO::saveTIFF (Glib::ustring fname, int bps, float isFloat, bool uncompr } for (int row = 0; row < height; row++) { - getScanline (row, linebuffer, bps); + getScanline (row, linebuffer, bps, isFloat); - /*if (bps == 16) { + if (bps == 16) { if(needsReverse && !uncompressed) { for(int i = 0; i < lineWidth; i += 2) { char temp = linebuffer[i]; @@ -1478,7 +1478,7 @@ int ImageIO::saveTIFF (Glib::ustring fname, int bps, float isFloat, bool uncompr linebuffer[i + 1] = temp; } } - } else */ if (bps == 32) { + } else if (bps == 32) { if(needsReverse && !uncompressed) { for(int i = 0; i < lineWidth; i += 4) { char temp = linebuffer[i]; diff --git a/rtengine/imageio.h b/rtengine/imageio.h index 135927bbf..cbf245291 100644 --- a/rtengine/imageio.h +++ b/rtengine/imageio.h @@ -112,8 +112,8 @@ public: } virtual int getBPS () = 0; - virtual void getScanline (int row, unsigned char* buffer, int bps) {} - virtual void setScanline (int row, unsigned char* buffer, int bps, unsigned int numSamples = 3, float minValue[3] = nullptr, float maxValue[3] = nullptr) {} + virtual void getScanline (int row, unsigned char* buffer, int bps, bool isFloat = false) {} + virtual void setScanline (int row, unsigned char* buffer, int bps, unsigned int numSamples = 3) {} virtual bool readImage (Glib::ustring &fname, FILE *fh) { From 258dfc2275e0e337e279313b0528e58ef1328fd0 Mon Sep 17 00:00:00 2001 From: heckflosse Date: Fri, 31 Aug 2018 13:01:27 +0200 Subject: [PATCH 4/4] Fix segfault when loading 16-bit float tiff --- rtengine/imagefloat.h | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/rtengine/imagefloat.h b/rtengine/imagefloat.h index ee719a352..65c291775 100644 --- a/rtengine/imagefloat.h +++ b/rtengine/imagefloat.h @@ -153,7 +153,8 @@ public: if (exponent == 0) { if (mantissa == 0) { // Plus or minus zero - return (uint32_t) (sign << 31); + tmp.i = (uint32_t) (sign << 31); + return tmp.f; } else { // Denormalized number -- renormalize it while (!(mantissa & 0x00000400)) { @@ -165,8 +166,9 @@ public: } } else if (exponent == 31) { if (mantissa == 0) { - // Positive or negative infinity, convert to maximum (16 bit) values. - return (uint32_t) ((sign << 31) | ((0x1eL + 127 - 15) << 23) | (0x3ffL << 13)); + // Positive or negative infinity, convert to maximum (16 bit) values. + tmp.i = (uint32_t)((sign << 31) | ((0x1eL + 127 - 15) << 23) | (0x3ffL << 13)); + return tmp.f; } else { // Nan -- Just set to zero. return 0;