From bc8b8902e643fcf2f0771b42b1badd6da902ca9e Mon Sep 17 00:00:00 2001 From: Alberto Griggio Date: Mon, 20 Nov 2017 00:10:51 +0100 Subject: [PATCH 1/5] make rtengine::processImage return an Imagefloat instead of an Image16 This is for supporting saving to 32-bit float TIFFs --- rtengine/image16.cc | 80 +++++++++++++++++------------------ rtengine/image16.h | 2 +- rtengine/imagefloat.cc | 62 +++++++++++++++++++++++++++ rtengine/imagefloat.h | 1 + rtengine/improccoordinator.cc | 15 +++---- rtengine/improcfun.h | 6 +-- rtengine/iplab2rgb.cc | 13 +++--- rtengine/ipresize.cc | 10 ++--- rtengine/rtengine.h | 4 +- rtengine/simpleprocess.cc | 20 ++++----- rtgui/batchqueue.cc | 2 +- rtgui/batchqueue.h | 2 +- rtgui/editorpanel.cc | 26 ++++++------ rtgui/editorpanel.h | 8 ++-- rtgui/main-cli.cc | 2 +- 15 files changed, 157 insertions(+), 96 deletions(-) diff --git a/rtengine/image16.cc b/rtengine/image16.cc index e0f7470a6..1bd0680d0 100644 --- a/rtengine/image16.cc +++ b/rtengine/image16.cc @@ -325,50 +325,50 @@ Image16::tofloat() return imgfloat; } -// Parallized transformation; create transform with cmsFLAGS_NOCACHE! -void Image16::ExecCMSTransform(cmsHTRANSFORM hTransform, const LabImage &labImage, int cx, int cy) -{ - // LittleCMS cannot parallelize planar Lab float images - // so build temporary buffers to allow multi processor execution -#ifdef _OPENMP - #pragma omp parallel -#endif - { - AlignedBuffer bufferLab(width * 3); - AlignedBuffer bufferRGB(width * 3); +// // Parallized transformation; create transform with cmsFLAGS_NOCACHE! +// void Image16::ExecCMSTransform(cmsHTRANSFORM hTransform, const LabImage &labImage, int cx, int cy) +// { +// // LittleCMS cannot parallelize planar Lab float images +// // so build temporary buffers to allow multi processor execution +// #ifdef _OPENMP +// #pragma omp parallel +// #endif +// { +// AlignedBuffer bufferLab(width * 3); +// AlignedBuffer bufferRGB(width * 3); -#ifdef _OPENMP - #pragma omp for schedule(static) -#endif +// #ifdef _OPENMP +// #pragma omp for schedule(static) +// #endif - for (int y = cy; y < cy + height; y++) - { - unsigned short *pRGB, *pR, *pG, *pB; - float *pLab, *pL, *pa, *pb; +// for (int y = cy; y < cy + height; y++) +// { +// unsigned short *pRGB, *pR, *pG, *pB; +// float *pLab, *pL, *pa, *pb; - pLab= bufferLab.data; - pL = labImage.L[y] + cx; - pa = labImage.a[y] + cx; - pb = labImage.b[y] + cx; +// pLab= bufferLab.data; +// pL = labImage.L[y] + cx; +// pa = labImage.a[y] + cx; +// pb = labImage.b[y] + cx; - for (int x = 0; x < width; x++) { - *(pLab++) = *(pL++) / 327.68f; - *(pLab++) = *(pa++) / 327.68f; - *(pLab++) = *(pb++) / 327.68f; - } +// for (int x = 0; x < width; x++) { +// *(pLab++) = *(pL++) / 327.68f; +// *(pLab++) = *(pa++) / 327.68f; +// *(pLab++) = *(pb++) / 327.68f; +// } - cmsDoTransform (hTransform, bufferLab.data, bufferRGB.data, width); +// cmsDoTransform (hTransform, bufferLab.data, bufferRGB.data, width); - pRGB = bufferRGB.data; - pR = r(y - cy); - pG = g(y - cy); - pB = b(y - cy); +// pRGB = bufferRGB.data; +// pR = r(y - cy); +// pG = g(y - cy); +// pB = b(y - cy); - for (int x = 0; x < width; x++) { - *(pR++) = *(pRGB++); - *(pG++) = *(pRGB++); - *(pB++) = *(pRGB++); - } - } // End of parallelization - } -} +// for (int x = 0; x < width; x++) { +// *(pR++) = *(pRGB++); +// *(pG++) = *(pRGB++); +// *(pB++) = *(pRGB++); +// } +// } // End of parallelization +// } +// } diff --git a/rtengine/image16.h b/rtengine/image16.h index 4d74dfbc4..ee402d556 100644 --- a/rtengine/image16.h +++ b/rtengine/image16.h @@ -96,7 +96,7 @@ public: delete this; } - void ExecCMSTransform(cmsHTRANSFORM hTransform, const LabImage &labImage, int cx, int cy); + /* void ExecCMSTransform(cmsHTRANSFORM hTransform, const LabImage &labImage, int cx, int cy); */ }; } diff --git a/rtengine/imagefloat.cc b/rtengine/imagefloat.cc index 30871c9b1..41b10552a 100644 --- a/rtengine/imagefloat.cc +++ b/rtengine/imagefloat.cc @@ -154,11 +154,25 @@ void Imagefloat::getScanline (int row, unsigned char* buffer, int bps) 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) { + unsigned short *sbuffer = (unsigned short *)buffer; + for (int i = 0, ix = 0; i < width; i++) { sbuffer[ix++] = r(row, i); sbuffer[ix++] = g(row, i); sbuffer[ix++] = b(row, i); } + } else if (bps == 8) { + for (int i = 0, ix = 0; i < width; i++) { + buffer[ix++] = rtengine::uint16ToUint8Rounded(r(row, i)); + buffer[ix++] = rtengine::uint16ToUint8Rounded(g(row, i)); + buffer[ix++] = rtengine::uint16ToUint8Rounded(b(row, i)); + } } } @@ -511,3 +525,51 @@ void Imagefloat::ExecCMSTransform(cmsHTRANSFORM hTransform) } // End of parallelization } } + +// Parallized transformation; create transform with cmsFLAGS_NOCACHE! +void Imagefloat::ExecCMSTransform(cmsHTRANSFORM hTransform, const LabImage &labImage, int cx, int cy) +{ + // LittleCMS cannot parallelize planar Lab float images + // so build temporary buffers to allow multi processor execution +#ifdef _OPENMP + #pragma omp parallel +#endif + { + AlignedBuffer bufferLab(width * 3); + AlignedBuffer bufferRGB(width * 3); + +#ifdef _OPENMP + #pragma omp for schedule(static) +#endif + + for (int y = cy; y < cy + height; y++) + { + float *pRGB, *pR, *pG, *pB; + float *pLab, *pL, *pa, *pb; + + pLab= bufferLab.data; + pL = labImage.L[y] + cx; + pa = labImage.a[y] + cx; + pb = labImage.b[y] + cx; + + for (int x = 0; x < width; x++) { + *(pLab++) = *(pL++) / 327.68f; + *(pLab++) = *(pa++) / 327.68f; + *(pLab++) = *(pb++) / 327.68f; + } + + cmsDoTransform (hTransform, bufferLab.data, bufferRGB.data, width); + + pRGB = bufferRGB.data; + pR = r(y - cy); + pG = g(y - cy); + pB = b(y - cy); + + for (int x = 0; x < width; x++) { + *(pR++) = *(pRGB++); + *(pG++) = *(pRGB++); + *(pB++) = *(pRGB++); + } + } // End of parallelization + } +} diff --git a/rtengine/imagefloat.h b/rtengine/imagefloat.h index 7348588df..753406d25 100644 --- a/rtengine/imagefloat.h +++ b/rtengine/imagefloat.h @@ -106,6 +106,7 @@ public: void calcCroppedHistogram(const ProcParams ¶ms, float scale, LUTu & hist); void ExecCMSTransform(cmsHTRANSFORM hTransform); + void ExecCMSTransform(cmsHTRANSFORM hTransform, const LabImage &labImage, int cx, int cy); }; } diff --git a/rtengine/improccoordinator.cc b/rtengine/improccoordinator.cc index 96710ee63..976676fc3 100644 --- a/rtengine/improccoordinator.cc +++ b/rtengine/improccoordinator.cc @@ -1270,21 +1270,18 @@ void ImProcCoordinator::saveInputICCReference (const Glib::ustring& fname, bool } } - Image16* im16 = im->to16(); - delete im; - int imw, imh; double tmpScale = ipf.resizeScale (¶ms, fW, fH, imw, imh); if (tmpScale != 1.0) { - Image16* tempImage = new Image16 (imw, imh); - ipf.resize (im16, tempImage, tmpScale); - delete im16; - im16 = tempImage; + Imagefloat* tempImage = new Imagefloat (imw, imh); + ipf.resize (im, tempImage, tmpScale); + delete im; + im = tempImage; } - im16->saveTIFF (fname, 16, true); - delete im16; + im->saveTIFF (fname, 16, true); + delete im; if (plistener) { plistener->setProgressState (false); diff --git a/rtengine/improcfun.h b/rtengine/improcfun.h index 08c3fa4cc..74ae1e754 100644 --- a/rtengine/improcfun.h +++ b/rtengine/improcfun.h @@ -239,9 +239,9 @@ public: void transform (Imagefloat* original, Imagefloat* transformed, int cx, int cy, int sx, int sy, int oW, int oH, int fW, int fH, const FramesMetaData *metadata, int rawRotationDeg, bool fullImage); float resizeScale (const ProcParams* params, int fw, int fh, int &imw, int &imh); void lab2monitorRgb (LabImage* lab, Image8* image); - void resize (Image16* src, Image16* dst, float dScale); + void resize (Imagefloat* src, Imagefloat* dst, float dScale); void Lanczos (const LabImage* src, LabImage* dst, float scale); - void Lanczos (const Image16* src, Image16* dst, float scale); + void Lanczos (const Imagefloat* src, Imagefloat* dst, float scale); void deconvsharpening (float** luminance, float** buffer, int W, int H, const SharpeningParams &sharpenParam); void MLsharpen (LabImage* lab);// Manuel's clarity / sharpening @@ -347,7 +347,7 @@ public: void ToneMapFattal02(Imagefloat *rgb); Image8* lab2rgb (LabImage* lab, int cx, int cy, int cw, int ch, const procparams::ColorManagementParams &icm); - Image16* lab2rgb16 (LabImage* lab, int cx, int cy, int cw, int ch, const procparams::ColorManagementParams &icm, GammaValues *ga = nullptr); + Imagefloat* lab2rgbOut (LabImage* lab, int cx, int cy, int cw, int ch, const procparams::ColorManagementParams &icm, GammaValues *ga = nullptr); // CieImage *ciec; bool transCoord (int W, int H, int x, int y, int w, int h, int& xv, int& yv, int& wv, int& hv, double ascaleDef = -1, const LensCorrection *pLCPMap = nullptr); diff --git a/rtengine/iplab2rgb.cc b/rtengine/iplab2rgb.cc index abef0a878..ba2fe6ffb 100644 --- a/rtengine/iplab2rgb.cc +++ b/rtengine/iplab2rgb.cc @@ -262,7 +262,7 @@ Image8* ImProcFunctions::lab2rgb (LabImage* lab, int cx, int cy, int cw, int ch, * If a custom gamma profile can be created, divide by 327.68, convert to xyz and apply the custom gamma transform * otherwise divide by 327.68, convert to xyz and apply the sRGB transform, before converting with gamma2curve */ -Image16* ImProcFunctions::lab2rgb16 (LabImage* lab, int cx, int cy, int cw, int ch, const procparams::ColorManagementParams &icm, GammaValues *ga) +Imagefloat* ImProcFunctions::lab2rgbOut (LabImage* lab, int cx, int cy, int cw, int ch, const procparams::ColorManagementParams &icm, GammaValues *ga) { if (cx < 0) { @@ -281,7 +281,7 @@ Image16* ImProcFunctions::lab2rgb16 (LabImage* lab, int cx, int cy, int cw, int ch = lab->H - cy; } - Image16* image = new Image16 (cw, ch); + Imagefloat* image = new Imagefloat (cw, ch); cmsHPROFILE oprof = nullptr; if (ga) { @@ -300,11 +300,12 @@ Image16* ImProcFunctions::lab2rgb16 (LabImage* lab, int cx, int cy, int cw, int } lcmsMutex->lock (); cmsHPROFILE iprof = cmsCreateLab4Profile(nullptr); - cmsHTRANSFORM hTransform = cmsCreateTransform (iprof, TYPE_Lab_FLT, oprof, TYPE_RGB_16, icm.outputIntent, flags); + cmsHTRANSFORM hTransform = cmsCreateTransform (iprof, TYPE_Lab_FLT, oprof, TYPE_RGB_FLT, icm.outputIntent, flags); lcmsMutex->unlock (); image->ExecCMSTransform(hTransform, *lab, cx, cy); cmsDeleteTransform(hTransform); + image->normalizeFloatTo65535(); } else { #ifdef _OPENMP #pragma omp parallel for schedule(dynamic,16) if (multiThread) @@ -329,9 +330,9 @@ Image16* ImProcFunctions::lab2rgb16 (LabImage* lab, int cx, int cy, int cw, int Color::xyz2srgb(x_, y_, z_, R, G, B); - image->r(i - cy, j - cx) = (int)Color::gamma2curve[CLIP(R)]; - image->g(i - cy, j - cx) = (int)Color::gamma2curve[CLIP(G)]; - image->b(i - cy, j - cx) = (int)Color::gamma2curve[CLIP(B)]; + image->r(i - cy, j - cx) = Color::gamma2curve[CLIP(R)]; + image->g(i - cy, j - cx) = Color::gamma2curve[CLIP(G)]; + image->b(i - cy, j - cx) = Color::gamma2curve[CLIP(B)]; } } } diff --git a/rtengine/ipresize.cc b/rtengine/ipresize.cc index 644e180c7..1a48e5c43 100644 --- a/rtengine/ipresize.cc +++ b/rtengine/ipresize.cc @@ -46,7 +46,7 @@ static inline float Lanc (float x, float a) } } -void ImProcFunctions::Lanczos (const Image16* src, Image16* dst, float scale) +void ImProcFunctions::Lanczos (const Imagefloat* src, Imagefloat* dst, float scale) { const float delta = 1.0f / scale; @@ -159,9 +159,9 @@ void ImProcFunctions::Lanczos (const Image16* src, Image16* dst, float scale) b += wh[k] * lb[jj]; } - dst->r (i, j) = CLIP (static_cast (r)); - dst->g (i, j) = CLIP (static_cast (g)); - dst->b (i, j) = CLIP (static_cast (b)); + dst->r (i, j) = CLIP (r);//static_cast (r)); + dst->g (i, j) = CLIP (g);//static_cast (g)); + dst->b (i, j) = CLIP (b);//static_cast (b)); } } @@ -396,7 +396,7 @@ float ImProcFunctions::resizeScale (const ProcParams* params, int fw, int fh, in return (float)dScale; } -void ImProcFunctions::resize (Image16* src, Image16* dst, float dScale) +void ImProcFunctions::resize (Imagefloat* src, Imagefloat* dst, float dScale) { #ifdef PROFILE time_t t1 = clock(); diff --git a/rtengine/rtengine.h b/rtengine/rtengine.h index edf903352..53ed06385 100644 --- a/rtengine/rtengine.h +++ b/rtengine/rtengine.h @@ -534,7 +534,7 @@ public: * @param pl is an optional ProgressListener if you want to keep track of the progress * @param tunnelMetaData tunnels IPTC and XMP to output without change * @return the resulting image, with the output profile applied, exif and iptc data set. You have to save it or you can access the pixel data directly. */ -IImage16* processImage (ProcessingJob* job, int& errorCode, ProgressListener* pl = nullptr, bool tunnelMetaData = false, bool flush = false); +IImagefloat* processImage (ProcessingJob* job, int& errorCode, ProgressListener* pl = nullptr, bool tunnelMetaData = false, bool flush = false); /** This class is used to control the batch processing. The class implementing this interface will be called when the full processing of an * image is ready and the next job to process is needed. */ @@ -545,7 +545,7 @@ public: * there is no jobs left. * @param img is the result of the last ProcessingJob * @return the next ProcessingJob to process */ - virtual ProcessingJob* imageReady (IImage16* img) = 0; + virtual ProcessingJob* imageReady (IImagefloat* img) = 0; virtual void error (Glib::ustring message) = 0; }; /** This function performs all the image processinf steps corresponding to the given ProcessingJob. It runs in the background, thus it returns immediately, diff --git a/rtengine/simpleprocess.cc b/rtengine/simpleprocess.cc index dc285ce63..45595a379 100644 --- a/rtengine/simpleprocess.cc +++ b/rtengine/simpleprocess.cc @@ -66,7 +66,7 @@ public: { } - Image16 *operator()() + Imagefloat *operator()() { if (!job->fast) { return normal_pipeline(); @@ -76,7 +76,7 @@ public: } private: - Image16 *normal_pipeline() + Imagefloat *normal_pipeline() { if (!stage_init()) { return nullptr; @@ -87,7 +87,7 @@ private: return stage_finish(); } - Image16 *fast_pipeline() + Imagefloat *fast_pipeline() { if (!job->pparams.resize.enabled) { return normal_pipeline(); @@ -831,7 +831,7 @@ private: } } - Image16 *stage_finish() + Imagefloat *stage_finish() { procparams::ProcParams& params = job->pparams; //ImProcFunctions ipf (¶ms, true); @@ -1227,7 +1227,7 @@ private: } } - Image16* readyImg = nullptr; + Imagefloat* readyImg = nullptr; cmsHPROFILE jprof = nullptr; bool customGamma = false; bool useLCMS = false; @@ -1237,7 +1237,7 @@ private: GammaValues ga; // if(params.blackwhite.enabled) params.toneCurve.hrenabled=false; - readyImg = ipf.lab2rgb16 (labView, cx, cy, cw, ch, params.icm, &ga); + readyImg = ipf.lab2rgbOut (labView, cx, cy, cw, ch, params.icm, &ga); customGamma = true; //or selected Free gamma @@ -1251,7 +1251,7 @@ private: // if Default gamma mode: we use the profile selected in the "Output profile" combobox; // gamma come from the selected profile, otherwise it comes from "Free gamma" tool - readyImg = ipf.lab2rgb16 (labView, cx, cy, cw, ch, params.icm); + readyImg = ipf.lab2rgbOut (labView, cx, cy, cw, ch, params.icm); if (settings->verbose) { printf ("Output profile_: \"%s\"\n", params.icm.output.c_str()); @@ -1281,7 +1281,7 @@ private: } if (tmpScale != 1.0 && params.resize.method == "Nearest") { // resize rgb data (gamma applied) - Image16* tempImage = new Image16 (imw, imh); + Imagefloat* tempImage = new Imagefloat (imw, imh); ipf.resize (readyImg, tempImage, tmpScale); delete readyImg; readyImg = tempImage; @@ -1566,7 +1566,7 @@ private: } // namespace -IImage16* processImage (ProcessingJob* pjob, int& errorCode, ProgressListener* pl, bool tunnelMetaData, bool flush) +IImagefloat* processImage (ProcessingJob* pjob, int& errorCode, ProgressListener* pl, bool tunnelMetaData, bool flush) { ImageProcessor proc (pjob, errorCode, pl, tunnelMetaData, flush); return proc(); @@ -1579,7 +1579,7 @@ void batchProcessingThread (ProcessingJob* job, BatchProcessingListener* bpl, bo while (currentJob) { int errorCode; - IImage16* img = processImage (currentJob, errorCode, bpl, tunnelMetaData, true); + IImagefloat* img = processImage (currentJob, errorCode, bpl, tunnelMetaData, true); if (errorCode) { bpl->error (M ("MAIN_MSG_CANNOTLOAD")); diff --git a/rtgui/batchqueue.cc b/rtgui/batchqueue.cc index 43ee5d79d..a2409a39f 100644 --- a/rtgui/batchqueue.cc +++ b/rtgui/batchqueue.cc @@ -578,7 +578,7 @@ void BatchQueue::startProcessing () } } -rtengine::ProcessingJob* BatchQueue::imageReady (rtengine::IImage16* img) +rtengine::ProcessingJob* BatchQueue::imageReady (rtengine::IImagefloat* img) { // save image img diff --git a/rtgui/batchqueue.h b/rtgui/batchqueue.h index 68373838e..0d91542b6 100644 --- a/rtgui/batchqueue.h +++ b/rtgui/batchqueue.h @@ -62,7 +62,7 @@ public: return (!fd.empty()); } - rtengine::ProcessingJob* imageReady (rtengine::IImage16* img); + rtengine::ProcessingJob* imageReady (rtengine::IImagefloat* img); void error (Glib::ustring msg); void setProgress (double p); void rightClicked (ThumbBrowserEntryBase* entry); diff --git a/rtgui/editorpanel.cc b/rtgui/editorpanel.cc index c2bec8692..7099007b6 100644 --- a/rtgui/editorpanel.cc +++ b/rtgui/editorpanel.cc @@ -1751,9 +1751,9 @@ void EditorPanel::procParamsChanged (Thumbnail* thm, int whoChangedIt) } } -bool EditorPanel::idle_saveImage (ProgressConnector *pc, Glib::ustring fname, SaveFormat sf, rtengine::procparams::ProcParams &pparams) +bool EditorPanel::idle_saveImage (ProgressConnector *pc, Glib::ustring fname, SaveFormat sf, rtengine::procparams::ProcParams &pparams) { - rtengine::IImage16* img = pc->returnValue(); + rtengine::IImagefloat* img = pc->returnValue(); delete pc; if ( img ) { @@ -1764,13 +1764,13 @@ bool EditorPanel::idle_saveImage (ProgressConnector *pc, Gl img->setSaveProgressListener (parent->getProgressListener()); if (sf.format == "tif") - ld->startFunc (sigc::bind (sigc::mem_fun (img, &rtengine::IImage16::saveAsTIFF), fname, sf.tiffBits, sf.tiffUncompressed), + ld->startFunc (sigc::bind (sigc::mem_fun (img, &rtengine::IImagefloat::saveAsTIFF), fname, sf.tiffBits, 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::IImage16::saveAsPNG), fname, sf.pngBits), + ld->startFunc (sigc::bind (sigc::mem_fun (img, &rtengine::IImagefloat::saveAsPNG), fname, sf.pngBits), sigc::bind (sigc::mem_fun (*this, &EditorPanel::idle_imageSaved), ld, img, fname, sf, pparams)); else if (sf.format == "jpg") - ld->startFunc (sigc::bind (sigc::mem_fun (img, &rtengine::IImage16::saveAsJPEG), fname, sf.jpegQuality, sf.jpegSubSamp), + ld->startFunc (sigc::bind (sigc::mem_fun (img, &rtengine::IImagefloat::saveAsJPEG), fname, sf.jpegQuality, sf.jpegSubSamp), sigc::bind (sigc::mem_fun (*this, &EditorPanel::idle_imageSaved), ld, img, fname, sf, pparams)); else { delete ld; @@ -1791,7 +1791,7 @@ bool EditorPanel::idle_saveImage (ProgressConnector *pc, Gl return false; } -bool EditorPanel::idle_imageSaved (ProgressConnector *pc, rtengine::IImage16* img, Glib::ustring fname, SaveFormat sf, rtengine::procparams::ProcParams &pparams) +bool EditorPanel::idle_imageSaved (ProgressConnector *pc, rtengine::IImagefloat* img, Glib::ustring fname, SaveFormat sf, rtengine::procparams::ProcParams &pparams) { img->free (); @@ -1916,7 +1916,7 @@ void EditorPanel::saveAsPressed () ipc->getParams (&pparams); rtengine::ProcessingJob* job = rtengine::ProcessingJob::create (ipc->getInitialImage(), pparams); - ProgressConnector *ld = new ProgressConnector(); + ProgressConnector *ld = new ProgressConnector(); ld->startFunc (sigc::bind (sigc::ptr_fun (&rtengine::processImage), job, err, parent->getProgressListener(), options.tunnelMetaData, false ), sigc::bind (sigc::mem_fun ( *this, &EditorPanel::idle_saveImage ), ld, fnameOut, sf, pparams)); saveimgas->set_sensitive (false); @@ -1960,7 +1960,7 @@ void EditorPanel::sendToGimpPressed () rtengine::procparams::ProcParams pparams; ipc->getParams (&pparams); rtengine::ProcessingJob* job = rtengine::ProcessingJob::create (ipc->getInitialImage(), pparams); - ProgressConnector *ld = new ProgressConnector(); + ProgressConnector *ld = new ProgressConnector(); ld->startFunc (sigc::bind (sigc::ptr_fun (&rtengine::processImage), job, err, parent->getProgressListener(), options.tunnelMetaData, false ), sigc::bind (sigc::mem_fun ( *this, &EditorPanel::idle_sendToGimp ), ld, openThm->getFileName() )); saveimgas->set_sensitive (false); @@ -1975,7 +1975,7 @@ bool EditorPanel::saveImmediately (const Glib::ustring &filename, const SaveForm rtengine::ProcessingJob *job = rtengine::ProcessingJob::create (ipc->getInitialImage(), pparams); // save immediately - rtengine::IImage16 *img = rtengine::processImage (job, err, nullptr, options.tunnelMetaData, false); + rtengine::IImagefloat *img = rtengine::processImage (job, err, nullptr, options.tunnelMetaData, false); int err = 0; @@ -2015,10 +2015,10 @@ void EditorPanel::syncFileBrowser() // synchronize filebrowser with image in E } } -bool EditorPanel::idle_sendToGimp ( ProgressConnector *pc, Glib::ustring fname) +bool EditorPanel::idle_sendToGimp ( ProgressConnector *pc, Glib::ustring fname) { - rtengine::IImage16* img = pc->returnValue(); + rtengine::IImagefloat* img = pc->returnValue(); delete pc; if (img) { @@ -2050,7 +2050,7 @@ bool EditorPanel::idle_sendToGimp ( ProgressConnector *pc, ProgressConnector *ld = new ProgressConnector(); img->setSaveProgressListener (parent->getProgressListener()); - ld->startFunc (sigc::bind (sigc::mem_fun (img, &rtengine::IImage16::saveAsTIFF), fileName, sf.tiffBits, sf.tiffUncompressed), + ld->startFunc (sigc::bind (sigc::mem_fun (img, &rtengine::IImagefloat::saveAsTIFF), fileName, sf.tiffBits, 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"); @@ -2063,7 +2063,7 @@ bool EditorPanel::idle_sendToGimp ( ProgressConnector *pc, return false; } -bool EditorPanel::idle_sentToGimp (ProgressConnector *pc, rtengine::IImage16* img, Glib::ustring filename) +bool EditorPanel::idle_sentToGimp (ProgressConnector *pc, rtengine::IImagefloat* img, Glib::ustring filename) { img->free (); int errore = pc->returnValue(); diff --git a/rtgui/editorpanel.h b/rtgui/editorpanel.h index 5beb9ee7d..50fc337ed 100644 --- a/rtgui/editorpanel.h +++ b/rtgui/editorpanel.h @@ -146,10 +146,10 @@ private: void close (); BatchQueueEntry* createBatchQueueEntry (); - bool idle_imageSaved (ProgressConnector *pc, rtengine::IImage16* img, Glib::ustring fname, SaveFormat sf, rtengine::procparams::ProcParams &pparams); - bool idle_saveImage (ProgressConnector *pc, Glib::ustring fname, SaveFormat sf, rtengine::procparams::ProcParams &pparams); - bool idle_sendToGimp ( ProgressConnector *pc, Glib::ustring fname); - bool idle_sentToGimp (ProgressConnector *pc, rtengine::IImage16* img, Glib::ustring filename); + bool idle_imageSaved (ProgressConnector *pc, rtengine::IImagefloat* img, Glib::ustring fname, SaveFormat sf, rtengine::procparams::ProcParams &pparams); + bool idle_saveImage (ProgressConnector *pc, Glib::ustring fname, SaveFormat sf, rtengine::procparams::ProcParams &pparams); + bool idle_sendToGimp ( ProgressConnector *pc, Glib::ustring fname); + bool idle_sentToGimp (ProgressConnector *pc, rtengine::IImagefloat* img, Glib::ustring filename); Glib::ustring lastSaveAsFileName; bool realized; diff --git a/rtgui/main-cli.cc b/rtgui/main-cli.cc index 1d1917e8c..233e04956 100644 --- a/rtgui/main-cli.cc +++ b/rtgui/main-cli.cc @@ -822,7 +822,7 @@ int processLineParams ( int argc, char **argv ) } // Process image - rtengine::IImage16* resultImage = rtengine::processImage (job, errorCode, nullptr, options.tunnelMetaData); + rtengine::IImagefloat* resultImage = rtengine::processImage (job, errorCode, nullptr, options.tunnelMetaData); if ( !resultImage ) { errors++; From 912f9f436b1163c2235426eca4d8830eebcd4966 Mon Sep 17 00:00:00 2001 From: Alberto Griggio Date: Mon, 20 Nov 2017 00:11:18 +0100 Subject: [PATCH 2/5] added support for 32-bit floating-point TIFF output --- rtengine/imageio.cc | 23 ++++++++++++++++------- rtexif/rtexif.cc | 2 ++ rtgui/saveformatpanel.cc | 12 +++++++++--- rtgui/saveformatpanel.h | 2 +- 4 files changed, 28 insertions(+), 11 deletions(-) diff --git a/rtengine/imageio.cc b/rtengine/imageio.cc index ff9c9b559..e7a45ecf6 100644 --- a/rtengine/imageio.cc +++ b/rtengine/imageio.cc @@ -1250,20 +1250,28 @@ int ImageIO::saveTIFF (Glib::ustring fname, int bps, bool uncompressed) } #if __BYTE_ORDER__==__ORDER_LITTLE_ENDIAN__ - bool needsReverse = bps == 16 && exifRoot->getOrder() == rtexif::MOTOROLA; + bool needsReverse = (bps == 16 || bps == 32) && exifRoot->getOrder() == rtexif::MOTOROLA; #else - bool needsReverse = bps == 16 && exifRoot->getOrder() == rtexif::INTEL; + bool needsReverse = (bps == 16 || bps == 32) && exifRoot->getOrder() == rtexif::INTEL; #endif for (int i = 0; i < height; i++) { getScanline (i, linebuffer, bps); - if (needsReverse) - for (int i = 0; i < lineWidth; i += 2) { - char c = linebuffer[i]; - linebuffer[i] = linebuffer[i + 1]; - linebuffer[i + 1] = c; + if (needsReverse) { + if (bps == 16) { + for (int i = 0; i < lineWidth; i += 2) { + char c = linebuffer[i]; + linebuffer[i] = linebuffer[i + 1]; + linebuffer[i + 1] = c; + } + } else { + for (int i = 0; i < lineWidth; i += 4) { + std::swap(linebuffer[i], linebuffer[i+3]); + std::swap(linebuffer[i+1], linebuffer[i+2]); + } } + } fwrite (linebuffer, lineWidth, 1, file); @@ -1362,6 +1370,7 @@ int ImageIO::saveTIFF (Glib::ustring fname, int bps, bool uncompressed) TIFFSetField (out, TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_UINT); TIFFSetField (out, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB); TIFFSetField (out, TIFFTAG_COMPRESSION, uncompressed ? COMPRESSION_NONE : COMPRESSION_DEFLATE); + TIFFSetField (out, TIFFTAG_SAMPLEFORMAT, bps == 32 ? SAMPLEFORMAT_IEEEFP : SAMPLEFORMAT_UINT); if (!uncompressed) { TIFFSetField (out, TIFFTAG_PREDICTOR, PREDICTOR_NONE); diff --git a/rtexif/rtexif.cc b/rtexif/rtexif.cc index bc0e2002f..1701b3058 100644 --- a/rtexif/rtexif.cc +++ b/rtexif/rtexif.cc @@ -3240,6 +3240,8 @@ int ExifManager::createTIFFHeader (const TagDirectory* root, const rtengine::pro Tag* stripOffs = new Tag (cl, lookupAttrib (ifdAttribs, "StripOffsets")); stripOffs->initInt (0, LONG, strips); cl->replaceTag (stripOffs); + Tag *sampleFormat = new Tag (cl, lookupAttrib (ifdAttribs, "SampleFormat"), bps == 32 ? 3 : 1, SHORT); + cl->replaceTag (sampleFormat); for (int i = 0; i < strips - 1; i++) { stripBC->setInt (rps * W * 3 * bps / 8, i * 4); diff --git a/rtgui/saveformatpanel.cc b/rtgui/saveformatpanel.cc index 13e687595..fef05de23 100644 --- a/rtgui/saveformatpanel.cc +++ b/rtgui/saveformatpanel.cc @@ -40,14 +40,16 @@ SaveFormatPanel::SaveFormatPanel () : listener (nullptr) format->append ("JPEG (8 bit)"); format->append ("TIFF (8 bit)"); format->append ("TIFF (16 bit)"); + format->append ("TIFF (32 bit float)"); format->append ("PNG (8 bit)"); format->append ("PNG (16 bit)"); fstr[0] = "jpg"; fstr[1] = "tif"; fstr[2] = "tif"; - fstr[3] = "png"; + fstr[3] = "tif"; fstr[4] = "png"; + fstr[5] = "png"; hb1->attach (*flab, 0, 0, 1, 1); hb1->attach (*format, 1, 0, 1, 1); @@ -121,8 +123,10 @@ void SaveFormatPanel::init (SaveFormat &sf) if (sf.format == "jpg") { format->set_active (0); } else if (sf.format == "png" && sf.pngBits == 16) { - format->set_active (4); + format->set_active (5); } else if (sf.format == "png" && sf.pngBits == 8) { + format->set_active (4); + } else if (sf.format == "tif" && sf.tiffBits == 32) { format->set_active (3); } else if (sf.format == "tif" && sf.tiffBits == 16) { format->set_active (2); @@ -146,7 +150,7 @@ SaveFormat SaveFormatPanel::getFormat () int sel = format->get_active_row_number(); sf.format = fstr[sel]; - if (sel == 4) { + if (sel == 5) { sf.pngBits = 16; } else { sf.pngBits = 8; @@ -154,6 +158,8 @@ SaveFormat SaveFormatPanel::getFormat () if (sel == 2) { sf.tiffBits = 16; + } else if (sel == 3) { + sf.tiffBits = 32; } else { sf.tiffBits = 8; } diff --git a/rtgui/saveformatpanel.h b/rtgui/saveformatpanel.h index 76ae7055d..8dc493051 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[5]; + Glib::ustring fstr[6]; Gtk::CheckButton* savesPP; From 5ea5cfd943d2f77f91f3b7f47e377074e57be629 Mon Sep 17 00:00:00 2001 From: Hombre Date: Tue, 19 Dec 2017 22:28:52 +0100 Subject: [PATCH 3/5] Fixing #4233 : "Segfault opening PREDICTOR_FLOATINGPOINT image" Now compressed file use Deflate compression for all bit depth, and PREDICTOR_HORIZONTAL for 8/16 bit integer images, and PREDICTOR_FLOATINGPOINT for 32 bits float images. --- rtengine/imageio.cc | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/rtengine/imageio.cc b/rtengine/imageio.cc index e7a45ecf6..ba52c4672 100644 --- a/rtengine/imageio.cc +++ b/rtengine/imageio.cc @@ -1373,16 +1373,37 @@ int ImageIO::saveTIFF (Glib::ustring fname, int bps, bool uncompressed) TIFFSetField (out, TIFFTAG_SAMPLEFORMAT, bps == 32 ? SAMPLEFORMAT_IEEEFP : SAMPLEFORMAT_UINT); if (!uncompressed) { - TIFFSetField (out, TIFFTAG_PREDICTOR, PREDICTOR_NONE); + TIFFSetField (out, TIFFTAG_PREDICTOR, bps == 32 ? PREDICTOR_FLOATINGPOINT : PREDICTOR_HORIZONTAL); } if (profileData) { TIFFSetField (out, TIFFTAG_ICCPROFILE, profileLength, profileData); } +#if __BYTE_ORDER__==__ORDER_LITTLE_ENDIAN__ + bool needsReverse = (bps == 16 || bps == 32) && exifRoot->getOrder() == rtexif::MOTOROLA; +#else + bool needsReverse = (bps == 16 || bps == 32) && exifRoot->getOrder() == rtexif::INTEL; +#endif + for (int row = 0; row < height; row++) { getScanline (row, linebuffer, bps); + if (needsReverse) { + if (bps == 16) { + for (int i = 0; i < lineWidth; i += 2) { + char c = linebuffer[i]; + linebuffer[i] = linebuffer[i + 1]; + linebuffer[i + 1] = c; + } + } else { + for (int i = 0; i < lineWidth; i += 4) { + std::swap(linebuffer[i], linebuffer[i+3]); + std::swap(linebuffer[i+1], linebuffer[i+2]); + } + } + } + if (TIFFWriteScanline (out, linebuffer, row, 0) < 0) { TIFFClose (out); delete [] linebuffer; From 96863bb95648575f8528dfa75bbf232c9c4deb40 Mon Sep 17 00:00:00 2001 From: Hombre Date: Mon, 1 Jan 2018 13:51:48 +0100 Subject: [PATCH 4/5] Sets UTF-8 as default charset for IPTC + suppress one of the 2 methods for saving TIFF images. --- rtengine/imageio.cc | 380 ++++++++++++++++++++----------------------- rtexif/rtexif.cc | 126 +------------- rtexif/stdattribs.cc | 2 +- 3 files changed, 182 insertions(+), 326 deletions(-) diff --git a/rtengine/imageio.cc b/rtengine/imageio.cc index ba52c4672..3790f00f6 100644 --- a/rtengine/imageio.cc +++ b/rtengine/imageio.cc @@ -75,15 +75,6 @@ FILE* g_fopen_withBinaryAndLock(const Glib::ustring& fname) return f; } -Glib::ustring to_utf8 (const std::string& str) -{ - try { - return Glib::locale_to_utf8 (str); - } catch (Glib::Error&) { - return Glib::convert_with_fallback (str, "UTF-8", "ISO-8859-1", "?"); - } -} - } Glib::ustring ImageIO::errorMsg[6] = {"Success", "Cannot read file.", "Invalid header.", "Error while reading header.", "File reading error", "Image format not supported."}; @@ -136,13 +127,19 @@ void ImageIO::setMetadata (const rtexif::TagDirectory* eroot, const rtengine::pr 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); - std::string loc = 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_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); } @@ -152,8 +149,7 @@ void ImageIO::setMetadata (const rtexif::TagDirectory* eroot, const rtengine::pr 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); - std::string loc = 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_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); } @@ -165,8 +161,7 @@ void ImageIO::setMetadata (const rtexif::TagDirectory* eroot, const rtengine::pr 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 = 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_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); } @@ -1121,13 +1116,13 @@ int ImageIO::saveJPEG (Glib::ustring fname, int quality, int subSamp) int bytes = 0; if (!error && (bytes = iptc_jpeg_ps3_save_iptc (nullptr, 0, iptcdata, size, buffer, 65532)) < 0) { - if (iptcdata) { - iptc_data_free_buf (iptc, iptcdata); - } - error = true; } + if (iptcdata) { + iptc_data_free_buf (iptc, iptcdata); + } + if (!error) { jpeg_write_marker(&cinfo, JPEG_APP0 + 13, buffer, bytes); } @@ -1212,219 +1207,198 @@ int ImageIO::saveTIFF (Glib::ustring fname, int bps, bool uncompressed) 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 = g_fopen_withBinaryAndLock (fname); + // 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 (!file) { - delete [] linebuffer; - return IMIO_CANNOTWRITEFILE; + 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); } - if (pl) { - pl->setProgressStr ("PROGRESSBAR_SAVETIFF"); - pl->setProgress (0.0); + removeTag = cl->getTag (0x9211); + + if (removeTag) { + removeTag->setKeep (false); } - // 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; + // ------------------ Apply list of change ----------------- - if (iptc && iptc_data_save (iptc, &iptcdata, &iptclen) && iptcdata) { + 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; } + } - int size = rtexif::ExifManager::createTIFFHeader (exifRoot, exifChange, width, height, bps, profileData, profileLength, (char*)iptcdata, iptclen, buffer, bufferSize); - - if (iptcdata) { - iptc_data_free_buf (iptc, iptcdata); + if (iptcdata) { + rtexif::Tag* iptcTag = new rtexif::Tag (nullptr, rtexif::lookupAttrib (rtexif::ifdAttribs, "IPTCData")); + iptcTag->initLongArray((char*)iptcdata, iptclen); +#if __BYTE_ORDER__==__ORDER_LITTLE_ENDIAN__ + bool needsReverse = exifRoot && exifRoot->getOrder() == rtexif::MOTOROLA; +#else + bool needsReverse = exifRoot && exifRoot->getOrder() == rtexif::INTEL; +#endif + 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); + } - // The maximum lenght is strangely not the same than for the JPEG file... - // Which maximum length is the good one ? - if (size > 0 && size <= static_cast(bufferSize)) { - fwrite (buffer, size, 1, file); - } + 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_SAMPLEFORMAT, SAMPLEFORMAT_UINT); + TIFFSetField (out, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB); + TIFFSetField (out, TIFFTAG_COMPRESSION, uncompressed ? COMPRESSION_NONE : COMPRESSION_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); + } #if __BYTE_ORDER__==__ORDER_LITTLE_ENDIAN__ - bool needsReverse = (bps == 16 || bps == 32) && exifRoot->getOrder() == rtexif::MOTOROLA; + bool needsReverse = (bps == 16 || bps == 32) && exifRoot->getOrder() == rtexif::MOTOROLA; #else - bool needsReverse = (bps == 16 || bps == 32) && exifRoot->getOrder() == rtexif::INTEL; + bool needsReverse = (bps == 16 || bps == 32) && exifRoot->getOrder() == rtexif::INTEL; #endif - for (int i = 0; i < height; i++) { - getScanline (i, linebuffer, bps); + for (int row = 0; row < height; row++) { + getScanline (row, linebuffer, bps); - if (needsReverse) { - if (bps == 16) { - for (int i = 0; i < lineWidth; i += 2) { - char c = linebuffer[i]; - linebuffer[i] = linebuffer[i + 1]; - linebuffer[i + 1] = c; - } - } else { - for (int i = 0; i < lineWidth; i += 4) { - std::swap(linebuffer[i], linebuffer[i+3]); - std::swap(linebuffer[i+1], linebuffer[i+2]); - } + if (needsReverse) { + if (bps == 16) { + for (int i = 0; i < lineWidth; i += 2) { + char c = linebuffer[i]; + linebuffer[i] = linebuffer[i + 1]; + linebuffer[i + 1] = c; + } + } else { + for (int i = 0; i < lineWidth; i += 4) { + std::swap(linebuffer[i], linebuffer[i+3]); + std::swap(linebuffer[i+1], linebuffer[i+2]); } } - - fwrite (linebuffer, lineWidth, 1, file); - - if (pl && !(i % 100)) { - pl->setProgress ((double)(i + 1) / height); - } } - if(buffer) { - delete [] buffer; - } - - if (ferror(file)) { - writeOk = false; - } - - 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 - 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) { + if (TIFFWriteScanline (out, linebuffer, row, 0) < 0) { + TIFFClose (out); delete [] linebuffer; return IMIO_CANNOTWRITEFILE; } - if (pl) { - pl->setProgressStr ("PROGRESSBAR_SAVETIFF"); - pl->setProgress (0.0); + if (pl && !(row % 100)) { + pl->setProgress ((double)(row + 1) / height); } - - 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 (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 = exifRoot->getTag (TIFFTAG_MODEL)) != nullptr) { - TIFFSetField (out, TIFFTAG_MODEL, tag->getValue()); - } - - if ((tag = exifRoot->getTag (TIFFTAG_MAKE)) != nullptr) { - TIFFSetField (out, TIFFTAG_MAKE, tag->getValue()); - } - - if ((tag = exifRoot->getTag (TIFFTAG_DATETIME)) != nullptr) { - TIFFSetField (out, TIFFTAG_DATETIME, tag->getValue()); - } - - if ((tag = exifRoot->getTag (TIFFTAG_ARTIST)) != nullptr) { - TIFFSetField (out, TIFFTAG_ARTIST, tag->getValue()); - } - - if ((tag = exifRoot->getTag (TIFFTAG_COPYRIGHT)) != nullptr) { - TIFFSetField (out, TIFFTAG_COPYRIGHT, tag->getValue()); - } - - } - - 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_SAMPLEFORMAT, SAMPLEFORMAT_UINT); - TIFFSetField (out, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB); - TIFFSetField (out, TIFFTAG_COMPRESSION, uncompressed ? COMPRESSION_NONE : COMPRESSION_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); - } - -#if __BYTE_ORDER__==__ORDER_LITTLE_ENDIAN__ - bool needsReverse = (bps == 16 || bps == 32) && exifRoot->getOrder() == rtexif::MOTOROLA; -#else - bool needsReverse = (bps == 16 || bps == 32) && exifRoot->getOrder() == rtexif::INTEL; -#endif - - for (int row = 0; row < height; row++) { - getScanline (row, linebuffer, bps); - - if (needsReverse) { - if (bps == 16) { - for (int i = 0; i < lineWidth; i += 2) { - char c = linebuffer[i]; - linebuffer[i] = linebuffer[i + 1]; - linebuffer[i + 1] = c; - } - } else { - for (int i = 0; i < lineWidth; i += 4) { - std::swap(linebuffer[i], linebuffer[i+3]); - std::swap(linebuffer[i+1], linebuffer[i+2]); - } - } - } - - 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 } + if (TIFFFlush(out) != 1) { + writeOk = false; + } + + TIFFClose (out); +#ifdef WIN32 + fclose (file); +#endif + delete [] linebuffer; if (pl) { diff --git a/rtexif/rtexif.cc b/rtexif/rtexif.cc index 60f8b6619..6179688ae 100644 --- a/rtexif/rtexif.cc +++ b/rtexif/rtexif.cc @@ -739,11 +739,12 @@ void TagDirectory::applyChange (std::string name, Glib::ustring value) } else { const TagAttrib* attrib = nullptr; - for (int i = 0; attribs[i].ignore != -1; i++) + for (int i = 0; attribs[i].ignore != -1; i++) { if (!strcmp (attribs[i].name, fseg.c_str())) { attrib = &attribs[i]; break; } + } if (attrib) { Tag* nt = new Tag (this, attrib); @@ -1663,15 +1664,11 @@ void Tag::toString (char* buffer, int ofs) return; } - size_t maxcount = 4; - - if (count < 4) { - maxcount = count; - } + size_t maxcount = rtengine::min(count, 10); strcpy (buffer, ""); - for (ssize_t i = 0; i < std::min(maxcount, valuesize - ofs); i++) { + for (ssize_t i = 0; i < rtengine::min(maxcount, valuesize - ofs); i++) { if (i > 0) { strcat (buffer, ", "); } @@ -3199,121 +3196,6 @@ int ExifManager::createJPEGMarker (const TagDirectory* root, const rtengine::pro return size + 6; } -int ExifManager::createTIFFHeader (const TagDirectory* root, const rtengine::procparams::ExifPairs& changeList, int W, int H, int bps, const char* profiledata, int profilelen, const char* iptcdata, int iptclen, unsigned char *&buffer, unsigned &bufferSize) -{ - -// write tiff header - int offs = 0; - ByteOrder order = HOSTORDER; - - if (root) { - order = root->getOrder (); - } - - TagDirectory* cl; - - if (root) { - cl = (const_cast (root))->clone (nullptr); - // remove some unknown top level tags which produce warnings when opening a tiff - Tag *removeTag = cl->getTag (0x9003); - - if (removeTag) { - removeTag->setKeep (false); - } - - removeTag = cl->getTag (0x9211); - - if (removeTag) { - removeTag->setKeep (false); - } - } else { - cl = new TagDirectory (nullptr, ifdAttribs, HOSTORDER); - } - -// add tiff strip data - int rps = 8; - int strips = ceil ((double)H / rps); - cl->replaceTag (new Tag (cl, lookupAttrib (ifdAttribs, "RowsPerStrip"), rps, LONG)); - Tag* stripBC = new Tag (cl, lookupAttrib (ifdAttribs, "StripByteCounts")); - stripBC->initInt (0, LONG, strips); - cl->replaceTag (stripBC); - Tag* stripOffs = new Tag (cl, lookupAttrib (ifdAttribs, "StripOffsets")); - stripOffs->initInt (0, LONG, strips); - cl->replaceTag (stripOffs); - Tag *sampleFormat = new Tag (cl, lookupAttrib (ifdAttribs, "SampleFormat"), bps == 32 ? 3 : 1, SHORT); - cl->replaceTag (sampleFormat); - - for (int i = 0; i < strips - 1; i++) { - stripBC->setInt (rps * W * 3 * bps / 8, i * 4); - } - - int remaining = (H - rps * floor ((double)H / rps)) * W * 3 * bps / 8; - - if (remaining) { - stripBC->setInt (remaining, (strips - 1) * 4); - } else { - stripBC->setInt (rps * W * 3 * bps / 8, (strips - 1) * 4); - } - - if (profiledata) { - Tag* icc = new Tag (cl, lookupAttrib (ifdAttribs, "ICCProfile")); - icc->initUndefArray (profiledata, profilelen); - cl->replaceTag (icc); - } - - if (iptcdata) { - Tag* iptc = new Tag (cl, lookupAttrib (ifdAttribs, "IPTCData")); - iptc->initLongArray (iptcdata, iptclen); - cl->replaceTag (iptc); - } - -// apply list of changes - for (rtengine::procparams::ExifPairs::const_iterator i = changeList.begin(); i != changeList.end(); ++i) { - cl->applyChange (i->first, i->second); - } - - // append default properties - const std::vector defTags = getDefaultTIFFTags (cl); - - defTags[0]->setInt (W, 0, LONG); - defTags[1]->setInt (H, 0, LONG); - defTags[8]->initInt (0, SHORT, 3); - - for (int i = 0; i < 3; i++) { - defTags[8]->setInt (bps, i * 2, SHORT); - } - - for (int i = defTags.size() - 1; i >= 0; i--) { - Tag* defTag = defTags[i]; - cl->replaceTag (defTag->clone (cl)); - delete defTag; - } - -// calculate strip offsets - int size = cl->calculateSize (); - int byps = bps / 8; - - for (int i = 0; i < strips; i++) { - stripOffs->setInt (size + 8 + i * rps * W * 3 * byps, i * 4); - } - - cl->sort (); - bufferSize = cl->calculateSize() + 8; - buffer = new unsigned char[bufferSize]; // this has to be deleted in caller - sset2 ((unsigned short)order, buffer + offs, order); - offs += 2; - sset2 (42, buffer + offs, order); - offs += 2; - sset4 (8, buffer + offs, order); - - int endOffs = cl->write (8, buffer); - -// cl->printAll(); - delete cl; - - return endOffs; -} - //----------------------------------------------------------------------------- // global functions to read byteorder dependent data //----------------------------------------------------------------------------- diff --git a/rtexif/stdattribs.cc b/rtexif/stdattribs.cc index a7e3fe00f..4982b0b81 100644 --- a/rtexif/stdattribs.cc +++ b/rtexif/stdattribs.cc @@ -809,10 +809,10 @@ const TagAttrib ifdAttribs[] = { {0, AC_WRITE, 0, nullptr, 0x828e, AUTO, "CFAPattern", &cfaInterpreter}, {0, AC_WRITE, 0, kodakIfdAttribs, 0x8290, AUTO, "KodakIFD", &stdInterpreter}, {0, AC_WRITE, 1, nullptr, 0x8298, AUTO, "Copyright", &stdInterpreter}, + {0, AC_SYSTEM, 0, nullptr, 0x83BB, AUTO, "IPTCData", &stdInterpreter}, {0, AC_DONTWRITE, 0, nullptr, 0x8606, AUTO, "LeafData", &stdInterpreter}, // is actually a subdir, but a proprietary format {0, AC_WRITE, 0, exifAttribs, 0x8769, AUTO, "Exif", &stdInterpreter}, {0, AC_SYSTEM, 0, nullptr, 0x8773, AUTO, "ICCProfile", &stdInterpreter}, - {0, AC_SYSTEM, 0, nullptr, 0x83BB, AUTO, "IPTCData", &stdInterpreter}, {0, AC_WRITE, 0, gpsAttribs, 0x8825, AUTO, "GPSInfo", &stdInterpreter}, {0, AC_WRITE, 0, nullptr, 0x9003, AUTO, "DateTimeOriginal", &stdInterpreter}, {0, AC_WRITE, 0, nullptr, 0x9004, AUTO, "DateTimeDigitized", &stdInterpreter}, From 9d2ccc19d3eecbbe6a7ee237b222ba6f8a583b8e Mon Sep 17 00:00:00 2001 From: Hombre Date: Mon, 1 Jan 2018 14:24:47 +0100 Subject: [PATCH 5/5] Better UNICODE support (UCS-2/UTF-8) for Exif.UserComment (#2017) - BOM is now checked and correctly handled - auto-detection of UTF-8 string if no BOM available, otherwise assume it's an UCS-2/UTF-16 string - try to autodetect endianess of UTF-16 string by counting zeros - possibility to enable writing BOM for this field (disabled for now) - for undefined charset (empty identifier), RT now assume that the string is what glib think is the local charset (not tested), and try to convert it to UTF-8 for display/editing JIS is still not handled though. --- rtexif/rtexif.cc | 48 ++++++++++++++----- rtexif/rtexif.h | 2 + rtexif/stdattribs.cc | 110 +++++++++++++++++++++++++++++++++++++++---- 3 files changed, 140 insertions(+), 20 deletions(-) diff --git a/rtexif/rtexif.cc b/rtexif/rtexif.cc index 6179688ae..3547f6ee3 100644 --- a/rtexif/rtexif.cc +++ b/rtexif/rtexif.cc @@ -1928,24 +1928,48 @@ void Tag::initInt (int data, TagType t, int cnt) setInt (data, 0, t); } +void Tag::swapByteOrder2(char *buffer, int count) +{ + char* ptr = buffer; + for (int i = 0; i < count; i+=2) { + unsigned char c = ptr[0]; + ptr[0] = ptr[1]; + ptr[1] = c; + ptr += 2; + } +} void Tag::initUserComment (const Glib::ustring &text) { + const bool useBOM = false; // set it to true if you want to output BOM in UCS-2/UTF-8 UserComments ; this could be turned to an options entry type = UNDEFINED; if (text.is_ascii()) { - count = 8 + strlen (text.c_str()); - valuesize = count; + valuesize = count = 8 + strlen (text.c_str()); value = new unsigned char[valuesize]; - strcpy ((char*)value, "ASCII"); - value[5] = value[6] = value[7] = 0; - strcpy ((char*)value + 8, text.c_str()); + memcpy(value, "ASCII\0\0\0", 8); + memcpy(value + 8, text.c_str(), valuesize - 8); } else { - wchar_t *commentStr = (wchar_t*)g_utf8_to_utf16 (text.c_str(), -1, NULL, NULL, NULL); - count = 8 + wcslen(commentStr)*2; - valuesize = count; - value = (unsigned char*)new char[valuesize]; - strcpy ((char*)value, "UNICODE"); - value[7] = 0; - wcscpy(((wchar_t*)value) + 4, commentStr); + wchar_t *commentStr = (wchar_t*)g_utf8_to_utf16 (text.c_str(), -1, nullptr, nullptr, nullptr); + size_t wcStrSize = wcslen(commentStr); + valuesize = count = wcStrSize * 2 + 8 + (useBOM ? 2 : 0); + value = new unsigned char[valuesize]; + memcpy(value, "UNICODE\0", 8); + + if (useBOM) { + if (getOrder() == INTEL) { //Little Endian + value[8] = 0xFF; + value[9] = 0xFE; + } else { + value[8] = 0xFE; + value[9] = 0xFF; + } + } + + // Swapping byte order to match the Exif's byte order + if (getOrder() != HOSTORDER) { + swapByteOrder2((char*)commentStr, wcStrSize * 2); + } + + memcpy(value + 8 + (useBOM ? 2 : 0), (char*)commentStr, wcStrSize * 2); g_free(commentStr); } } diff --git a/rtexif/rtexif.h b/rtexif/rtexif.h index 125d38c94..452cedba1 100644 --- a/rtexif/rtexif.h +++ b/rtexif/rtexif.h @@ -236,6 +236,8 @@ public: void initLongArray (const char* data, int len); void initRational (int num, int den); + static void swapByteOrder2 (char *buffer, int count); + // get basic tag properties int getID () const { diff --git a/rtexif/stdattribs.cc b/rtexif/stdattribs.cc index 4982b0b81..ca19b7f1c 100644 --- a/rtexif/stdattribs.cc +++ b/rtexif/stdattribs.cc @@ -452,12 +452,109 @@ public: } count = std::min (count, 65535); // limit to 65535 chars to avoid crashes in case of corrupted metadata - char *buffer = new char[count - 7]; + char *buffer = new char[count - 6]; // include 2 ending null chars for UCS-2 string (possibly) + char *value = (char*)t->getValue(); - if (!memcmp ((char*)t->getValue(), "ASCII\0\0\0", 8)) { - strncpy (buffer, (char*)t->getValue() + 8, count - 8); + if (!memcmp(value, "ASCII\0\0\0", 8)) { + memcpy(buffer, value + 8, count - 8); buffer[count - 8] = '\0'; + } else if (!memcmp(value, "UNICODE\0", 8)) { + memcpy(buffer, value + 8, count - 8); + buffer[count - 7] = buffer[count - 8] = '\0'; + Glib::ustring tmp1(buffer); + + + bool hasBOM = false; + enum ByteOrder bo = UNKNOWN; + if (count % 2 || (count >= 11 && (buffer[0] == 0xEF && buffer[1] == 0xBB && buffer[2] == 0xBF))) { + // odd string length can only be UTF-8, don't change anything + std::string retVal (buffer + 3); + delete [] buffer; + return retVal; + } else if (count >= 10) { + if (buffer[0] == 0xFF && buffer[1] == 0xFE) { + bo = INTEL; // little endian + hasBOM = true; + } else if (buffer[0] == 0xFE && buffer[1] == 0xFF) { + bo = MOTOROLA; // big endian + hasBOM = true; + } + } + if (bo == UNKNOWN) { + // auto-detecting byte order; we still don't know if it's UCS-2 or UTF-8 + int a = 0, b = 0, c = 0, d = 0; + for (int j = 8; j < count; j++) { + char cc = value[j]; + if (!(j%2)) { + // counting zeros for first byte + if (!cc) { + ++a; + } + } else { + // counting zeros for second byte + if (!cc) { + ++b; + } + } + if (!(cc & 0x80) || ((cc & 0xC0) == 0xC0) || ((cc & 0xC0) == 0x80)) { + ++c; + } + if ((cc & 0xC0) == 0x80) { + ++d; + } + } + if (c == (count - 8) && d) { + // this is an UTF-8 string + std::string retVal (buffer); + delete [] buffer; + return retVal; + } + if ((a || b) && a != b) { + bo = a > b ? MOTOROLA : INTEL; + } + } + if (bo == UNKNOWN) { + // assuming platform's byte order +#if __BYTE_ORDER__==__ORDER_LITTLE_ENDIAN__ + bo = INTEL; +#else + bo = MOTOROLA; +#endif + } + + // now swapping if necessary + if (!hasBOM && bo != HOSTORDER) { + if (t->getOrder() != HOSTORDER) { + Tag::swapByteOrder2(buffer, count - 8); + } + } + + glong written; + char* utf8Str = g_utf16_to_utf8((unsigned short int*)buffer, -1, nullptr, &written, nullptr); + delete [] buffer; + buffer = new char[written + 1]; + memcpy(buffer, utf8Str, written); + buffer[written] = 0; + } else if (!memcmp(value, "\0\0\0\0\0\0\0\0", 8)) { + // local charset string, whatever it is + memcpy(buffer, value + 8, count - 8); + buffer[count - 7] = buffer[count - 8] = '\0'; + + gsize written = 0; + char *utf8Str = g_locale_to_utf8(buffer, count - 8, nullptr, &written, nullptr); + if (utf8Str && written) { + delete [] buffer; + size_t length = strlen(utf8Str); + buffer = new char[length + 1]; + strcpy(buffer, utf8Str); + } else { + buffer[0] = 0; + } + if (utf8Str) { + g_free(utf8Str); + } } else { + // JIS: unsupported buffer[0] = 0; } @@ -467,11 +564,8 @@ public: } virtual void fromString (Tag* t, const std::string& value) { - char *buffer = new char[t->getCount()]; - memcpy (buffer, "ASCII\0\0\0", 8); - strcpy (buffer + 8, value.c_str()); - t->fromString (buffer, value.size() + 9); - delete [] buffer; + Glib::ustring tmpStr(value); + t->userCommentFromString (tmpStr); } }; UserCommentInterpreter userCommentInterpreter;