/* * This file is part of RawTherapee. * * Copyright (c) 2004-2010 Gabor Horvath * * RawTherapee is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * RawTherapee is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with RawTherapee. If not, see . */ #include "rtengine.h" #include "rtthumbnail.h" #include "../rtgui/options.h" #include "image8.h" #include #include "curves.h" #include #include "improcfun.h" #include "colortemp.h" #include "mytime.h" #include "utils.h" #include "iccstore.h" #include "iccmatrices.h" #include "rawimagesource.h" #include "stdimagesource.h" #include #include #include "safekeyfile.h" #include "safegtk.h" #include "rawimage.h" #include "jpeg.h" #include "../rtgui/ppversion.h" #include "improccoordinator.h" #include extern Options options; namespace rtengine { using namespace procparams; Thumbnail* Thumbnail::loadFromImage (const Glib::ustring& fname, int &w, int &h, int fixwh, double wbEq, bool inspectorMode) { StdImageSource imgSrc; if (imgSrc.load(fname)) { return NULL; } ImageIO* img = imgSrc.getImageIO(); Thumbnail* tpp = new Thumbnail (); unsigned char* data; img->getEmbeddedProfileData (tpp->embProfileLength, data); if (data && tpp->embProfileLength) { tpp->embProfileData = new unsigned char [tpp->embProfileLength]; memcpy (tpp->embProfileData, data, tpp->embProfileLength); } tpp->scaleForSave = 8192; tpp->defGain = 1.0; tpp->gammaCorrected = false; tpp->isRaw = 0; memset (tpp->colorMatrix, 0, sizeof(tpp->colorMatrix)); tpp->colorMatrix[0][0] = 1.0; tpp->colorMatrix[1][1] = 1.0; tpp->colorMatrix[2][2] = 1.0; if (inspectorMode) { // Special case, meaning that we want a full sized thumbnail image (e.g. for the Inspector feature) w = img->width; h = img->height; tpp->scale = 1.; } else { if (fixwh == 1) { w = h * img->width / img->height; tpp->scale = (double)img->height / h; } else { h = w * img->height / img->width; tpp->scale = (double)img->width / w; } } // bilinear interpolation if (tpp->thumbImg) { delete tpp->thumbImg; tpp->thumbImg = NULL; } if (inspectorMode) { // we want an Image8 if (img->getType() == rtengine::sImage8) { // copy the image Image8 *srcImg = static_cast(img); Image8 *thImg = new Image8 (w, h); srcImg->copyData(thImg); tpp->thumbImg = thImg; } else { // copy the image with a conversion tpp->thumbImg = resizeTo(w, h, TI_Bilinear, img); } } else { // we want the same image type than the source file tpp->thumbImg = resizeToSameType(w, h, TI_Bilinear, img); // histogram computation tpp->aeHistCompression = 3; tpp->aeHistogram(65536 >> tpp->aeHistCompression); double avg_r = 0; double avg_g = 0; double avg_b = 0; int n = 0; if (img->getType() == rtengine::sImage8) { Image8 *image = static_cast(img); image->computeHistogramAutoWB(avg_r, avg_g, avg_b, n, tpp->aeHistogram, tpp->aeHistCompression); } else if (img->getType() == sImage16) { Image16 *image = static_cast(img); image->computeHistogramAutoWB(avg_r, avg_g, avg_b, n, tpp->aeHistogram, tpp->aeHistCompression); } else if (img->getType() == sImagefloat) { Imagefloat *image = static_cast(img); image->computeHistogramAutoWB(avg_r, avg_g, avg_b, n, tpp->aeHistogram, tpp->aeHistCompression); } else { printf("loadFromImage: Unsupported image type \"%s\"!\n", img->getType()); } if (n > 0) { ColorTemp cTemp; tpp->redAWBMul = avg_r / double(n); tpp->greenAWBMul = avg_g / double(n); tpp->blueAWBMul = avg_b / double(n); tpp->wbEqual = wbEq; cTemp.mul2temp (tpp->redAWBMul, tpp->greenAWBMul, tpp->blueAWBMul, tpp->wbEqual, tpp->autoWBTemp, tpp->autoWBGreen); } tpp->init (); } return tpp; } Thumbnail* Thumbnail::loadQuickFromRaw (const Glib::ustring& fname, RawMetaDataLocation& rml, int &w, int &h, int fixwh, bool rotate, bool inspectorMode) { RawImage *ri = new RawImage(fname); int r = ri->loadRaw(false, false); if( r ) { delete ri; return NULL; } rml.exifBase = ri->get_exifBase(); rml.ciffBase = ri->get_ciffBase(); rml.ciffLength = ri->get_ciffLen(); Image8* img = new Image8 (); // No sample format detection occurred earlier, so we set them here, // as they are mandatory for the setScanline method img->setSampleFormat(IIOSF_UNSIGNED_CHAR); img->setSampleArrangement(IIOSA_CHUNKY); int err = 1; // see if it is something we support if ( ri->is_supportedThumb() ) { const char* data((const char*)fdata(ri->get_thumbOffset(), ri->get_file())); if ( (unsigned char)data[1] == 0xd8 ) { err = img->loadJPEGFromMemory(data, ri->get_thumbLength()); } else { err = img->loadPPMFromMemory(data, ri->get_thumbWidth(), ri->get_thumbHeight(), ri->get_thumbSwap(), ri->get_thumbBPS()); } } // did we succeed? if ( err ) { printf("Could not extract thumb from %s\n", fname.data()); delete img; delete ri; return NULL; } Thumbnail* tpp = new Thumbnail (); tpp->isRaw = 1; memset (tpp->colorMatrix, 0, sizeof(tpp->colorMatrix)); tpp->colorMatrix[0][0] = 1.0; tpp->colorMatrix[1][1] = 1.0; tpp->colorMatrix[2][2] = 1.0; if (inspectorMode) { // Special case, meaning that we want a full sized thumbnail image (e.g. for the Inspector feature) w = img->width; h = img->height; tpp->scale = 1.; } else { if (fixwh == 1) { w = h * img->width / img->height; tpp->scale = (double)img->height / h; } else { h = w * img->height / img->width; tpp->scale = (double)img->width / w; } } if (tpp->thumbImg) { delete tpp->thumbImg; tpp->thumbImg = NULL; } if (inspectorMode) { tpp->thumbImg = img; } else { tpp->thumbImg = resizeTo(w, h, TI_Nearest, img); delete img; } if (rotate && ri->get_rotateDegree() > 0) { std::string fname = ri->get_filename(); std::string suffix = fname.length() > 4 ? fname.substr(fname.length() - 3) : ""; for (int i = 0; i < suffix.length(); i++) { suffix[i] = std::tolower(suffix[i]); } // Leaf .mos, Mamiya .mef and Phase One .iiq files have thumbnails already rotated. if (suffix != "mos" && suffix != "mef" && suffix != "iiq") { tpp->thumbImg->rotate(ri->get_rotateDegree()); // width/height may have changed after rotating w = tpp->thumbImg->width; h = tpp->thumbImg->height; } } if (!inspectorMode) { tpp->init (); } delete ri; return tpp; } #define FISRED(filter,row,col) \ ((filter >> ((((row) << 1 & 14) + ((col) & 1)) << 1) & 3)==0 || !filter) #define FISGREEN(filter,row,col) \ ((filter >> ((((row) << 1 & 14) + ((col) & 1)) << 1) & 3)==1 || !filter) #define FISBLUE(filter,row,col) \ ((filter >> ((((row) << 1 & 14) + ((col) & 1)) << 1) & 3)==2 || !filter) RawMetaDataLocation Thumbnail::loadMetaDataFromRaw (const Glib::ustring& fname) { RawMetaDataLocation rml; rml.exifBase = -1; rml.ciffBase = -1; rml.ciffLength = -1; RawImage ri(fname); int r = ri.loadRaw(false); if( !r ) { rml.exifBase = ri.get_exifBase(); rml.ciffBase = ri.get_ciffBase(); rml.ciffLength = ri.get_ciffLen(); } return rml; } Thumbnail* Thumbnail::loadFromRaw (const Glib::ustring& fname, RawMetaDataLocation& rml, int &w, int &h, int fixwh, double wbEq, bool rotate) { RawImage *ri = new RawImage (fname); int r = ri->loadRaw(1, 0); if( r ) { delete ri; return NULL; } int width = ri->get_width(); int height = ri->get_height(); rtengine::Thumbnail* tpp = new rtengine::Thumbnail; tpp->isRaw = true; tpp->embProfile = NULL; tpp->embProfileData = NULL; tpp->embProfileLength = ri->get_profileLen(); if (ri->get_profileLen()) tpp->embProfile = cmsOpenProfileFromMem(ri->get_profile(), ri->get_profileLen()); //\ TODO check if mutex is needed tpp->redMultiplier = ri->get_pre_mul(0); tpp->greenMultiplier = ri->get_pre_mul(1); tpp->blueMultiplier = ri->get_pre_mul(2); ri->scale_colors(); ri->pre_interpolate(); rml.exifBase = ri->get_exifBase(); rml.ciffBase = ri->get_ciffBase(); rml.ciffLength = ri->get_ciffLen(); tpp->camwbRed = tpp->redMultiplier / ri->get_pre_mul(0); tpp->camwbGreen = tpp->greenMultiplier / ri->get_pre_mul(1); tpp->camwbBlue = tpp->blueMultiplier / ri->get_pre_mul(2); tpp->defGain = 1.0 / min(ri->get_pre_mul(0), ri->get_pre_mul(1), ri->get_pre_mul(2)); tpp->gammaCorrected = true; unsigned filter = ri->get_filters(); int firstgreen = 1; // locate first green location in the first row if(ri->getSensorType() == ST_BAYER) while (!FISGREEN(filter, 1, firstgreen)) { firstgreen++; } int skip = 1; if (ri->get_FujiWidth() != 0) { if (fixwh == 1) { // fix height, scale width skip = ((ri->get_height() - ri->get_FujiWidth()) / sqrt(0.5) - firstgreen - 1) / h; } else { skip = (ri->get_FujiWidth() / sqrt(0.5) - firstgreen - 1) / w; } } else { if (fixwh == 1) { // fix height, scale width skip = (ri->get_height() - firstgreen - 1) / h; } else { skip = (ri->get_width() - firstgreen - 1) / w; } } if (skip % 2) { skip--; } if (skip < 1) { skip = 1; } int hskip = skip, vskip = skip; if (!ri->get_model().compare("D1X")) { hskip *= 2; } int rofs = 0; int tmpw = (width - 2) / hskip; int tmph = (height - 2) / vskip; DCraw::dcrawImage_t image = ri->get_image(); Imagefloat* tmpImg = new Imagefloat(tmpw, tmph); if (ri->getSensorType() == ST_BAYER) { // demosaicing! (sort of) for (int row = 1, y = 0; row < height - 1 && y < tmph; row += vskip, y++) { rofs = row * width; for (int col = firstgreen, x = 0; col < width - 1 && x < tmpw; col += hskip, x++) { int ofs = rofs + col; int g = image[ofs][1]; int r, b; if (FISRED(filter, row, col + 1)) { r = (image[ofs + 1 ][0] + image[ofs - 1 ][0]) >> 1; b = (image[ofs + width][2] + image[ofs - width][2]) >> 1; } else { b = (image[ofs + 1 ][2] + image[ofs - 1 ][2]) >> 1; r = (image[ofs + width][0] + image[ofs - width][0]) >> 1; } tmpImg->r(y, x) = r; tmpImg->g(y, x) = g; tmpImg->b(y, x) = b; } } } else if (ri->get_colors() == 1) { for (int row = 1, y = 0; row < height - 1 && y < tmph; row += vskip, y++) { rofs = row * width; for (int col = firstgreen, x = 0; col < width - 1 && x < tmpw; col += hskip, x++) { int ofs = rofs + col; tmpImg->r(y, x) = tmpImg->g(y, x) = tmpImg->b(y, x) = image[ofs][0]; } } } else { if(ri->getSensorType() == ST_FUJI_XTRANS) { for( int row = 1, y = 0; row < height - 1 && y < tmph; row += vskip, y++) { rofs = row * width; for( int col = 1, x = 0; col < width - 1 && x < tmpw; col += hskip, x++ ) { int ofs = rofs + col; float sum[3] = {}; int c; for(int v = -1; v <= 1; v++) { for(int h = -1; h <= 1; h++) { c = ri->XTRANSFC(row + v, col + h); sum[c] += image[ofs + v * width + h][c]; } } c = ri->XTRANSFC(row, col); switch (c) { case 0: tmpImg->r(y, x) = image[ofs][0]; tmpImg->g(y, x) = sum[1] / 5.f; tmpImg->b(y, x) = sum[2] / 3.f; break; case 1: tmpImg->r(y, x) = sum[0] / 2.f; tmpImg->g(y, x) = image[ofs][1]; tmpImg->b(y, x) = sum[2] / 2.f; break; case 2: tmpImg->r(y, x) = sum[0] / 3.f; tmpImg->g(y, x) = sum[1] / 5.f; tmpImg->b(y, x) = image[ofs][2]; break; } } } } else { int iwidth = ri->get_iwidth(); int iheight = ri->get_iheight(); int left_margin = ri->get_leftmargin(); firstgreen += left_margin; int top_margin = ri->get_topmargin(); for (int row = 1 + top_margin, y = 0; row < iheight + top_margin - 1 && y < tmph; row += vskip, y++) { rofs = row * iwidth; for (int col = firstgreen, x = 0; col < iwidth + left_margin - 1 && x < tmpw; col += hskip, x++) { int ofs = rofs + col; tmpImg->r(y, x) = image[ofs][0]; tmpImg->g(y, x) = image[ofs][1]; tmpImg->b(y, x) = image[ofs][2]; } } } } if (ri->get_FujiWidth() != 0) { int fw = ri->get_FujiWidth() / hskip; double step = sqrt(0.5); int wide = fw / step; int high = (tmph - fw) / step; Imagefloat* fImg = new Imagefloat(wide, high); float r, c; for (int row = 0; row < high; row++) for (int col = 0; col < wide; col++) { unsigned ur = r = fw + (row - col) * step; unsigned uc = c = (row + col) * step; if (ur > tmph - 2 || uc > tmpw - 2) { continue; } double fr = r - ur; double fc = c - uc; fImg->r(row, col) = (tmpImg->r(ur, uc) * (1 - fc) + tmpImg->r(ur, uc + 1) * fc) * (1 - fr) + (tmpImg->r(ur + 1, uc) * (1 - fc) + tmpImg->r(ur + 1, uc + 1) * fc) * fr; fImg->g(row, col) = (tmpImg->g(ur, uc) * (1 - fc) + tmpImg->g(ur, uc + 1) * fc) * (1 - fr) + (tmpImg->g(ur + 1, uc) * (1 - fc) + tmpImg->g(ur + 1, uc + 1) * fc) * fr; fImg->b(row, col) = (tmpImg->b(ur, uc) * (1 - fc) + tmpImg->b(ur, uc + 1) * fc) * (1 - fr) + (tmpImg->b(ur + 1, uc) * (1 - fc) + tmpImg->b(ur + 1, uc + 1) * fc) * fr; } delete tmpImg; tmpImg = fImg; tmpw = wide; tmph = high; } if (fixwh == 1) { // fix height, scale width w = tmpw * h / tmph; } else { h = tmph * w / tmpw; } if (tpp->thumbImg) { delete tpp->thumbImg; } tpp->thumbImg = NULL; tpp->thumbImg = resizeTo(w, h, TI_Bilinear, tmpImg); delete tmpImg; if (ri->get_FujiWidth() != 0) { tpp->scale = (double) (height - ri->get_FujiWidth()) / sqrt(0.5) / h; } else { tpp->scale = (double) height / h; } // generate histogram for auto exposure tpp->aeHistCompression = 3; tpp->aeHistogram(65536 >> tpp->aeHistCompression); tpp->aeHistogram.clear(); int radd = 4; int gadd = 4; int badd = 4; if (!filter) { radd = gadd = badd = 1; } for (int i = 8; i < height - 8; i++) { int start, end; if (ri->get_FujiWidth() != 0) { int fw = ri->get_FujiWidth(); start = ABS(fw - i) + 8; end = min(height + width - fw - i, fw + i) - 8; } else { start = 8; end = width - 8; } if (ri->get_colors() == 1) { for (int j = start; j < end; j++) { tpp->aeHistogram[((int)(image[i * width + j][0])) >> tpp->aeHistCompression] += radd; tpp->aeHistogram[((int)(image[i * width + j][0])) >> tpp->aeHistCompression] += gadd; tpp->aeHistogram[((int)(image[i * width + j][0])) >> tpp->aeHistCompression] += badd; } } else if(ri->getSensorType() == ST_BAYER) { for (int j = start; j < end; j++) if (FISGREEN(filter, i, j)) { tpp->aeHistogram[((int)(tpp->camwbGreen * image[i * width + j][1])) >> tpp->aeHistCompression] += gadd; } else if (FISRED(filter, i, j)) { tpp->aeHistogram[((int)(tpp->camwbRed * image[i * width + j][0])) >> tpp->aeHistCompression] += radd; } else if (FISBLUE(filter, i, j)) { tpp->aeHistogram[((int)(tpp->camwbBlue * image[i * width + j][2])) >> tpp->aeHistCompression] += badd; } } else if(ri->getSensorType() == ST_FUJI_XTRANS) { for (int j = start; j < end; j++) if (ri->ISXTRANSGREEN(i, j)) { tpp->aeHistogram[((int)(tpp->camwbGreen * image[i * width + j][1])) >> tpp->aeHistCompression] += gadd; } else if (ri->ISXTRANSRED(i, j)) { tpp->aeHistogram[((int)(tpp->camwbRed * image[i * width + j][0])) >> tpp->aeHistCompression] += radd; } else if (ri->ISXTRANSBLUE(i, j)) { tpp->aeHistogram[((int)(tpp->camwbBlue * image[i * width + j][2])) >> tpp->aeHistCompression] += badd; } } else { /* if(ri->getSensorType()==ST_FOVEON) */ for (int j = start; j < end; j++) { tpp->aeHistogram[((int)(image[i * width + j][0] * 2.f)) >> tpp->aeHistCompression] += radd; tpp->aeHistogram[((int)(image[i * width + j][1])) >> tpp->aeHistCompression] += gadd; tpp->aeHistogram[((int)(image[i * width + j][2] * 0.5f)) >> tpp->aeHistCompression] += badd; } } } // generate autoWB double avg_r = 0; double avg_g = 0; double avg_b = 0; const float eps = 1e-5; //tolerance to avoid dividing by zero float rn = eps, gn = eps, bn = eps; for (int i = 32; i < height - 32; i++) { int start, end; if (ri->get_FujiWidth() != 0) { int fw = ri->get_FujiWidth(); start = ABS(fw - i) + 32; end = min(height + width - fw - i, fw + i) - 32; } else { start = 32; end = width - 32; } if(ri->getSensorType() == ST_BAYER) { for (int j = start; j < end; j++) { if (!filter) { double d = tpp->defGain * image[i * width + j][0]; if (d > 64000.) { continue; } avg_g += d; avg_r += d; avg_b += d; rn++; gn++; bn++; } else if (FISGREEN(filter, i, j)) { double d = tpp->defGain * image[i * width + j][1]; if (d > 64000.) { continue; } avg_g += d; gn++; } else if (FISRED(filter, i, j)) { double d = tpp->defGain * image[i * width + j][0]; if (d > 64000.) { continue; } avg_r += d; rn++; } else if (FISBLUE(filter, i, j)) { double d = tpp->defGain * image[i * width + j][2]; if (d > 64000.) { continue; } avg_b += d; bn++; } } } else if(ri->getSensorType() == ST_FUJI_XTRANS) { for (int j = start; j < end; j++) { if (ri->ISXTRANSGREEN(i, j)) { double d = tpp->defGain * image[i * width + j][1]; if (d > 64000.) { continue; } avg_g += d; gn++; } else if (ri->ISXTRANSRED(i, j)) { double d = tpp->defGain * image[i * width + j][0]; if (d > 64000.) { continue; } avg_r += d; rn++; } else if (ri->ISXTRANSBLUE(i, j)) { double d = tpp->defGain * image[i * width + j][2]; if (d > 64000.) { continue; } avg_b += d; bn++; } } } else { /* if(ri->getSensorType()==ST_FOVEON) */ for (int j = start; j < end; j++) { double d = tpp->defGain * image[i * width + j][0]; if (d <= 64000.) { avg_r += d; rn++; } d = tpp->defGain * image[i * width + j][1]; if (d <= 64000.) { avg_g += d; gn++; } d = tpp->defGain * image[i * width + j][2]; if (d <= 64000.) { avg_b += d; bn++; } } } } double reds = avg_r / rn * tpp->camwbRed; double greens = avg_g / gn * tpp->camwbGreen; double blues = avg_b / bn * tpp->camwbBlue; tpp->redAWBMul = ri->get_rgb_cam(0, 0) * reds + ri->get_rgb_cam(0, 1) * greens + ri->get_rgb_cam(0, 2) * blues; tpp->greenAWBMul = ri->get_rgb_cam(1, 0) * reds + ri->get_rgb_cam(1, 1) * greens + ri->get_rgb_cam(1, 2) * blues; tpp->blueAWBMul = ri->get_rgb_cam(2, 0) * reds + ri->get_rgb_cam(2, 1) * greens + ri->get_rgb_cam(2, 2) * blues; tpp->wbEqual = wbEq; ColorTemp cTemp; cTemp.mul2temp(tpp->redAWBMul, tpp->greenAWBMul, tpp->blueAWBMul, tpp->wbEqual, tpp->autoWBTemp, tpp->autoWBGreen); if (rotate && ri->get_rotateDegree() > 0) { tpp->thumbImg->rotate(ri->get_rotateDegree()); } for (int a = 0; a < 3; a++) for (int b = 0; b < 3; b++) { tpp->colorMatrix[a][b] = ri->get_rgb_cam(a, b); } tpp->init(); delete ri; return tpp; } #undef FISRED #undef FISGREEN #undef FISBLUE unsigned short *Thumbnail::igammatab = 0; unsigned char *Thumbnail::gammatab = 0; void Thumbnail::initGamma () { igammatab = new unsigned short[256]; gammatab = new unsigned char[65536]; for (int i = 0; i < 256; i++) { igammatab[i] = (unsigned short)(255.0 * pow((double)i / 255.0, Color::sRGBGamma)); } for (int i = 0; i < 65536; i++) { gammatab[i] = (unsigned char)(255.0 * pow((double)i / 65535.0, 1.f / Color::sRGBGamma)); } } void Thumbnail::cleanupGamma () { delete [] igammatab; delete [] gammatab; } void Thumbnail::init () { RawImageSource::inverse33 (colorMatrix, iColorMatrix); //colorMatrix is rgb_cam memset (cam2xyz, 0, sizeof(cam2xyz)); for (int i = 0; i < 3; i++) for (int j = 0; j < 3; j++) for (int k = 0; k < 3; k++) { cam2xyz[i][j] += xyz_sRGB[i][k] * colorMatrix[k][j]; } camProfile = iccStore->createFromMatrix (cam2xyz, false, "Camera"); } Thumbnail::Thumbnail () : camProfile(NULL), thumbImg(NULL), camwbRed(1.0), camwbGreen(1.0), camwbBlue(1.0), redAWBMul(-1.0), greenAWBMul(-1.0), blueAWBMul(-1.0), autoWBTemp(2700), autoWBGreen(1.0), wbEqual(-1.0), embProfileLength(0), embProfileData(NULL), embProfile(NULL), redMultiplier(1.0), greenMultiplier(1.0), blueMultiplier(1.0), defGain(1.0), scaleForSave(8192), gammaCorrected(false), aeHistCompression(3) { } Thumbnail::~Thumbnail () { delete thumbImg; //delete [] aeHistogram; delete [] embProfileData; if (embProfile) { cmsCloseProfile(embProfile); } if (camProfile) { cmsCloseProfile(camProfile); } } // Simple processing of RAW internal JPGs IImage8* Thumbnail::quickProcessImage (const procparams::ProcParams& params, int rheight, rtengine::TypeInterpolation interp, double& myscale) { int rwidth; if (params.coarse.rotate == 90 || params.coarse.rotate == 270) { rwidth = rheight; rheight = thumbImg->height * rwidth / thumbImg->width; } else { rwidth = thumbImg->width * rheight / thumbImg->height; } Image8* baseImg = resizeTo(rwidth, rheight, interp, thumbImg); if (params.coarse.rotate) { baseImg->rotate (params.coarse.rotate); } if (params.coarse.hflip) { baseImg->hflip (); } if (params.coarse.vflip) { baseImg->vflip (); } return baseImg; } // Full thumbnail processing, second stage if complete profile exists IImage8* Thumbnail::processImage (const procparams::ProcParams& params, int rheight, TypeInterpolation interp, std::string camName, double focalLen, double focalLen35mm, float focusDist, float shutter, float fnumber, float iso, std::string expcomp_, double& myscale) { // check if the WB's equalizer value has changed if (wbEqual < (params.wb.equal - 5e-4) || wbEqual > (params.wb.equal + 5e-4)) { wbEqual = params.wb.equal; // recompute the autoWB ColorTemp cTemp; cTemp.mul2temp (redAWBMul, greenAWBMul, blueAWBMul, wbEqual, autoWBTemp, autoWBGreen); } // compute WB multipliers ColorTemp currWB = ColorTemp (params.wb.temperature, params.wb.green, params.wb.equal, params.wb.method); if (params.wb.method == "Camera") { //recall colorMatrix is rgb_cam double cam_r = colorMatrix[0][0] * camwbRed + colorMatrix[0][1] * camwbGreen + colorMatrix[0][2] * camwbBlue; double cam_g = colorMatrix[1][0] * camwbRed + colorMatrix[1][1] * camwbGreen + colorMatrix[1][2] * camwbBlue; double cam_b = colorMatrix[2][0] * camwbRed + colorMatrix[2][1] * camwbGreen + colorMatrix[2][2] * camwbBlue; currWB = ColorTemp (cam_r, cam_g, cam_b, params.wb.equal); } else if (params.wb.method == "Auto") { currWB = ColorTemp (autoWBTemp, autoWBGreen, wbEqual, "Custom"); } double r, g, b; currWB.getMultipliers (r, g, b); //iColorMatrix is cam_rgb double rm = iColorMatrix[0][0] * r + iColorMatrix[0][1] * g + iColorMatrix[0][2] * b; double gm = iColorMatrix[1][0] * r + iColorMatrix[1][1] * g + iColorMatrix[1][2] * b; double bm = iColorMatrix[2][0] * r + iColorMatrix[2][1] * g + iColorMatrix[2][2] * b; rm = camwbRed / rm; gm = camwbGreen / gm; bm = camwbBlue / bm; double mul_lum = 0.299 * rm + 0.587 * gm + 0.114 * bm; double logDefGain = log(defGain) / log(2.0); int rmi, gmi, bmi; // Since HL recovery is not rendered in thumbs // if (!isRaw || !params.toneCurve.hrenabled) { logDefGain = 0.0; rmi = 1024.0 * rm * defGain / mul_lum; gmi = 1024.0 * gm * defGain / mul_lum; bmi = 1024.0 * bm * defGain / mul_lum; /* } else { rmi = 1024.0 * rm / mul_lum; gmi = 1024.0 * gm / mul_lum; bmi = 1024.0 * bm / mul_lum; }*/ // The RAW exposure is not reflected since it's done in preprocessing. If we only have e.g. the chached thumb, // that is already preprocessed. So we simulate the effect here roughly my modifying the exposure accordingly if (isRaw && fabs(1.0 - params.raw.expos) > 0.001) { rmi *= params.raw.expos; gmi *= params.raw.expos; bmi *= params.raw.expos; } // resize to requested width and perform coarse transformation int rwidth; if (params.coarse.rotate == 90 || params.coarse.rotate == 270) { rwidth = rheight; rheight = int(size_t(thumbImg->height) * size_t(rwidth) / size_t(thumbImg->width)); } else { rwidth = int(size_t(thumbImg->width) * size_t(rheight) / size_t(thumbImg->height)); } Imagefloat* baseImg = resizeTo(rwidth, rheight, interp, thumbImg); if (params.coarse.rotate) { baseImg->rotate (params.coarse.rotate); rwidth = baseImg->width; rheight = baseImg->height; } if (params.coarse.hflip) { baseImg->hflip (); } if (params.coarse.vflip) { baseImg->vflip (); } // apply white balance and raw white point (simulated) int val; unsigned short val_; for (int i = 0; i < rheight; i++) for (int j = 0; j < rwidth; j++) { baseImg->convertTo(baseImg->r(i, j), val_); val = static_cast(val_) * rmi >> 10; baseImg->r(i, j) = CLIP(val); baseImg->convertTo(baseImg->g(i, j), val_); val = static_cast(val_) * gmi >> 10; baseImg->g(i, j) = CLIP(val); baseImg->convertTo(baseImg->b(i, j), val_); val = static_cast(val_) * bmi >> 10; baseImg->b(i, j) = CLIP(val); } /* // apply highlight recovery, if needed -- CURRENTLY BROKEN DUE TO INCOMPATIBLE DATA TYPES, BUT HL RECOVERY AREN'T COMPUTED FOR THUMBNAILS ANYWAY... if (isRaw && params.toneCurve.hrenabled) { int maxval = 65535 / defGain; if (params.toneCurve.method=="Luminance" || params.toneCurve.method=="Color") for (int i=0; ir[i], baseImg->g[i], baseImg->b[i], baseImg->r[i], baseImg->g[i], baseImg->b[i], rwidth, maxval); else if (params.toneCurve.method=="CIELab blending") { double icamToD50[3][3]; RawImageSource::inverse33 (cam2xyz, icamToD50); for (int i=0; ir[i], baseImg->g[i], baseImg->b[i], baseImg->r[i], baseImg->g[i], baseImg->b[i], rwidth, maxval, cam2xyz, icamToD50); } } */ // if luma denoise has to be done for thumbnails, it should be right here // perform color space transformation if (isRaw) { double pre_mul[3] = { redMultiplier, greenMultiplier, blueMultiplier }; RawImageSource::colorSpaceConversion (baseImg, params.icm, currWB, pre_mul, embProfile, camProfile, cam2xyz, camName ); } else { StdImageSource::colorSpaceConversion (baseImg, params.icm, embProfile, thumbImg->getSampleFormat()); } int fw = baseImg->width; int fh = baseImg->height; //ColorTemp::CAT02 (baseImg, ¶ms) ;//perhaps not good! ImProcFunctions ipf (¶ms, false); ipf.setScale (sqrt(double(fw * fw + fh * fh)) / sqrt(double(thumbImg->width * thumbImg->width + thumbImg->height * thumbImg->height))*scale); ipf.updateColorProfiles (params.icm, options.rtSettings.monitorProfile, options.rtSettings.monitorIntent); LUTu hist16 (65536); LUTu hist16C (65536); double gamma = isRaw ? Color::sRGBGamma : 0; // usually in ImageSource, but we don't have that here ipf.firstAnalysis (baseImg, ¶ms, hist16); // perform transform if (ipf.needsTransform()) { Imagefloat* trImg = new Imagefloat (fw, fh); int origFW; int origFH; double tscale; getDimensions(origFW, origFH, tscale); ipf.transform (baseImg, trImg, 0, 0, 0, 0, fw, fh, origFW * tscale + 0.5, origFH * tscale + 0.5, focalLen, focalLen35mm, focusDist, 0, true); // Raw rotate degree not detectable here delete baseImg; baseImg = trImg; } // update blurmap SHMap* shmap = NULL; if (params.sh.enabled) { shmap = new SHMap (fw, fh, false); double radius = sqrt (double(fw * fw + fh * fh)) / 2.0; double shradius = params.sh.radius; if (!params.sh.hq) { shradius *= radius / 1800.0; } shmap->update (baseImg, shradius, ipf.lumimul, params.sh.hq, 16); } // RGB processing double expcomp = params.toneCurve.expcomp; int bright = params.toneCurve.brightness; int contr = params.toneCurve.contrast; int black = params.toneCurve.black; int hlcompr = params.toneCurve.hlcompr; int hlcomprthresh = params.toneCurve.hlcomprthresh; if (params.toneCurve.autoexp && aeHistogram) { ipf.getAutoExp (aeHistogram, aeHistCompression, logDefGain, params.toneCurve.clip, expcomp, bright, contr, black, hlcompr, hlcomprthresh); //ipf.getAutoExp (aeHistogram, aeHistCompression, logDefGain, params.toneCurve.clip, params.toneCurve.expcomp, params.toneCurve.brightness, params.toneCurve.contrast, params.toneCurve.black, params.toneCurve.hlcompr); } LUTf curve1 (65536); LUTf curve2 (65536); LUTf curve (65536); LUTf satcurve (65536); LUTf lhskcurve (65536); LUTf clcurve (65536); LUTf clToningcurve (65536); LUTf cl2Toningcurve (65536); LUTf rCurve (65536); LUTf gCurve (65536); LUTf bCurve (65536); LUTu dummy; ToneCurve customToneCurve1, customToneCurve2; ColorGradientCurve ctColorCurve; OpacityCurve ctOpacityCurve; // NoisCurve dnNoisCurve; ColorAppearance customColCurve1; ColorAppearance customColCurve2; ColorAppearance customColCurve3; ToneCurve customToneCurvebw1; ToneCurve customToneCurvebw2; CurveFactory::complexCurve (expcomp, black / 65535.0, hlcompr, hlcomprthresh, params.toneCurve.shcompr, bright, contr, params.toneCurve.curveMode, params.toneCurve.curve, params.toneCurve.curveMode2, params.toneCurve.curve2, hist16, dummy, curve1, curve2, curve, dummy, customToneCurve1, customToneCurve2, 16); CurveFactory::RGBCurve (params.rgbCurves.rcurve, rCurve, 16); CurveFactory::RGBCurve (params.rgbCurves.gcurve, gCurve, 16); CurveFactory::RGBCurve (params.rgbCurves.bcurve, bCurve, 16); TMatrix wprof = iccStore->workingSpaceMatrix (params.icm.working); double wp[3][3] = { {wprof[0][0], wprof[0][1], wprof[0][2]}, {wprof[1][0], wprof[1][1], wprof[1][2]}, {wprof[2][0], wprof[2][1], wprof[2][2]} }; TMatrix wiprof = iccStore->workingSpaceInverseMatrix (params.icm.working); double wip[3][3] = { {wiprof[0][0], wiprof[0][1], wiprof[0][2]}, {wiprof[1][0], wiprof[1][1], wiprof[1][2]}, {wiprof[2][0], wiprof[2][1], wiprof[2][2]} }; bool opautili = false; params.colorToning.getCurves(ctColorCurve, ctOpacityCurve, wp, wip, opautili); //params.dirpyrDenoise.getCurves(dnNoisCurve, lldenoisutili); bool clctoningutili = false; bool llctoningutili = false; CurveFactory::curveToningCL(clctoningutili, params.colorToning.clcurve, clToningcurve, scale == 1 ? 1 : 16); CurveFactory::curveToningLL(llctoningutili, params.colorToning.cl2curve, cl2Toningcurve, scale == 1 ? 1 : 16); CurveFactory::curveBW (params.blackwhite.beforeCurve, params.blackwhite.afterCurve, hist16, dummy, customToneCurvebw1, customToneCurvebw2, 16); double rrm, ggm, bbm; float autor, autog, autob; float satLimit = float(params.colorToning.satProtectionThreshold) / 100.f * 0.7f + 0.3f; float satLimitOpacity = 1.f - (float(params.colorToning.saturatedOpacity) / 100.f); if(params.colorToning.enabled && params.colorToning.autosat) { //for colortoning evaluation of saturation settings float moyS = 0.f; float eqty = 0.f; ipf.moyeqt (baseImg, moyS, eqty);//return image : mean saturation and standard dev of saturation //printf("moy=%f ET=%f\n", moyS,eqty); float satp = ((moyS + 1.5f * eqty) - 0.3f) / 0.7f; //1.5 sigma ==> 93% pixels with high saturation -0.3 / 0.7 convert to Hombre scale if(satp >= 0.92f) { satp = 0.92f; //avoid values too high (out of gamut) } if(satp <= 0.15f) { satp = 0.15f; //avoid too low values } satLimit = 100.f * satp; satLimitOpacity = 100.f * (moyS - 0.85f * eqty); //-0.85 sigma==>20% pixels with low saturation } autor = autog = autob = -9000.f; // This will ask to compute the "auto" values for the B&W tool LabImage* labView = new LabImage (fw, fh); DCPProfile *dcpProf = NULL; if (isRaw) { cmsHPROFILE dummy; RawImageSource::findInputProfile(params.icm.input, NULL, camName, &dcpProf, dummy); if (dcpProf != NULL) { dcpProf->setStep2ApplyState(params.icm.working, params.icm.toneCurve, params.icm.applyLookTable, params.icm.applyBaselineExposureOffset); } } ipf.rgbProc (baseImg, labView, NULL, curve1, curve2, curve, shmap, params.toneCurve.saturation, rCurve, gCurve, bCurve, satLimit , satLimitOpacity, ctColorCurve, ctOpacityCurve, opautili, clToningcurve, cl2Toningcurve, customToneCurve1, customToneCurve2, customToneCurvebw1, customToneCurvebw2, rrm, ggm, bbm, autor, autog, autob, expcomp, hlcompr, hlcomprthresh, dcpProf); // freeing up some memory customToneCurve1.Reset(); customToneCurve2.Reset(); ctColorCurve.Reset(); ctOpacityCurve.Reset(); // dnNoisCurve.Reset(); customToneCurvebw1.Reset(); customToneCurvebw2.Reset(); if (shmap) { delete shmap; } // luminance histogram update hist16.clear(); hist16C.clear(); for (int i = 0; i < fh; i++) for (int j = 0; j < fw; j++) { hist16[CLIP((int)((labView->L[i][j])))]++; hist16C[CLIP((int)sqrt(labView->a[i][j]*labView->a[i][j] + labView->b[i][j]*labView->b[i][j]))]++; } // luminance processing // ipf.EPDToneMap(labView,0,6); bool utili = false; bool autili = false; bool butili = false; bool ccutili = false; bool cclutili = false; bool clcutili = false; CurveFactory::complexLCurve (params.labCurve.brightness, params.labCurve.contrast, params.labCurve.lcurve, hist16, hist16, curve, dummy, 16, utili); CurveFactory::curveCL(clcutili, params.labCurve.clcurve, clcurve, hist16C, dummy, 16); CurveFactory::complexsgnCurve (1.f, autili, butili, ccutili, cclutili, params.labCurve.chromaticity, params.labCurve.rstprotection, params.labCurve.acurve, params.labCurve.bcurve, params.labCurve.cccurve, params.labCurve.lccurve, curve1, curve2, satcurve, lhskcurve, hist16C, hist16C, dummy, dummy, 16); //ipf.luminanceCurve (labView, labView, curve); ipf.chromiLuminanceCurve (NULL, 1, labView, labView, curve1, curve2, satcurve, lhskcurve, clcurve, curve, utili, autili, butili, ccutili, cclutili, clcutili, dummy, dummy, dummy, dummy); ipf.vibrance(labView); if((params.colorappearance.enabled && !params.colorappearance.tonecie) || !params.colorappearance.enabled) { ipf.EPDToneMap(labView, 5, 6); } //if(!params.colorappearance.enabled){ipf.EPDToneMap(labView,5,6);} CurveFactory::curveLightBrightColor ( params.colorappearance.curveMode, params.colorappearance.curve, params.colorappearance.curveMode2, params.colorappearance.curve2, params.colorappearance.curveMode3, params.colorappearance.curve3, hist16, hist16, dummy, hist16C, dummy, customColCurve1, customColCurve2, customColCurve3, 16); if(params.colorappearance.enabled) { int begh = 0, endh = labView->H; bool execsharp = false; float d; float fnum = fnumber;// F number float fiso = iso;// ISO float fspeed = shutter;//speed char * writ = new char[expcomp_.size() + 1];//convert expcomp_ to char std::copy(expcomp_.begin(), expcomp_.end(), writ); writ[expcomp_.size()] = '\0'; float fcomp = atof(writ); //compensation + - delete[] writ; float adap; if(fnum < 0.3f || fiso < 5.f || fspeed < 0.00001f) //if no exif data or wrong { adap = 2000.f; } else { float E_V = fcomp + log2 ((fnum * fnum) / fspeed / (fiso / 100.f)); float expo2 = params.toneCurve.expcomp; // exposure compensation in tonecurve ==> direct EV E_V += expo2; float expo1;//exposure raw white point expo1 = log2(params.raw.expos); //log2 ==>linear to EV E_V += expo1; adap = powf(2.f, E_V - 3.f); //cd / m2 //end calculation adaptation scene luminosity } LUTf CAMBrightCurveJ; LUTf CAMBrightCurveQ; float CAMMean; int sk; int scale; sk = 16; int rtt = 0; CieImage* cieView = new CieImage (fw, fh); ipf.ciecam_02float (cieView, adap, begh, endh, 1, 2, labView, ¶ms, customColCurve1, customColCurve2, customColCurve3, dummy, dummy, CAMBrightCurveJ, CAMBrightCurveQ, CAMMean, 5, 6, execsharp, d, sk, rtt); delete cieView; } // color processing //ipf.colorCurve (labView, labView); // obtain final image Image8* readyImg = new Image8 (fw, fh); ipf.lab2monitorRgb (labView, readyImg); delete labView; delete baseImg; // calculate scale if (params.coarse.rotate == 90 || params.coarse.rotate == 270) { myscale = scale * thumbImg->width / fh; } else { myscale = scale * thumbImg->height / fh; } myscale = 1.0 / myscale; /* // apply crop if (params.crop.enabled) { int ix = 0; for (int i=0; i(params.crop.y+params.crop.h)/myscale || j(params.crop.x+params.crop.w)/myscale) { readyImg->data[ix++] /= 3; readyImg->data[ix++] /= 3; readyImg->data[ix++] /= 3; } else ix += 3; }*/ return readyImg; } int Thumbnail::getImageWidth (const procparams::ProcParams& params, int rheight, float &ratio) { if (thumbImg == NULL) { return 0; // Can happen if thumb is just building and GUI comes in with resize wishes } int rwidth; if (params.coarse.rotate == 90 || params.coarse.rotate == 270) { ratio = (float)(thumbImg->height) / (float)(thumbImg->width); } else { ratio = (float)(thumbImg->width) / (float)(thumbImg->height); } rwidth = (int)(ratio * (float)rheight); return rwidth; } void Thumbnail::getDimensions (int& w, int& h, double& scaleFac) { if (thumbImg) { w = thumbImg->width; h = thumbImg->height; scaleFac = scale; } else { w = 0; h = 0; scale = 1; } } void Thumbnail::getCamWB (double& temp, double& green) { double cam_r = colorMatrix[0][0] * camwbRed + colorMatrix[0][1] * camwbGreen + colorMatrix[0][2] * camwbBlue; double cam_g = colorMatrix[1][0] * camwbRed + colorMatrix[1][1] * camwbGreen + colorMatrix[1][2] * camwbBlue; double cam_b = colorMatrix[2][0] * camwbRed + colorMatrix[2][1] * camwbGreen + colorMatrix[2][2] * camwbBlue; ColorTemp currWB = ColorTemp (cam_r, cam_g, cam_b, 1.0); // we do not take the equalizer into account here, because we want camera's WB temp = currWB.getTemp (); green = currWB.getGreen (); } void Thumbnail::getAutoWB (double& temp, double& green, double equal) { if (equal != wbEqual) { // compute the values depending on equal ColorTemp cTemp; wbEqual = equal; // compute autoWBTemp and autoWBGreen cTemp.mul2temp(redAWBMul, greenAWBMul, blueAWBMul, wbEqual, autoWBTemp, autoWBGreen); } temp = autoWBTemp; green = autoWBGreen; } void Thumbnail::getAutoWBMultipliers (double& rm, double& gm, double& bm) { rm = redAWBMul; gm = greenAWBMul; bm = blueAWBMul; } void Thumbnail::applyAutoExp (procparams::ProcParams& params) { if (params.toneCurve.autoexp && aeHistogram) { ImProcFunctions ipf (¶ms, false); ipf.getAutoExp (aeHistogram, aeHistCompression, log(defGain) / log(2.0), params.toneCurve.clip, params.toneCurve.expcomp, params.toneCurve.brightness, params.toneCurve.contrast, params.toneCurve.black, params.toneCurve.hlcompr, params.toneCurve.hlcomprthresh); } } void Thumbnail::getSpotWB (const procparams::ProcParams& params, int xp, int yp, int rect, double& rtemp, double& rgreen) { std::vector points, red, green, blue; for (int i = yp - rect; i <= yp + rect; i++) for (int j = xp - rect; j <= xp + rect; j++) { points.push_back (Coord2D (j, i)); } int fw = thumbImg->width, fh = thumbImg->height; if (params.coarse.rotate == 90 || params.coarse.rotate == 270) { fw = thumbImg->height; fh = thumbImg->width; } ImProcFunctions ipf (¶ms, false); ipf.transCoord (fw, fh, points, red, green, blue); int tr = getCoarseBitMask(params.coarse); // calculate spot wb (copy & pasted from stdimagesource) double reds = 0, greens = 0, blues = 0; int rn = 0, gn = 0, bn = 0; thumbImg->getSpotWBData(reds, greens, blues, rn, gn, bn, red, green, blue, tr); reds = reds / rn * camwbRed; greens = greens / gn * camwbGreen; blues = blues / bn * camwbBlue; double rm = colorMatrix[0][0] * reds + colorMatrix[0][1] * greens + colorMatrix[0][2] * blues; double gm = colorMatrix[1][0] * reds + colorMatrix[1][1] * greens + colorMatrix[1][2] * blues; double bm = colorMatrix[2][0] * reds + colorMatrix[2][1] * greens + colorMatrix[2][2] * blues; ColorTemp ct (rm, gm, bm, params.wb.equal); rtemp = ct.getTemp (); rgreen = ct.getGreen (); } void Thumbnail::transformPixel (int x, int y, int tran, int& tx, int& ty) { int W = thumbImg->width; int H = thumbImg->height; int sw = W, sh = H; if ((tran & TR_ROT) == TR_R90 || (tran & TR_ROT) == TR_R270) { sw = H; sh = W; } int ppx = x, ppy = y; if (tran & TR_HFLIP) { ppx = sw - 1 - x ; } if (tran & TR_VFLIP) { ppy = sh - 1 - y; } tx = ppx; ty = ppy; if ((tran & TR_ROT) == TR_R180) { tx = W - 1 - ppx; ty = H - 1 - ppy; } else if ((tran & TR_ROT) == TR_R90) { tx = ppy; ty = H - 1 - ppx; } else if ((tran & TR_ROT) == TR_R270) { tx = W - 1 - ppy; ty = ppx; } tx /= scale; ty /= scale; } unsigned char* Thumbnail::getGrayscaleHistEQ (int trim_width) { if (!thumbImg) { return NULL; } if (thumbImg->width < trim_width) { return NULL; } // to utilize the 8 bit color range of the thumbnail we brighten it and apply gamma correction unsigned char* tmpdata = new unsigned char[thumbImg->height * trim_width]; int ix = 0, max; if (gammaCorrected) { // if it's gamma correct (usually a RAW), we have the problem that there is a lot noise etc. that makes the maximum way too high. // Strategy is limit a certain percent of pixels so the overall picture quality when scaling to 8 bit is way better const double BurnOffPct = 0.03; // *100 = percent pixels that may be clipped // Calc the histogram unsigned int* hist16 = new unsigned int [65536]; memset(hist16, 0, sizeof(int) * 65536); if (thumbImg->getType() == sImage8) { Image8 *image = static_cast(thumbImg); image->calcGrayscaleHist(hist16); } else if (thumbImg->getType() == sImage16) { Image16 *image = static_cast(thumbImg); image->calcGrayscaleHist(hist16); } else if (thumbImg->getType() == sImagefloat) { Imagefloat *image = static_cast(thumbImg); image->calcGrayscaleHist(hist16); } else { printf("getGrayscaleHistEQ #1: Unsupported image type \"%s\"!\n", thumbImg->getType()); } // Go down till we cut off that many pixels unsigned long cutoff = thumbImg->height * thumbImg->height * 4 * BurnOffPct; int max_; unsigned long sum = 0; for (max_ = 65535; max_ > 16384 && sum < cutoff; max_--) { sum += hist16[max_]; } delete[] hist16; scaleForSave = 65535 * 8192 / max_; // Correction and gamma to 8 Bit if (thumbImg->getType() == sImage8) { Image8 *image = static_cast(thumbImg); for (int i = 0; i < thumbImg->height; i++) for (int j = (thumbImg->width - trim_width) / 2; j < trim_width + (thumbImg->width - trim_width) / 2; j++) { unsigned short r_, g_, b_; image->convertTo(image->r(i, j), r_); image->convertTo(image->g(i, j), g_); image->convertTo(image->b(i, j), b_); int r = gammatab[min(r_, static_cast(max_)) * scaleForSave >> 13]; int g = gammatab[min(g_, static_cast(max_)) * scaleForSave >> 13]; int b = gammatab[min(b_, static_cast(max_)) * scaleForSave >> 13]; tmpdata[ix++] = (r * 19595 + g * 38469 + b * 7472) >> 16; } } else if (thumbImg->getType() == sImage16) { Image16 *image = static_cast(thumbImg); for (int i = 0; i < thumbImg->height; i++) for (int j = (thumbImg->width - trim_width) / 2; j < trim_width + (thumbImg->width - trim_width) / 2; j++) { unsigned short r_, g_, b_; image->convertTo(image->r(i, j), r_); image->convertTo(image->g(i, j), g_); image->convertTo(image->b(i, j), b_); int r = gammatab[min(r_, static_cast(max_)) * scaleForSave >> 13]; int g = gammatab[min(g_, static_cast(max_)) * scaleForSave >> 13]; int b = gammatab[min(b_, static_cast(max_)) * scaleForSave >> 13]; tmpdata[ix++] = (r * 19595 + g * 38469 + b * 7472) >> 16; } } else if (thumbImg->getType() == sImagefloat) { Imagefloat *image = static_cast(thumbImg); for (int i = 0; i < thumbImg->height; i++) for (int j = (thumbImg->width - trim_width) / 2; j < trim_width + (thumbImg->width - trim_width) / 2; j++) { unsigned short r_, g_, b_; image->convertTo(image->r(i, j), r_); image->convertTo(image->g(i, j), g_); image->convertTo(image->b(i, j), b_); int r = gammatab[min(r_, static_cast(max_)) * scaleForSave >> 13]; int g = gammatab[min(g_, static_cast(max_)) * scaleForSave >> 13]; int b = gammatab[min(b_, static_cast(max_)) * scaleForSave >> 13]; tmpdata[ix++] = (r * 19595 + g * 38469 + b * 7472) >> 16; } } } else { // If it's not gamma corrected (usually a JPG) we take the normal maximum max = 0; if (thumbImg->getType() == sImage8) { Image8 *image = static_cast(thumbImg); unsigned char max_ = 0; for (int row = 0; row < image->height; row++) for (int col = 0; col < image->width; col++) { if (image->r(row, col) > max_) { max_ = image->r(row, col); } if (image->g(row, col) > max_) { max_ = image->g(row, col); } if (image->b(row, col) > max_) { max_ = image->b(row, col); } } image->convertTo(max_, max); if (max < 16384) { max = 16384; } scaleForSave = 65535 * 8192 / max; // Correction and gamma to 8 Bit for (int i = 0; i < image->height; i++) for (int j = (image->width - trim_width) / 2; j < trim_width + (image->width - trim_width) / 2; j++) { unsigned short rtmp, gtmp, btmp; image->convertTo(image->r(i, j), rtmp); image->convertTo(image->g(i, j), gtmp); image->convertTo(image->b(i, j), btmp); int r = rtmp * scaleForSave >> 21; int g = gtmp * scaleForSave >> 21; int b = btmp * scaleForSave >> 21; tmpdata[ix++] = (r * 19595 + g * 38469 + b * 7472) >> 16; } } else if (thumbImg->getType() == sImage16) { Image16 *image = static_cast(thumbImg); unsigned short max_ = 0; for (int row = 0; row < image->height; row++) for (int col = 0; col < image->width; col++) { if (image->r(row, col) > max_) { max_ = image->r(row, col); } if (image->g(row, col) > max_) { max_ = image->g(row, col); } if (image->b(row, col) > max_) { max_ = image->b(row, col); } } image->convertTo(max_, max); if (max < 16384) { max = 16384; } scaleForSave = 65535 * 8192 / max; // Correction and gamma to 8 Bit for (int i = 0; i < image->height; i++) for (int j = (image->width - trim_width) / 2; j < trim_width + (image->width - trim_width) / 2; j++) { unsigned short rtmp, gtmp, btmp; image->convertTo(image->r(i, j), rtmp); image->convertTo(image->g(i, j), gtmp); image->convertTo(image->b(i, j), btmp); int r = rtmp * scaleForSave >> 21; int g = gtmp * scaleForSave >> 21; int b = btmp * scaleForSave >> 21; tmpdata[ix++] = (r * 19595 + g * 38469 + b * 7472) >> 16; } } else if (thumbImg->getType() == sImagefloat) { Imagefloat *image = static_cast(thumbImg); float max_ = 0.f; for (int row = 0; row < image->height; row++) for (int col = 0; col < image->width; col++) { if (image->r(row, col) > max_) { max_ = image->r(row, col); } if (image->g(row, col) > max_) { max_ = image->g(row, col); } if (image->b(row, col) > max_) { max_ = image->b(row, col); } } image->convertTo(max_, max); if (max < 16384) { max = 16384; } scaleForSave = 65535 * 8192 / max; // Correction and gamma to 8 Bit for (int i = 0; i < image->height; i++) for (int j = (image->width - trim_width) / 2; j < trim_width + (image->width - trim_width) / 2; j++) { unsigned short rtmp, gtmp, btmp; image->convertTo(image->r(i, j), rtmp); image->convertTo(image->g(i, j), gtmp); image->convertTo(image->b(i, j), btmp); int r = rtmp * scaleForSave >> 21; int g = gtmp * scaleForSave >> 21; int b = btmp * scaleForSave >> 21; tmpdata[ix++] = (r * 19595 + g * 38469 + b * 7472) >> 16; } } else { printf("getGrayscaleHistEQ #2: Unsupported image type \"%s\"!\n", thumbImg->getType()); } } // histogram equalization unsigned int hist[256] = {0}; for (int i = 0; i < ix; i++) { hist[tmpdata[i]]++; } int cdf = 0, cdf_min = -1; for (int i = 0; i < 256; i++) { cdf += hist[i]; if (cdf > 0 && cdf_min == -1) { cdf_min = cdf; } if (cdf_min != -1) { hist[i] = (cdf - cdf_min) * 255 / ((thumbImg->height * trim_width) - cdf_min); } } for (int i = 0; i < ix; i++) { tmpdata[i] = hist[tmpdata[i]]; } return tmpdata; } bool Thumbnail::writeImage (const Glib::ustring& fname, int format) { if (!thumbImg) { return false; } Glib::ustring fullFName = fname + ".rtti"; FILE* f = safe_g_fopen (fullFName, "wb"); if (!f) { return false; } fwrite (thumbImg->getType(), sizeof (char), strlen(thumbImg->getType()), f); fputc ('\n', f); guint32 w = guint32(thumbImg->width); guint32 h = guint32(thumbImg->height); fwrite (&w, sizeof (guint32), 1, f); fwrite (&h, sizeof (guint32), 1, f); if (thumbImg->getType() == sImage8) { Image8 *image = static_cast(thumbImg); image->writeData(f); } else if (thumbImg->getType() == sImage16) { Image16 *image = static_cast(thumbImg); image->writeData(f); } else if (thumbImg->getType() == sImagefloat) { Imagefloat *image = static_cast(thumbImg); image->writeData(f); } //thumbImg->writeData(f); fclose (f); return true; } bool Thumbnail::readImage (const Glib::ustring& fname) { if (thumbImg) { delete thumbImg; thumbImg = NULL; } Glib::ustring fullFName = fname + ".rtti"; if (!safe_file_test (fullFName, Glib::FILE_TEST_EXISTS)) { return false; } FILE* f = safe_g_fopen (fullFName, "rb"); if (!f) { return false; } char imgType[31]; // 30 -> arbitrary size, but should be enough for all image type's name fgets(imgType, 30, f); imgType[strlen(imgType) - 1] = '\0'; // imgType has a \n trailing character, so we overwrite it by the \0 char guint32 width, height; fread (&width, 1, sizeof (guint32), f); fread (&height, 1, sizeof (guint32), f); bool success = false; if (!strcmp(imgType, sImage8)) { Image8 *image = new Image8(width, height); image->readData(f); thumbImg = image; success = true; } else if (!strcmp(imgType, sImage16)) { Image16 *image = new Image16(width, height); image->readData(f); thumbImg = image; success = true; } else if (!strcmp(imgType, sImagefloat)) { Imagefloat *image = new Imagefloat(width, height); image->readData(f); thumbImg = image; success = true; } else { printf("readImage: Unsupported image type \"%s\"!\n", imgType); } fclose(f); return success; } bool Thumbnail::readData (const Glib::ustring& fname) { setlocale(LC_NUMERIC, "C"); // to set decimal point to "." SafeKeyFile keyFile; try { MyMutex::MyLock thmbLock(thumbMutex); if (!keyFile.load_from_file (fname)) { return false; } if (keyFile.has_group ("LiveThumbData")) { if (keyFile.has_key ("LiveThumbData", "CamWBRed")) { camwbRed = keyFile.get_double ("LiveThumbData", "CamWBRed"); } if (keyFile.has_key ("LiveThumbData", "CamWBGreen")) { camwbGreen = keyFile.get_double ("LiveThumbData", "CamWBGreen"); } if (keyFile.has_key ("LiveThumbData", "CamWBBlue")) { camwbBlue = keyFile.get_double ("LiveThumbData", "CamWBBlue"); } if (keyFile.has_key ("LiveThumbData", "RedAWBMul")) { redAWBMul = keyFile.get_double ("LiveThumbData", "RedAWBMul"); } if (keyFile.has_key ("LiveThumbData", "GreenAWBMul")) { greenAWBMul = keyFile.get_double ("LiveThumbData", "GreenAWBMul"); } if (keyFile.has_key ("LiveThumbData", "BlueAWBMul")) { blueAWBMul = keyFile.get_double ("LiveThumbData", "BlueAWBMul"); } if (keyFile.has_key ("LiveThumbData", "AEHistCompression")) { aeHistCompression = keyFile.get_integer ("LiveThumbData", "AEHistCompression"); } if (keyFile.has_key ("LiveThumbData", "RedMultiplier")) { redMultiplier = keyFile.get_double ("LiveThumbData", "RedMultiplier"); } if (keyFile.has_key ("LiveThumbData", "GreenMultiplier")) { greenMultiplier = keyFile.get_double ("LiveThumbData", "GreenMultiplier"); } if (keyFile.has_key ("LiveThumbData", "BlueMultiplier")) { blueMultiplier = keyFile.get_double ("LiveThumbData", "BlueMultiplier"); } if (keyFile.has_key ("LiveThumbData", "Scale")) { scale = keyFile.get_double ("LiveThumbData", "Scale"); } if (keyFile.has_key ("LiveThumbData", "DefaultGain")) { defGain = keyFile.get_double ("LiveThumbData", "DefaultGain"); } if (keyFile.has_key ("LiveThumbData", "ScaleForSave")) { scaleForSave = keyFile.get_integer ("LiveThumbData", "ScaleForSave"); } if (keyFile.has_key ("LiveThumbData", "GammaCorrected")) { gammaCorrected = keyFile.get_boolean ("LiveThumbData", "GammaCorrected"); } if (keyFile.has_key ("LiveThumbData", "ColorMatrix")) { std::vector cm = keyFile.get_double_list ("LiveThumbData", "ColorMatrix"); int ix = 0; for (int i = 0; i < 3; i++) for (int j = 0; j < 3; j++) { colorMatrix[i][j] = cm[ix++]; } } } return true; } catch (Glib::Error &err) { if (options.rtSettings.verbose) { printf("Thumbnail::readData / Error code %d while reading values from \"%s\":\n%s\n", err.code(), fname.c_str(), err.what().c_str()); } } catch (...) { if (options.rtSettings.verbose) { printf("Thumbnail::readData / Unknown exception while trying to load \"%s\"!\n", fname.c_str()); } } return false; } bool Thumbnail::writeData (const Glib::ustring& fname) { SafeKeyFile keyFile; MyMutex::MyLock thmbLock(thumbMutex); try { if( safe_file_test(fname, Glib::FILE_TEST_EXISTS) ) { keyFile.load_from_file (fname); } } catch (Glib::Error &err) { if (options.rtSettings.verbose) { printf("Thumbnail::writeData / Error code %d while reading values from \"%s\":\n%s\n", err.code(), fname.c_str(), err.what().c_str()); } } catch (...) { if (options.rtSettings.verbose) { printf("Thumbnail::writeData / Unknown exception while trying to save \"%s\"!\n", fname.c_str()); } } keyFile.set_double ("LiveThumbData", "CamWBRed", camwbRed); keyFile.set_double ("LiveThumbData", "CamWBGreen", camwbGreen); keyFile.set_double ("LiveThumbData", "CamWBBlue", camwbBlue); keyFile.set_double ("LiveThumbData", "RedAWBMul", redAWBMul); keyFile.set_double ("LiveThumbData", "GreenAWBMul", greenAWBMul); keyFile.set_double ("LiveThumbData", "BlueAWBMul", blueAWBMul); keyFile.set_integer ("LiveThumbData", "AEHistCompression", aeHistCompression); keyFile.set_double ("LiveThumbData", "RedMultiplier", redMultiplier); keyFile.set_double ("LiveThumbData", "GreenMultiplier", greenMultiplier); keyFile.set_double ("LiveThumbData", "BlueMultiplier", blueMultiplier); keyFile.set_double ("LiveThumbData", "Scale", scale); keyFile.set_double ("LiveThumbData", "DefaultGain", defGain); keyFile.set_integer ("LiveThumbData", "ScaleForSave", scaleForSave); keyFile.set_boolean ("LiveThumbData", "GammaCorrected", gammaCorrected); Glib::ArrayHandle cm ((double*)colorMatrix, 9, Glib::OWNERSHIP_NONE); keyFile.set_double_list ("LiveThumbData", "ColorMatrix", cm); FILE *f = safe_g_fopen (fname, "wt"); if (!f) { if (options.rtSettings.verbose) { printf("Thumbnail::writeData / Error: unable to open file \"%s\" with write access!\n", fname.c_str()); } return false; } else { fprintf (f, "%s", keyFile.to_data().c_str()); fclose (f); } return true; } bool Thumbnail::readEmbProfile (const Glib::ustring& fname) { FILE* f = safe_g_fopen (fname, "rb"); if (!f) { embProfileData = NULL; embProfile = NULL; embProfileLength = 0; } else { fseek (f, 0, SEEK_END); embProfileLength = ftell (f); fseek (f, 0, SEEK_SET); embProfileData = new unsigned char[embProfileLength]; fread (embProfileData, 1, embProfileLength, f); fclose (f); embProfile = cmsOpenProfileFromMem (embProfileData, embProfileLength); return true; } return false; } bool Thumbnail::writeEmbProfile (const Glib::ustring& fname) { if (embProfileData) { FILE* f = safe_g_fopen(fname, "wb"); if (f) { fwrite (embProfileData, 1, embProfileLength, f); fclose (f); return true; } } return false; } bool Thumbnail::readAEHistogram (const Glib::ustring& fname) { FILE* f = safe_g_fopen (fname, "rb"); if (!f) { aeHistogram(0); } else { aeHistogram(65536 >> aeHistCompression); fread (&aeHistogram[0], 1, (65536 >> aeHistCompression)*sizeof(aeHistogram[0]), f); fclose (f); return true; } return false; } bool Thumbnail::writeAEHistogram (const Glib::ustring& fname) { if (aeHistogram) { FILE* f = safe_g_fopen (fname, "wb"); if (f) { fwrite (&aeHistogram[0], 1, (65536 >> aeHistCompression)*sizeof(aeHistogram[0]), f); fclose (f); return true; } } return false; } unsigned char* Thumbnail::getImage8Data() { if (thumbImg && thumbImg->getType() == rtengine::sImage8) { Image8* img8 = static_cast(thumbImg); return img8->data; } return NULL; } }