You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
966 lines
29 KiB
966 lines
29 KiB
/* ============================================================
|
|
*
|
|
* This file is a part of kipi-plugins project
|
|
* http://www.kipi-plugins.org
|
|
*
|
|
* Date : 2006-12-09
|
|
* Description : a tread-safe dcraw program interface
|
|
*
|
|
* Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
|
|
* Copyright (C) 2006-2008 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
|
|
* Copyright (C) 2007-2008 by Guillaume Castagnino <casta at xwing dot info>
|
|
*
|
|
* NOTE: Do not use kdDebug() in this implementation because
|
|
* it will be multithreaded. Use qDebug() instead.
|
|
* See B.K.O #133026 for details.
|
|
*
|
|
* 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.
|
|
*
|
|
* ============================================================ */
|
|
|
|
extern "C"
|
|
{
|
|
#include <unistd.h>
|
|
#include <sys/ipc.h>
|
|
#include <sys/shm.h>
|
|
}
|
|
|
|
// C++ includes.
|
|
|
|
#include <cstdlib>
|
|
#include <cstdio>
|
|
#include <cmath>
|
|
|
|
// Qt Includes.
|
|
|
|
#include <qfile.h>
|
|
#include <qtimer.h>
|
|
#include <qcstring.h>
|
|
#include <qfileinfo.h>
|
|
#include <qapplication.h>
|
|
#include <qmutex.h>
|
|
#include <qwaitcondition.h>
|
|
|
|
// KDE includes.
|
|
|
|
#include <kprocess.h>
|
|
#include <kstandarddirs.h>
|
|
|
|
// Local includes.
|
|
|
|
#include "version.h"
|
|
#include "dcrawbinary.h"
|
|
#include "kdcraw.h"
|
|
#include "kdcraw.moc"
|
|
|
|
namespace KDcrawIface
|
|
{
|
|
|
|
class KDcrawPriv
|
|
{
|
|
public:
|
|
|
|
KDcrawPriv()
|
|
{
|
|
running = false;
|
|
normalExit = false;
|
|
process = 0;
|
|
queryTimer = 0;
|
|
data = 0;
|
|
width = 0;
|
|
height = 0;
|
|
rgbmax = 0;
|
|
dataPos = 0;
|
|
}
|
|
|
|
bool running;
|
|
bool normalExit;
|
|
|
|
uchar *data;
|
|
|
|
int dataPos;
|
|
int width;
|
|
int height;
|
|
int rgbmax;
|
|
|
|
QString filePath;
|
|
|
|
QMutex mutex;
|
|
|
|
QWaitCondition condVar;
|
|
|
|
QTimer *queryTimer;
|
|
|
|
KProcess *process;
|
|
|
|
};
|
|
|
|
KDcraw::KDcraw()
|
|
{
|
|
d = new KDcrawPriv;
|
|
m_cancel = false;
|
|
}
|
|
|
|
KDcraw::~KDcraw()
|
|
{
|
|
cancel();
|
|
delete d;
|
|
}
|
|
|
|
QString KDcraw::version()
|
|
{
|
|
return QString(kdcraw_version);
|
|
}
|
|
|
|
void KDcraw::cancel()
|
|
{
|
|
m_cancel = true;
|
|
}
|
|
|
|
bool KDcraw::loadDcrawPreview(QImage& image, const QString& path)
|
|
{
|
|
// In first, try to extrcat the embedded JPEG preview. Very fast.
|
|
bool ret = loadEmbeddedPreview(image, path);
|
|
if (ret) return true;
|
|
|
|
// In second, decode and half size of RAW picture. More slow.
|
|
return (loadHalfPreview(image, path));
|
|
}
|
|
|
|
bool KDcraw::loadEmbeddedPreview(QImage& image, const QString& path)
|
|
{
|
|
FILE *f=NULL;
|
|
QByteArray imgData;
|
|
const int MAX_IPC_SIZE = (1024*32);
|
|
char buffer[MAX_IPC_SIZE];
|
|
QFile file;
|
|
Q_LONG len;
|
|
QCString command;
|
|
|
|
QFileInfo fileInfo(path);
|
|
QString rawFilesExt(KDcrawIface::DcrawBinary::instance()->rawFiles());
|
|
QString ext = fileInfo.extension(false).upper();
|
|
|
|
if (!fileInfo.exists() || ext.isEmpty() || !rawFilesExt.upper().contains(ext))
|
|
return false;
|
|
|
|
// Try to extract embedded thumbnail using dcraw with options:
|
|
// -c : write to stdout
|
|
// -e : Extract the camera-generated thumbnail, not the raw image (JPEG or a PPM file).
|
|
// Note : this code require at least dcraw version 8.x
|
|
|
|
command = DcrawBinary::path();
|
|
command += " -c -e ";
|
|
command += QFile::encodeName( KProcess::quote( path ) );
|
|
qDebug("Running RAW decoding command: %s", (const char*)command);
|
|
|
|
f = popen( command.data(), "r" );
|
|
|
|
if ( f == NULL )
|
|
return false;
|
|
|
|
file.open( IO_ReadOnly, f );
|
|
|
|
while ((len = file.readBlock(buffer, MAX_IPC_SIZE)) != 0)
|
|
{
|
|
if ( len == -1 )
|
|
{
|
|
file.close();
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
int oldSize = imgData.size();
|
|
imgData.resize( imgData.size() + len );
|
|
memcpy(imgData.data()+oldSize, buffer, len);
|
|
}
|
|
}
|
|
|
|
file.close();
|
|
pclose( f );
|
|
|
|
if ( !imgData.isEmpty() )
|
|
{
|
|
if (image.loadFromData( imgData ))
|
|
{
|
|
qDebug("Using embedded RAW preview extraction");
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool KDcraw::loadHalfPreview(QImage& image, const QString& path)
|
|
{
|
|
FILE *f=NULL;
|
|
QByteArray imgData;
|
|
const int MAX_IPC_SIZE = (1024*32);
|
|
char buffer[MAX_IPC_SIZE];
|
|
QFile file;
|
|
Q_LONG len;
|
|
QCString command;
|
|
|
|
QFileInfo fileInfo(path);
|
|
QString rawFilesExt(KDcrawIface::DcrawBinary::instance()->rawFiles());
|
|
QString ext = fileInfo.extension(false).upper();
|
|
|
|
if (!fileInfo.exists() || ext.isEmpty() || !rawFilesExt.upper().contains(ext))
|
|
return false;
|
|
|
|
// Try to use simple RAW extraction method in 8 bits ppm output.
|
|
// -c : write to stdout
|
|
// -h : Half-size color image (3x faster than -q)
|
|
// -a : Use automatic white balance
|
|
// -w : Use camera white balance, if possible
|
|
|
|
f=NULL;
|
|
command = DcrawBinary::path();
|
|
command += " -c -h -w -a ";
|
|
command += QFile::encodeName( KProcess::quote( path ) );
|
|
qDebug("Running RAW decoding command: %s", (const char*)command);
|
|
|
|
f = popen( command.data(), "r" );
|
|
|
|
if ( f == NULL )
|
|
return false;
|
|
|
|
file.open( IO_ReadOnly, f );
|
|
|
|
while ((len = file.readBlock(buffer, MAX_IPC_SIZE)) != 0)
|
|
{
|
|
if ( len == -1 )
|
|
{
|
|
file.close();
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
int oldSize = imgData.size();
|
|
imgData.resize( imgData.size() + len );
|
|
memcpy(imgData.data()+oldSize, buffer, len);
|
|
}
|
|
}
|
|
|
|
file.close();
|
|
pclose( f );
|
|
|
|
if ( !imgData.isEmpty() )
|
|
{
|
|
if (image.loadFromData( imgData ))
|
|
{
|
|
qDebug("Using reduced RAW picture extraction");
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool KDcraw::rawFileIdentify(DcrawInfoContainer& identify, const QString& path)
|
|
{
|
|
FILE *f=NULL;
|
|
QByteArray txtData;
|
|
const int MAX_IPC_SIZE = (1024*32);
|
|
char buffer[MAX_IPC_SIZE];
|
|
QFile file;
|
|
Q_LONG len;
|
|
QCString command;
|
|
|
|
QFileInfo fileInfo(path);
|
|
QString rawFilesExt(KDcrawIface::DcrawBinary::instance()->rawFiles());
|
|
QString ext = fileInfo.extension(false).upper();
|
|
|
|
if (!fileInfo.exists() || ext.isEmpty() || !rawFilesExt.upper().contains(ext))
|
|
return false;
|
|
|
|
// Try to get camera maker/model using dcraw with options:
|
|
// -i : identify files without decoding them.
|
|
// -v : verbose mode.
|
|
|
|
command = DcrawBinary::path();
|
|
command += " -i -v ";
|
|
command += QFile::encodeName( KProcess::quote( path ) );
|
|
qDebug("Running RAW decoding command: %s", (const char*)command);
|
|
|
|
f = popen( command.data(), "r" );
|
|
|
|
if ( f == NULL )
|
|
{
|
|
identify = DcrawInfoContainer();
|
|
return false;
|
|
}
|
|
|
|
file.open( IO_ReadOnly, f );
|
|
|
|
while ((len = file.readBlock(buffer, MAX_IPC_SIZE)) != 0)
|
|
{
|
|
if ( len == -1 )
|
|
{
|
|
identify = DcrawInfoContainer();
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
int oldSize = txtData.size();
|
|
txtData.resize( txtData.size() + len );
|
|
memcpy(txtData.data()+oldSize, buffer, len);
|
|
}
|
|
}
|
|
|
|
file.close();
|
|
pclose( f );
|
|
QString dcrawInfo(txtData);
|
|
|
|
if ( dcrawInfo.isEmpty() )
|
|
{
|
|
identify = DcrawInfoContainer();
|
|
return false;
|
|
}
|
|
|
|
int pos;
|
|
|
|
// Extract Time Stamp.
|
|
QString timeStampHeader("Timestamp: ");
|
|
pos = dcrawInfo.find(timeStampHeader);
|
|
if (pos != -1)
|
|
{
|
|
QString timeStamp = dcrawInfo.mid(pos).section('\n', 0, 0);
|
|
timeStamp.remove(0, timeStampHeader.length());
|
|
identify.dateTime = QDateTime::fromString(timeStamp);
|
|
}
|
|
|
|
// Extract Camera Maker.
|
|
QString makeHeader("Camera: ");
|
|
pos = dcrawInfo.find(makeHeader);
|
|
if (pos != -1)
|
|
{
|
|
QString make = dcrawInfo.mid(pos).section('\n', 0, 0);
|
|
make.remove(0, makeHeader.length());
|
|
identify.make = make;
|
|
}
|
|
|
|
// Extract Camera Model.
|
|
QString modelHeader("Model: ");
|
|
pos = dcrawInfo.find(modelHeader);
|
|
if (pos != -1)
|
|
{
|
|
QString model = dcrawInfo.mid(pos).section('\n', 0, 0);
|
|
model.remove(0, modelHeader.length());
|
|
identify.model = model;
|
|
}
|
|
|
|
// Extract Picture Owner.
|
|
QString ownerHeader("Owner: ");
|
|
pos = dcrawInfo.find(ownerHeader);
|
|
if (pos != -1)
|
|
{
|
|
QString owner = dcrawInfo.mid(pos).section('\n', 0, 0);
|
|
owner.remove(0, ownerHeader.length());
|
|
identify.owner = owner;
|
|
}
|
|
|
|
// Extract DNG Version.
|
|
QString DNGVersionHeader("DNG Version: ");
|
|
pos = dcrawInfo.find(DNGVersionHeader);
|
|
if (pos != -1)
|
|
{
|
|
QString DNGVersion = dcrawInfo.mid(pos).section('\n', 0, 0);
|
|
DNGVersion.remove(0, DNGVersionHeader.length());
|
|
identify.DNGVersion = DNGVersion;
|
|
}
|
|
|
|
// Extract ISO Speed.
|
|
QString isoSpeedHeader("ISO speed: ");
|
|
pos = dcrawInfo.find(isoSpeedHeader);
|
|
if (pos != -1)
|
|
{
|
|
QString isoSpeed = dcrawInfo.mid(pos).section('\n', 0, 0);
|
|
isoSpeed.remove(0, isoSpeedHeader.length());
|
|
identify.sensitivity = isoSpeed.toLong();
|
|
}
|
|
|
|
// Extract Shutter Speed.
|
|
QString shutterSpeedHeader("Shutter: ");
|
|
pos = dcrawInfo.find(shutterSpeedHeader);
|
|
if (pos != -1)
|
|
{
|
|
QString shutterSpeed = dcrawInfo.mid(pos).section('\n', 0, 0);
|
|
shutterSpeed.remove(0, shutterSpeedHeader.length());
|
|
|
|
if (shutterSpeed.startsWith("1/"))
|
|
shutterSpeed.remove(0, 2); // remove "1/" at start of string.
|
|
|
|
shutterSpeed.remove(shutterSpeed.length()-4, 4); // remove " sec" at end of string.
|
|
identify.exposureTime = shutterSpeed.toFloat();
|
|
}
|
|
|
|
// Extract Aperture.
|
|
QString apertureHeader("Aperture: f/");
|
|
pos = dcrawInfo.find(apertureHeader);
|
|
if (pos != -1)
|
|
{
|
|
QString aperture = dcrawInfo.mid(pos).section('\n', 0, 0);
|
|
aperture.remove(0, apertureHeader.length());
|
|
identify.aperture = aperture.toFloat();
|
|
}
|
|
|
|
// Extract Focal Length.
|
|
QString focalLengthHeader("Focal Length: ");
|
|
pos = dcrawInfo.find(focalLengthHeader);
|
|
if (pos != -1)
|
|
{
|
|
QString focalLength = dcrawInfo.mid(pos).section('\n', 0, 0);
|
|
focalLength.remove(0, focalLengthHeader.length());
|
|
focalLength.remove(focalLength.length()-3, 3); // remove " mm" at end of string.
|
|
identify.focalLength = focalLength.toFloat();
|
|
}
|
|
|
|
// Extract Image Size.
|
|
|
|
QString imageSizeHeader("Image size: ");
|
|
pos = dcrawInfo.find(imageSizeHeader);
|
|
if (pos != -1)
|
|
{
|
|
QString imageSize = dcrawInfo.mid(pos).section('\n', 0, 0);
|
|
imageSize.remove(0, imageSizeHeader.length());
|
|
int width = imageSize.section(" x ", 0, 0).toInt();
|
|
int height = imageSize.section(" x ", 1, 1).toInt();
|
|
identify.imageSize = QSize(width, height);
|
|
}
|
|
|
|
// Extract "Has an embedded ICC profile" flag.
|
|
|
|
QString hasIccProfileHeader("Embedded ICC profile: ");
|
|
pos = dcrawInfo.find(hasIccProfileHeader);
|
|
if (pos != -1)
|
|
{
|
|
QString hasIccProfile = dcrawInfo.mid(pos).section('\n', 0, 0);
|
|
hasIccProfile.remove(0, hasIccProfileHeader.length());
|
|
if (hasIccProfile.contains("yes"))
|
|
identify.hasIccProfile = true;
|
|
else
|
|
identify.hasIccProfile = false;
|
|
}
|
|
|
|
// Check if picture is decodable.
|
|
|
|
identify.isDecodable = true;
|
|
pos = dcrawInfo.find("Cannot decode file");
|
|
if (pos != -1)
|
|
identify.isDecodable = false;
|
|
|
|
// Extract "Has Secondary Pixel" flag.
|
|
|
|
QString hasSecondaryPixelHeader("Secondary pixels: ");
|
|
pos = dcrawInfo.find(hasSecondaryPixelHeader);
|
|
if (pos != -1)
|
|
{
|
|
QString hasSecondaryPixel = dcrawInfo.mid(pos).section('\n', 0, 0);
|
|
hasSecondaryPixel.remove(0, hasSecondaryPixelHeader.length());
|
|
if (hasSecondaryPixel.contains("yes"))
|
|
identify.hasSecondaryPixel = true;
|
|
else
|
|
identify.hasSecondaryPixel = false;
|
|
}
|
|
|
|
// Extract Pixel Aspect Ratio.
|
|
QString aspectRatioHeader("Pixel Aspect Ratio: ");
|
|
pos = dcrawInfo.find(aspectRatioHeader);
|
|
if (pos != -1)
|
|
{
|
|
QString aspectRatio = dcrawInfo.mid(pos).section('\n', 0, 0);
|
|
aspectRatio.remove(0, aspectRatioHeader.length());
|
|
identify.pixelAspectRatio = aspectRatio.toFloat();
|
|
}
|
|
|
|
// Extract Raw Colors.
|
|
QString rawColorsHeader("Raw colors: ");
|
|
pos = dcrawInfo.find(rawColorsHeader);
|
|
if (pos != -1)
|
|
{
|
|
QString rawColors = dcrawInfo.mid(pos).section('\n', 0, 0);
|
|
rawColors.remove(0, rawColorsHeader.length());
|
|
identify.rawColors = rawColors.toInt();
|
|
}
|
|
|
|
// Extract Filter Pattern.
|
|
QString filterHeader("Filter pattern: ");
|
|
pos = dcrawInfo.find(filterHeader);
|
|
if (pos != -1)
|
|
{
|
|
QString filter = dcrawInfo.mid(pos).section('\n', 0, 0);
|
|
filter.remove(0, filterHeader.length());
|
|
identify.filterPattern = filter;
|
|
}
|
|
|
|
// Extract Daylight Multipliers.
|
|
QString daylightMultHeader("Daylight multipliers: ");
|
|
pos = dcrawInfo.find(daylightMultHeader);
|
|
if (pos != -1)
|
|
{
|
|
QString daylightMult = dcrawInfo.mid(pos).section('\n', 0, 0);
|
|
daylightMult.remove(0, daylightMultHeader.length());
|
|
identify.daylightMult[0] = daylightMult.section(" ", 0, 0).toDouble();
|
|
identify.daylightMult[1] = daylightMult.section(" ", 1, 1).toDouble();
|
|
identify.daylightMult[2] = daylightMult.section(" ", 2, 2).toDouble();
|
|
}
|
|
|
|
// Extract Camera Multipliers.
|
|
QString cameraMultHeader("Camera multipliers: ");
|
|
pos = dcrawInfo.find(cameraMultHeader);
|
|
if (pos != -1)
|
|
{
|
|
QString cameraMult = dcrawInfo.mid(pos).section('\n', 0, 0);
|
|
cameraMult.remove(0, cameraMultHeader.length());
|
|
identify.cameraMult[0] = cameraMult.section(" ", 0, 0).toDouble();
|
|
identify.cameraMult[1] = cameraMult.section(" ", 1, 1).toDouble();
|
|
identify.cameraMult[2] = cameraMult.section(" ", 2, 2).toDouble();
|
|
identify.cameraMult[3] = cameraMult.section(" ", 3, 3).toDouble();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------------
|
|
|
|
bool KDcraw::decodeHalfRAWImage(const QString& filePath, RawDecodingSettings rawDecodingSettings,
|
|
QByteArray &imageData, int &width, int &height, int &rgbmax)
|
|
{
|
|
m_rawDecodingSettings = rawDecodingSettings;
|
|
m_rawDecodingSettings.halfSizeColorImage = true;
|
|
return (loadFromDcraw(filePath, imageData, width, height, rgbmax));
|
|
}
|
|
|
|
bool KDcraw::decodeRAWImage(const QString& filePath, RawDecodingSettings rawDecodingSettings,
|
|
QByteArray &imageData, int &width, int &height, int &rgbmax)
|
|
{
|
|
m_rawDecodingSettings = rawDecodingSettings;
|
|
return (loadFromDcraw(filePath, imageData, width, height, rgbmax));
|
|
}
|
|
|
|
bool KDcraw::checkToCancelWaitingData()
|
|
{
|
|
return m_cancel;
|
|
}
|
|
|
|
bool KDcraw::checkToCancelRecievingData()
|
|
{
|
|
return m_cancel;
|
|
}
|
|
|
|
void KDcraw::setWaitingDataProgress(double)
|
|
{
|
|
}
|
|
|
|
void KDcraw::setRecievingDataProgress(double)
|
|
{
|
|
}
|
|
|
|
bool KDcraw::loadFromDcraw(const QString& filePath, QByteArray &imageData,
|
|
int &width, int &height, int &rgbmax)
|
|
{
|
|
m_cancel = false;
|
|
d->dataPos = 0;
|
|
d->filePath = filePath;
|
|
d->running = true;
|
|
d->normalExit = false;
|
|
d->process = 0;
|
|
d->data = 0;
|
|
d->width = 0;
|
|
d->height = 0;
|
|
d->rgbmax = 0;
|
|
|
|
// trigger startProcess and loop to wait dcraw decoding
|
|
QApplication::postEvent(this, new QCustomEvent(QEvent::User));
|
|
|
|
// The time from starting dcraw to when it first outputs something takes
|
|
// much longer than the time while it outputs the data and the time while
|
|
// we process the data.
|
|
// We do not have progress information for this, but it is much more promising to the user
|
|
// if there is progress which does not stay at a fixed value.
|
|
// So we make up some progress (0% - 40%), using the file size as an indicator how long it might take.
|
|
QTime dcrawStartTime = QTime::currentTime();
|
|
int fileSize = QFileInfo(filePath).size();
|
|
|
|
// This is the magic number that describes how fast the function grows
|
|
// It _should_ be dependent on how fast the computer is, but we do not have this piece of information
|
|
// So this is a number that works well on my computer.
|
|
double K50 = 3000.0*fileSize;
|
|
double part = 0;
|
|
int checkpointTime = 0;
|
|
int checkpoint = 0;
|
|
|
|
// The shuttingDown is a hack needed to prevent hanging when this KProcess-based loader
|
|
// is waiting for the process to finish, but the main thread is waiting
|
|
// for the thread to finish and no KProcess events are delivered.
|
|
// Remove when porting to Qt4.
|
|
while (d->running && !checkToCancelRecievingData())
|
|
{
|
|
if (d->dataPos == 0)
|
|
{
|
|
int elapsedMsecs = dcrawStartTime.msecsTo(QTime::currentTime());
|
|
if (elapsedMsecs > checkpointTime)
|
|
checkpointTime += 300;
|
|
|
|
// What we do here is a sigmoidal curve, it starts slowly,
|
|
// then grows more rapidly, slows down again and
|
|
// get asymptotically closer to the maximum.
|
|
// (this is the Hill Equation, 2.8 the Hill Coefficient, to pour some blood in this)
|
|
double elapsedMsecsPow = pow(elapsedMsecs, 2.8);
|
|
part = (elapsedMsecsPow) / (K50 + elapsedMsecsPow);
|
|
|
|
// While we waiting to receive data, progress from 0% to 40%
|
|
setWaitingDataProgress(0.4*part);
|
|
}
|
|
else if (d->dataPos > checkpoint)
|
|
{
|
|
// While receiving data, progress from 40% to 70%
|
|
double delta = 0.3 + 0.4 - 0.4*part;
|
|
int imageSize = d->width * d->height * (m_rawDecodingSettings.sixteenBitsImage ? 6 : 3);
|
|
checkpoint += (int)(imageSize / (20 * delta));
|
|
setRecievingDataProgress(0.4*part + delta * (((float)d->dataPos)/((float)imageSize)));
|
|
}
|
|
|
|
QMutexLocker lock(&d->mutex);
|
|
d->condVar.wait(&d->mutex, 10);
|
|
}
|
|
|
|
if (!d->normalExit || m_cancel)
|
|
{
|
|
delete [] d->data;
|
|
d->data = 0;
|
|
return false;
|
|
}
|
|
|
|
// Copy decoded image data to byte array.
|
|
width = d->width;
|
|
height = d->height;
|
|
rgbmax = d->rgbmax;
|
|
imageData = QByteArray(d->width * d->height * (m_rawDecodingSettings.sixteenBitsImage ? 6 : 3));
|
|
memcpy(imageData.data(), d->data, imageData.size());
|
|
|
|
delete [] d->data;
|
|
d->data = 0;
|
|
|
|
return true;
|
|
}
|
|
|
|
void KDcraw::customEvent(QCustomEvent *)
|
|
{
|
|
// KProcess (because of QSocketNotifier) is not reentrant.
|
|
// We must only use it from the main thread.
|
|
startProcess();
|
|
|
|
// set up timer to call continueQuery at regular intervals
|
|
if (d->running)
|
|
{
|
|
d->queryTimer = new QTimer;
|
|
connect(d->queryTimer, SIGNAL(timeout()),
|
|
this, SLOT(slotContinueQuery()));
|
|
d->queryTimer->start(30);
|
|
}
|
|
}
|
|
|
|
void KDcraw::slotContinueQuery()
|
|
{
|
|
// this is called from the timer
|
|
|
|
if (checkToCancelWaitingData())
|
|
{
|
|
d->process->kill();
|
|
}
|
|
}
|
|
|
|
void KDcraw::startProcess()
|
|
{
|
|
if (m_cancel)
|
|
{
|
|
d->running = false;
|
|
d->normalExit = false;
|
|
return;
|
|
}
|
|
|
|
// create KProcess and build argument list
|
|
|
|
d->process = new KProcess;
|
|
|
|
connect(d->process, SIGNAL(processExited(KProcess *)),
|
|
this, SLOT(slotProcessExited(KProcess *)));
|
|
|
|
connect(d->process, SIGNAL(receivedStdout(KProcess *, char *, int)),
|
|
this, SLOT(slotReceivedStdout(KProcess *, char *, int)));
|
|
|
|
connect(d->process, SIGNAL(receivedStderr(KProcess *, char *, int)),
|
|
this, SLOT(slotReceivedStderr(KProcess *, char *, int)));
|
|
|
|
// run dcraw with options:
|
|
// -c : write to stdout
|
|
// -v : verboze mode.
|
|
//
|
|
// -4 : 16bit ppm output
|
|
//
|
|
// -f : Interpolate RGB as four colors. This blurs the image a little, but it eliminates false 2x2 mesh patterns.
|
|
// -a : Use automatic white balance
|
|
// -w : Use camera white balance, if possible
|
|
// -n : Use wavelets to erase noise while preserving real detail.
|
|
// -j : Do not stretch the image to its correct aspect ratio.
|
|
// -q : Use an interpolation method.
|
|
// -p : Use the input ICC profiles to define the camera's raw colorspace.
|
|
// -o : Use ICC profiles to define the output colorspace.
|
|
// -h : Output a half-size color image. Twice as fast as -q 0.
|
|
// -b : set Brightness value.
|
|
// -k : set Black Point value.
|
|
// -r : set Raw Color Balance Multipliers.
|
|
// -C : set Correct chromatic aberration correction.
|
|
|
|
*d->process << DcrawBinary::path();
|
|
*d->process << "-c";
|
|
*d->process << "-v";
|
|
|
|
if (m_rawDecodingSettings.sixteenBitsImage)
|
|
*d->process << "-4";
|
|
|
|
if (m_rawDecodingSettings.halfSizeColorImage)
|
|
*d->process << "-h";
|
|
|
|
if (m_rawDecodingSettings.RGBInterpolate4Colors)
|
|
*d->process << "-f";
|
|
|
|
if (m_rawDecodingSettings.DontStretchPixels)
|
|
*d->process << "-j";
|
|
|
|
*d->process << "-H";
|
|
*d->process << QString::number(m_rawDecodingSettings.unclipColors);
|
|
|
|
*d->process << "-b";
|
|
*d->process << QString::number(m_rawDecodingSettings.brightness);
|
|
|
|
if (m_rawDecodingSettings.enableBlackPoint)
|
|
{
|
|
*d->process << "-k";
|
|
*d->process << QString::number(m_rawDecodingSettings.blackPoint);
|
|
}
|
|
|
|
switch (m_rawDecodingSettings.whiteBalance)
|
|
{
|
|
case RawDecodingSettings::NONE:
|
|
break;
|
|
case RawDecodingSettings::CAMERA:
|
|
*d->process << "-w";
|
|
break;
|
|
case RawDecodingSettings::AUTO:
|
|
*d->process << "-a";
|
|
break;
|
|
case RawDecodingSettings::CUSTOM:
|
|
/* Convert between Temperature and RGB.
|
|
*/
|
|
double T;
|
|
double RGB[3];
|
|
double xD, yD, X, Y, Z;
|
|
DcrawInfoContainer identify;
|
|
|
|
T = 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_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 completly wrong WB
|
|
*/
|
|
if (rawFileIdentify (identify, d->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];
|
|
qDebug("Warning: cannot get daylight multipliers");
|
|
}
|
|
|
|
*d->process << "-r";
|
|
*d->process << QString::number(RGB[0], 'f', 5);
|
|
*d->process << QString::number(RGB[1], 'f', 5);
|
|
*d->process << QString::number(RGB[2], 'f', 5);
|
|
*d->process << QString::number(RGB[1], 'f', 5);
|
|
break;
|
|
}
|
|
|
|
*d->process << "-q";
|
|
*d->process << QString::number(m_rawDecodingSettings.RAWQuality);
|
|
|
|
if (m_rawDecodingSettings.enableNoiseReduction)
|
|
{
|
|
*d->process << "-n";
|
|
*d->process << QString::number(m_rawDecodingSettings.NRThreshold);
|
|
}
|
|
|
|
if (m_rawDecodingSettings.enableCACorrection)
|
|
{
|
|
*d->process << "-C";
|
|
*d->process << QString::number(m_rawDecodingSettings.caMultiplier[0], 'f', 5);
|
|
*d->process << QString::number(m_rawDecodingSettings.caMultiplier[1], 'f', 5);
|
|
}
|
|
|
|
*d->process << "-o";
|
|
*d->process << QString::number(m_rawDecodingSettings.outputColorSpace);
|
|
|
|
*d->process << QFile::encodeName(d->filePath);
|
|
|
|
QString args;
|
|
for (uint i = 0 ; i < d->process->args().count(); i++)
|
|
{
|
|
args.append(d->process->args()[i]);
|
|
args.append(QString(" "));
|
|
}
|
|
|
|
qDebug("Running RAW decoding command: %s", args.ascii());
|
|
|
|
// actually start the process
|
|
if ( !d->process->start(KProcess::NotifyOnExit,
|
|
KProcess::Communication(KProcess::Stdout | KProcess::Stderr)) )
|
|
{
|
|
qWarning("Failed to start RAW decoding");
|
|
delete d->process;
|
|
d->process = 0;
|
|
d->running = false;
|
|
d->normalExit = false;
|
|
return;
|
|
}
|
|
}
|
|
|
|
void KDcraw::slotProcessExited(KProcess *)
|
|
{
|
|
// set variables, clean up, wake up loader thread
|
|
|
|
QMutexLocker lock(&d->mutex);
|
|
d->running = false;
|
|
d->normalExit = d->process->normalExit() && d->process->exitStatus() == 0;
|
|
delete d->process;
|
|
d->process = 0;
|
|
delete d->queryTimer;
|
|
d->queryTimer = 0;
|
|
d->condVar.wakeAll();
|
|
}
|
|
|
|
void KDcraw::slotReceivedStdout(KProcess *, char *buffer, int buflen)
|
|
{
|
|
if (!d->data)
|
|
{
|
|
// first data packet:
|
|
// Parse PPM header to find out size and allocate buffer
|
|
|
|
// PPM header is "P6 <width> <height> <maximum rgb value "
|
|
// where the blanks are newline characters
|
|
|
|
QString magic = QString::fromAscii(buffer, 2);
|
|
if (magic != "P6")
|
|
{
|
|
qWarning("Cannot parse header from RAW decoding: Magic is: %s", magic.ascii());
|
|
d->process->kill();
|
|
return;
|
|
}
|
|
|
|
// Find the third newline that marks the header end in a dcraw generated ppm.
|
|
int i = 0;
|
|
int counter = 0;
|
|
|
|
while (i < buflen)
|
|
{
|
|
if (counter == 3) break;
|
|
if (buffer[i] == '\n')
|
|
{
|
|
counter++;
|
|
}
|
|
++i;
|
|
}
|
|
|
|
QStringList splitlist = QStringList::split("\n", QString::fromAscii(buffer, i));
|
|
QStringList sizes = QStringList::split(" ", splitlist[1]);
|
|
if (splitlist.size() < 3 || sizes.size() < 2)
|
|
{
|
|
qWarning("Cannot parse header from RAW decoding: Could not split");
|
|
d->process->kill();
|
|
return;
|
|
}
|
|
|
|
d->width = sizes[0].toInt();
|
|
d->height = sizes[1].toInt();
|
|
d->rgbmax = splitlist[2].toInt();
|
|
|
|
#ifdef ENABLE_DEBUG_MESSAGES
|
|
qDebug("Parsed PPM header: width %i height %i rgbmax %i", d->width, d->height, d->rgbmax);
|
|
#endif
|
|
|
|
// cut header from data for memcpy below
|
|
buffer += i;
|
|
buflen -= i;
|
|
|
|
// allocate buffer
|
|
d->data = new uchar[d->width * d->height * (m_rawDecodingSettings.sixteenBitsImage ? 6 : 3)];
|
|
d->dataPos = 0;
|
|
}
|
|
|
|
// copy data to buffer
|
|
memcpy(d->data + d->dataPos, buffer, buflen);
|
|
d->dataPos += buflen;
|
|
}
|
|
|
|
void KDcraw::slotReceivedStderr(KProcess *, char *buffer, int buflen)
|
|
{
|
|
QCString message(buffer, buflen);
|
|
qDebug("RAW decoding StdErr: %s", (const char*)message);
|
|
}
|
|
|
|
} // namespace KDcrawIface
|