/** =========================================================== * @file * * This file is a part of digiKam project * http://www.digikam.org * * @date 2008-10-09 * @brief internal private container for KDcraw * * @author Copyright (C) 2008-2013 by Gilles Caulier * caulier dot gilles at gmail dot com * * This program 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 2, or (at your option) * any later version. * * This program 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. * * ============================================================ */ #include "kdcraw.h" #include "kdcraw_p.h" // Qt includes #include #include namespace KDcrawIface { int callbackForLibRaw(void* data, enum LibRaw_progress p, int iteration, int expected) { if (data) { KDcraw::Private* const d = static_cast(data); if (d) { return d->progressCallback(p, iteration, expected); } } return 0; } // -------------------------------------------------------------------------------------------------- KDcraw::Private::Private(KDcraw* const p) { m_progress = 0.0; m_parent = p; } KDcraw::Private::~Private() { } void KDcraw::Private::createPPMHeader(QByteArray& imgData, libraw_processed_image_t* const img) { QString header = QString("P%1\n%2 %3\n%4\n").arg(img->colors == 3 ? "6" : "5") .arg(img->width) .arg(img->height) .arg((1 << img->bits)-1); imgData.append(header.toAscii()); imgData.append(QByteArray((const char*)img->data, (int)img->data_size)); } int KDcraw::Private::progressCallback(enum LibRaw_progress p, int iteration, int expected) { kDebug() << "LibRaw progress: " << libraw_strprogress(p) << " pass " << iteration << " of " << expected; // post a little change in progress indicator to show raw processor activity. setProgress(progressValue()+0.01); // Clean processing termination by user... if (m_parent->checkToCancelWaitingData()) { kDebug() << "LibRaw process terminaison invoked..."; m_parent->m_cancel = true; m_progress = 0.0; return 1; } // Return 0 to continue processing... return 0; } void KDcraw::Private::setProgress(double value) { m_progress = value; m_parent->setWaitingDataProgress(m_progress); } double KDcraw::Private::progressValue() const { return m_progress; } void KDcraw::Private::fillIndentifyInfo(LibRaw* const raw, DcrawInfoContainer& identify) { identify.dateTime.setTime_t(raw->imgdata.other.timestamp); identify.make = QString(raw->imgdata.idata.make); identify.model = QString(raw->imgdata.idata.model); identify.owner = QString(raw->imgdata.other.artist); identify.DNGVersion = QString::number(raw->imgdata.idata.dng_version); identify.sensitivity = raw->imgdata.other.iso_speed; identify.exposureTime = raw->imgdata.other.shutter; identify.aperture = raw->imgdata.other.aperture; identify.focalLength = raw->imgdata.other.focal_len; identify.imageSize = QSize(raw->imgdata.sizes.width, raw->imgdata.sizes.height); identify.fullSize = QSize(raw->imgdata.sizes.raw_width, raw->imgdata.sizes.raw_height); identify.outputSize = QSize(raw->imgdata.sizes.iwidth, raw->imgdata.sizes.iheight); identify.thumbSize = QSize(raw->imgdata.thumbnail.twidth, raw->imgdata.thumbnail.theight); identify.topMargin = raw->imgdata.sizes.top_margin; identify.leftMargin = raw->imgdata.sizes.left_margin; identify.hasIccProfile = raw->imgdata.color.profile ? true : false; identify.isDecodable = true; identify.pixelAspectRatio = raw->imgdata.sizes.pixel_aspect; identify.rawColors = raw->imgdata.idata.colors; identify.rawImages = raw->imgdata.idata.raw_count; identify.blackPoint = raw->imgdata.color.black; for (int ch = 0; ch < 4; ch++) { identify.blackPointCh[ch] = raw->imgdata.color.cblack[ch]; } identify.whitePoint = raw->imgdata.color.maximum; identify.orientation = (DcrawInfoContainer::ImageOrientation)raw->imgdata.sizes.flip; memcpy(&identify.cameraColorMatrix1, &raw->imgdata.color.cmatrix, sizeof(raw->imgdata.color.cmatrix)); memcpy(&identify.cameraColorMatrix2, &raw->imgdata.color.rgb_cam, sizeof(raw->imgdata.color.rgb_cam)); memcpy(&identify.cameraXYZMatrix, &raw->imgdata.color.cam_xyz, sizeof(raw->imgdata.color.cam_xyz)); if (raw->imgdata.idata.filters) { if (!raw->imgdata.idata.cdesc[3]) { raw->imgdata.idata.cdesc[3] = 'G'; } for (int i=0; i < 16; i++) { identify.filterPattern.append(raw->imgdata.idata.cdesc[raw->COLOR(i >> 1,i & 1)]); } identify.colorKeys = raw->imgdata.idata.cdesc; } for(int c = 0 ; c < raw->imgdata.idata.colors ; c++) { identify.daylightMult[c] = raw->imgdata.color.pre_mul[c]; } if (raw->imgdata.color.cam_mul[0] > 0) { for(int c = 0 ; c < 4 ; c++) { identify.cameraMult[c] = raw->imgdata.color.cam_mul[c]; } } } bool KDcraw::Private::loadFromLibraw(const QString& filePath, QByteArray& imageData, int& width, int& height, int& rgbmax) { m_parent->m_cancel = false; LibRaw raw; // Set progress call back function. raw.set_progress_handler(callbackForLibRaw, this); QByteArray deadpixelPath = QFile::encodeName(m_parent->m_rawDecodingSettings.deadPixelMap); QByteArray cameraProfile = QFile::encodeName(m_parent->m_rawDecodingSettings.inputProfile); QByteArray outputProfile = QFile::encodeName(m_parent->m_rawDecodingSettings.outputProfile); if (!m_parent->m_rawDecodingSettings.autoBrightness) { // Use a fixed white level, ignoring the image histogram. raw.imgdata.params.no_auto_bright = 1; } if (m_parent->m_rawDecodingSettings.sixteenBitsImage) { // (-4) 16bit ppm output raw.imgdata.params.output_bps = 16; } if (m_parent->m_rawDecodingSettings.halfSizeColorImage) { // (-h) Half-size color image (3x faster than -q). raw.imgdata.params.half_size = 1; } if (m_parent->m_rawDecodingSettings.RGBInterpolate4Colors) { // (-f) Interpolate RGB as four colors. raw.imgdata.params.four_color_rgb = 1; } if (m_parent->m_rawDecodingSettings.DontStretchPixels) { // (-j) Do not stretch the image to its correct aspect ratio. raw.imgdata.params.use_fuji_rotate = 1; } // (-H) Unclip highlight color. raw.imgdata.params.highlight = m_parent->m_rawDecodingSettings.unclipColors; if (m_parent->m_rawDecodingSettings.brightness != 1.0) { // (-b) Set Brightness value. raw.imgdata.params.bright = m_parent->m_rawDecodingSettings.brightness; } if (m_parent->m_rawDecodingSettings.enableBlackPoint) { // (-k) Set Black Point value. raw.imgdata.params.user_black = m_parent->m_rawDecodingSettings.blackPoint; } if (m_parent->m_rawDecodingSettings.enableWhitePoint) { // (-S) Set White Point value (saturation). raw.imgdata.params.user_sat = m_parent->m_rawDecodingSettings.whitePoint; } if (m_parent->m_rawDecodingSettings.medianFilterPasses > 0) { // (-m) After interpolation, clean up color artifacts by repeatedly applying a 3x3 median filter to the R-G and B-G channels. raw.imgdata.params.med_passes = m_parent->m_rawDecodingSettings.medianFilterPasses; } if (!m_parent->m_rawDecodingSettings.deadPixelMap.isEmpty()) { // (-P) Read the dead pixel list from this file. raw.imgdata.params.bad_pixels = deadpixelPath.data(); } switch (m_parent->m_rawDecodingSettings.whiteBalance) { case RawDecodingSettings::NONE: { break; } case RawDecodingSettings::CAMERA: { // (-w) Use camera white balance, if possible. raw.imgdata.params.use_camera_wb = 1; break; } case RawDecodingSettings::AUTO: { // (-a) Use automatic white balance. raw.imgdata.params.use_auto_wb = 1; break; } case RawDecodingSettings::CUSTOM: { /* Convert between Temperature and RGB. */ double T; double RGB[3]; double xD, yD, X, Y, Z; DcrawInfoContainer identify; T = m_parent->m_rawDecodingSettings.customWhiteBalance; /* Here starts the code picked and adapted from ufraw (0.12.1) to convert Temperature + green multiplier to RGB multipliers */ /* Convert between Temperature and RGB. * Base on information from http://www.brucelindbloom.com/ * The fit for D-illuminant between 4000K and 12000K are from CIE * The generalization to 2000K < T < 4000K and the blackbody fits * are my own and should be taken with a grain of salt. */ const double XYZ_to_RGB[3][3] = { { 3.24071, -0.969258, 0.0556352 }, {-1.53726, 1.87599, -0.203996 }, {-0.498571, 0.0415557, 1.05707 } }; // Fit for CIE Daylight illuminant if (T <= 4000) { xD = 0.27475e9/(T*T*T) - 0.98598e6/(T*T) + 1.17444e3/T + 0.145986; } else if (T <= 7000) { xD = -4.6070e9/(T*T*T) + 2.9678e6/(T*T) + 0.09911e3/T + 0.244063; } else { xD = -2.0064e9/(T*T*T) + 1.9018e6/(T*T) + 0.24748e3/T + 0.237040; } yD = -3*xD*xD + 2.87*xD - 0.275; X = xD/yD; Y = 1; Z = (1-xD-yD)/yD; RGB[0] = X*XYZ_to_RGB[0][0] + Y*XYZ_to_RGB[1][0] + Z*XYZ_to_RGB[2][0]; RGB[1] = X*XYZ_to_RGB[0][1] + Y*XYZ_to_RGB[1][1] + Z*XYZ_to_RGB[2][1]; RGB[2] = X*XYZ_to_RGB[0][2] + Y*XYZ_to_RGB[1][2] + Z*XYZ_to_RGB[2][2]; /* End of the code picked to ufraw */ RGB[1] = RGB[1] / m_parent->m_rawDecodingSettings.customWhiteBalanceGreen; /* By default, decraw override his default D65 WB We need to keep it as a basis : if not, colors with some DSLR will have a high dominant of color that will lead to a completely wrong WB */ if (rawFileIdentify(identify, filePath)) { RGB[0] = identify.daylightMult[0] / RGB[0]; RGB[1] = identify.daylightMult[1] / RGB[1]; RGB[2] = identify.daylightMult[2] / RGB[2]; } else { RGB[0] = 1.0 / RGB[0]; RGB[1] = 1.0 / RGB[1]; RGB[2] = 1.0 / RGB[2]; kDebug() << "Warning: cannot get daylight multipliers"; } // (-r) set Raw Color Balance Multipliers. raw.imgdata.params.user_mul[0] = RGB[0]; raw.imgdata.params.user_mul[1] = RGB[1]; raw.imgdata.params.user_mul[2] = RGB[2]; raw.imgdata.params.user_mul[3] = RGB[1]; break; } case RawDecodingSettings::AERA: { // (-A) Calculate the white balance by averaging a rectangular area from image. raw.imgdata.params.greybox[0] = m_parent->m_rawDecodingSettings.whiteBalanceArea.left(); raw.imgdata.params.greybox[1] = m_parent->m_rawDecodingSettings.whiteBalanceArea.top(); raw.imgdata.params.greybox[2] = m_parent->m_rawDecodingSettings.whiteBalanceArea.width(); raw.imgdata.params.greybox[3] = m_parent->m_rawDecodingSettings.whiteBalanceArea.height(); break; } } // (-q) Use an interpolation method. raw.imgdata.params.user_qual = m_parent->m_rawDecodingSettings.RAWQuality; switch (m_parent->m_rawDecodingSettings.NRType) { case RawDecodingSettings::WAVELETSNR: { // (-n) Use wavelets to erase noise while preserving real detail. raw.imgdata.params.threshold = m_parent->m_rawDecodingSettings.NRThreshold; break; } case RawDecodingSettings::FBDDNR: { // (100 - 1000) => (1 - 10) conversion raw.imgdata.params.fbdd_noiserd = lround(m_parent->m_rawDecodingSettings.NRThreshold / 100.0); break; } case RawDecodingSettings::LINENR: { // (100 - 1000) => (0.001 - 0.02) conversion. raw.imgdata.params.linenoise = m_parent->m_rawDecodingSettings.NRThreshold * 2.11E-5 + 0.00111111; raw.imgdata.params.cfaline = true; break; } case RawDecodingSettings::IMPULSENR: { // (100 - 1000) => (0.005 - 0.05) conversion. raw.imgdata.params.lclean = m_parent->m_rawDecodingSettings.NRThreshold * 5E-5; raw.imgdata.params.cclean = m_parent->m_rawDecodingSettings.NRChroThreshold * 5E-5; raw.imgdata.params.cfa_clean = true; break; } default: // No Noise Reduction { raw.imgdata.params.threshold = 0; raw.imgdata.params.fbdd_noiserd = 0; raw.imgdata.params.linenoise = 0; raw.imgdata.params.cfaline = false; raw.imgdata.params.lclean = 0; raw.imgdata.params.cclean = 0; raw.imgdata.params.cfa_clean = false; break; } } // Chromatic aberration correction. raw.imgdata.params.ca_correc = m_parent->m_rawDecodingSettings.enableCACorrection; raw.imgdata.params.cared = m_parent->m_rawDecodingSettings.caMultiplier[0]; raw.imgdata.params.cablue = m_parent->m_rawDecodingSettings.caMultiplier[1]; // Exposure Correction before interpolation. raw.imgdata.params.exp_correc = m_parent->m_rawDecodingSettings.expoCorrection; raw.imgdata.params.exp_shift = m_parent->m_rawDecodingSettings.expoCorrectionShift; raw.imgdata.params.exp_preser = m_parent->m_rawDecodingSettings.expoCorrectionHighlight; switch (m_parent->m_rawDecodingSettings.inputColorSpace) { case RawDecodingSettings::EMBEDDED: { // (-p embed) Use input profile from RAW file to define the camera's raw colorspace. raw.imgdata.params.camera_profile = (char*)"embed"; break; } case RawDecodingSettings::CUSTOMINPUTCS: { if (!m_parent->m_rawDecodingSettings.inputProfile.isEmpty()) { // (-p) Use input profile file to define the camera's raw colorspace. raw.imgdata.params.camera_profile = cameraProfile.data(); } break; } default: { // No input profile break; } } switch (m_parent->m_rawDecodingSettings.outputColorSpace) { case RawDecodingSettings::CUSTOMOUTPUTCS: { if (!m_parent->m_rawDecodingSettings.outputProfile.isEmpty()) { // (-o) Use ICC profile file to define the output colorspace. raw.imgdata.params.output_profile = outputProfile.data(); } break; } default: { // (-o) Define the output colorspace. raw.imgdata.params.output_color = m_parent->m_rawDecodingSettings.outputColorSpace; break; } } //-- Extended demosaicing settings ---------------------------------------------------------- raw.imgdata.params.dcb_iterations = m_parent->m_rawDecodingSettings.dcbIterations; raw.imgdata.params.dcb_enhance_fl = m_parent->m_rawDecodingSettings.dcbEnhanceFl; raw.imgdata.params.eeci_refine = m_parent->m_rawDecodingSettings.eeciRefine; raw.imgdata.params.es_med_passes = m_parent->m_rawDecodingSettings.esMedPasses; //------------------------------------------------------------------------------------------- setProgress(0.1); kDebug() << filePath; kDebug() << m_parent->m_rawDecodingSettings; int ret = raw.open_file(QFile::encodeName(filePath)); if (ret != LIBRAW_SUCCESS) { kDebug() << "LibRaw: failed to run open_file: " << libraw_strerror(ret); raw.recycle(); return false; } if (m_parent->m_cancel) { raw.recycle(); return false; } setProgress(0.2); ret = raw.unpack(); if (ret != LIBRAW_SUCCESS) { kDebug() << "LibRaw: failed to run unpack: " << libraw_strerror(ret); raw.recycle(); return false; } if (m_parent->m_cancel) { raw.recycle(); return false; } setProgress(0.25); if (m_parent->m_rawDecodingSettings.fixColorsHighlights) { kDebug() << "Applying LibRaw highlights adjustments"; // 1.0 is fallback to default value raw.imgdata.params.adjust_maximum_thr = 1.0; } else { kDebug() << "Disabling LibRaw highlights adjustments"; // 0.0 disables this feature raw.imgdata.params.adjust_maximum_thr = 0.0; } ret = raw.dcraw_process(); if (ret != LIBRAW_SUCCESS) { kDebug() << "LibRaw: failed to run dcraw_process: " << libraw_strerror(ret); raw.recycle(); return false; } if (m_parent->m_cancel) { raw.recycle(); return false; } setProgress(0.3); libraw_processed_image_t* img = raw.dcraw_make_mem_image(&ret); if(!img) { kDebug() << "LibRaw: failed to run dcraw_make_mem_image: " << libraw_strerror(ret); raw.recycle(); return false; } if (m_parent->m_cancel) { // Clear memory allocation. Introduced with LibRaw 0.11.0 raw.dcraw_clear_mem(img); raw.recycle(); return false; } setProgress(0.35); width = img->width; height = img->height; rgbmax = (1 << img->bits)-1; if (img->colors == 3) { imageData = QByteArray((const char*)img->data, (int)img->data_size); } else { // img->colors == 1 (Grayscale) : convert to RGB imageData = QByteArray(); for (int i = 0 ; i < (int)img->data_size ; ++i) { for (int j = 0 ; j < 3 ; ++j) { imageData.append(img->data[i]); } } } // Clear memory allocation. Introduced with LibRaw 0.11.0 raw.dcraw_clear_mem(img); raw.recycle(); if (m_parent->m_cancel) { return false; } setProgress(0.4); kDebug() << "LibRaw: data info: width=" << width << " height=" << height << " rgbmax=" << rgbmax; return true; } bool KDcraw::Private::loadEmbeddedPreview(QByteArray& imgData, LibRaw& raw) { int ret = raw.unpack_thumb(); if (ret != LIBRAW_SUCCESS) { raw.recycle(); kDebug() << "LibRaw: failed to run unpack_thumb: " << libraw_strerror(ret); raw.recycle(); return false; } libraw_processed_image_t* const thumb = raw.dcraw_make_mem_thumb(&ret); if(!thumb) { kDebug() << "LibRaw: failed to run dcraw_make_mem_thumb: " << libraw_strerror(ret); raw.recycle(); return false; } if(thumb->type == LIBRAW_IMAGE_BITMAP) { createPPMHeader(imgData, thumb); } else { imgData = QByteArray((const char*)thumb->data, (int)thumb->data_size); } // Clear memory allocation. Introduced with LibRaw 0.11.0 raw.dcraw_clear_mem(thumb); raw.recycle(); if ( imgData.isEmpty() ) { kDebug() << "Failed to load JPEG thumb from LibRaw!"; return false; } return true; } bool KDcraw::Private::loadHalfPreview(QImage& image, LibRaw& raw) { raw.imgdata.params.use_auto_wb = 1; // Use automatic white balance. raw.imgdata.params.use_camera_wb = 1; // Use camera white balance, if possible. raw.imgdata.params.half_size = 1; // Half-size color image (3x faster than -q). QByteArray imgData; int ret = raw.unpack(); if (ret != LIBRAW_SUCCESS) { kDebug() << "LibRaw: failed to run unpack: " << libraw_strerror(ret); raw.recycle(); return false; } ret = raw.dcraw_process(); if (ret != LIBRAW_SUCCESS) { kDebug() << "LibRaw: failed to run dcraw_process: " << libraw_strerror(ret); raw.recycle(); return false; } libraw_processed_image_t* halfImg = raw.dcraw_make_mem_image(&ret); if(!halfImg) { kDebug() << "LibRaw: failed to run dcraw_make_mem_image: " << libraw_strerror(ret); raw.recycle(); return false; } Private::createPPMHeader(imgData, halfImg); // Clear memory allocation. Introduced with LibRaw 0.11.0 raw.dcraw_clear_mem(halfImg); raw.recycle(); if ( imgData.isEmpty() ) { kDebug() << "Failed to load half preview from LibRaw!"; return false; } if (!image.loadFromData(imgData)) { kDebug() << "Failed to load PPM data from LibRaw!"; return false; } return true; } } // namespace KDcrawIface