You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
429 lines
12 KiB
C++
429 lines
12 KiB
C++
/*!
|
|
\file cimage.cpp
|
|
|
|
*/
|
|
|
|
#include "cimage.h"
|
|
|
|
#include "opencv2/imgproc/types_c.h"
|
|
#include "opencv2/imgproc.hpp"
|
|
|
|
#include <QTransform>
|
|
#include <QFileInfo>
|
|
|
|
#include <QDebug>
|
|
#include <QElapsedTimer>
|
|
|
|
|
|
cImage::cImage() :
|
|
QImage(),
|
|
m_isChromatic(true),
|
|
m_camType(camera_unknown),
|
|
m_raw(false)
|
|
{
|
|
}
|
|
|
|
cImage::cImage(const QSize &size, QImage::Format format) :
|
|
QImage(size, format),
|
|
m_isChromatic(true),
|
|
m_camType(camera_unknown),
|
|
m_raw(false)
|
|
{
|
|
}
|
|
|
|
cImage::cImage(int width, int height, QImage::Format format) :
|
|
QImage(width, height, format),
|
|
m_isChromatic(true),
|
|
m_camType(camera_unknown),
|
|
m_raw(false)
|
|
{
|
|
}
|
|
|
|
cImage::cImage(uchar *data, int width, int height, QImage::Format format, QImageCleanupFunction cleanupFunction, void *cleanupInfo) :
|
|
QImage(data, width, height, format, cleanupFunction, cleanupInfo),
|
|
m_isChromatic(true),
|
|
m_camType(camera_unknown),
|
|
m_raw(false)
|
|
{
|
|
}
|
|
|
|
cImage::cImage(const uchar *data, int width, int height, QImage::Format format, QImageCleanupFunction cleanupFunction, void *cleanupInfo) :
|
|
QImage(data, width, height, format, cleanupFunction, cleanupInfo),
|
|
m_isChromatic(true),
|
|
m_camType(camera_unknown),
|
|
m_raw(false)
|
|
{
|
|
}
|
|
|
|
cImage::cImage(uchar *data, int width, int height, int bytesPerLine, QImage::Format format, QImageCleanupFunction cleanupFunction, void *cleanupInfo) :
|
|
QImage(data, width, height, bytesPerLine, format, cleanupFunction, cleanupInfo),
|
|
m_isChromatic(true),
|
|
m_camType(camera_unknown),
|
|
m_raw(false)
|
|
{
|
|
}
|
|
|
|
cImage::cImage(const uchar *data, int width, int height, int bytesPerLine, QImage::Format format, QImageCleanupFunction cleanupFunction, void *cleanupInfo) :
|
|
QImage(data, width, height, bytesPerLine, format, cleanupFunction, cleanupInfo),
|
|
m_isChromatic(true),
|
|
m_camType(camera_unknown),
|
|
m_raw(false)
|
|
{
|
|
}
|
|
|
|
cImage::cImage(const QString &fileName, const char *format) :
|
|
QImage(),
|
|
m_isChromatic(true),
|
|
m_camType(camera_unknown),
|
|
m_raw(false)
|
|
{
|
|
load(fileName, format);
|
|
}
|
|
|
|
cImage::cImage(const QImage &image) :
|
|
QImage(image),
|
|
m_isChromatic(true),
|
|
m_camType(camera_unknown),
|
|
m_raw(false)
|
|
{
|
|
}
|
|
|
|
cImage::cImage(QImage &&other) :
|
|
QImage(other),
|
|
m_isChromatic(true),
|
|
m_camType(camera_unknown),
|
|
m_raw(false)
|
|
{
|
|
}
|
|
|
|
bool cImage::load(const QString &fileName, const char *format)
|
|
{
|
|
QFileInfo fileInfo(fileName);
|
|
|
|
if(!fileInfo.suffix().compare("NEF", Qt::CaseInsensitive))
|
|
return(loadRAW(fileName));
|
|
|
|
if(QImage::load(fileName, format))
|
|
return(true);
|
|
|
|
return(loadRAW(fileName));
|
|
}
|
|
|
|
bool cImage::openBuffer(const QString &fileName, const QSharedPointer<QByteArray>& ba, LibRaw& iProcessor)
|
|
{
|
|
int error = LIBRAW_DATA_ERROR;
|
|
QFileInfo fi(fileName);
|
|
|
|
if(fi.suffix().contains("iiq", Qt::CaseInsensitive) || !ba || ba->isEmpty())
|
|
error = iProcessor.open_file(fileName.toStdString().c_str());
|
|
else
|
|
{
|
|
if(ba->isEmpty() || ba->size() < 100)
|
|
return(false);
|
|
|
|
error = iProcessor.open_buffer((void*)ba->constData(), static_cast<size_t>(ba->size()));
|
|
}
|
|
|
|
return (error == LIBRAW_SUCCESS);
|
|
}
|
|
|
|
void cImage::detectSpecialCamera(const LibRaw & iProcessor)
|
|
{
|
|
if(QString(iProcessor.imgdata.idata.model) == "IQ260 Achromatic")
|
|
m_isChromatic = false;
|
|
|
|
if (QString(iProcessor.imgdata.idata.model).contains("IQ260"))
|
|
m_camType = camera_iiq;
|
|
else if (!QString(iProcessor.imgdata.idata.make).compare("Canon", Qt::CaseInsensitive))
|
|
m_camType = camera_canon;
|
|
}
|
|
|
|
cv::Mat cImage::demosaic(LibRaw & iProcessor)
|
|
{
|
|
cv::Mat rawMat = cv::Mat(iProcessor.imgdata.sizes.height, iProcessor.imgdata.sizes.width, CV_16UC1);
|
|
double dynamicRange = static_cast<double>(iProcessor.imgdata.color.maximum - iProcessor.imgdata.color.black);
|
|
|
|
// normalize all image values
|
|
for(int rIdx = 0; rIdx < rawMat.rows; rIdx++)
|
|
{
|
|
unsigned short *ptrRaw = rawMat.ptr<unsigned short>(rIdx);
|
|
|
|
for (int cIdx = 0; cIdx < rawMat.cols; cIdx++)
|
|
{
|
|
int colIdx = iProcessor.COLOR(rIdx, cIdx);
|
|
double val = static_cast<double>(iProcessor.imgdata.image[(rawMat.cols*rIdx) + cIdx][colIdx]);
|
|
|
|
// normalize the value w.r.t the black point defined
|
|
val = (val - iProcessor.imgdata.color.black) / dynamicRange;
|
|
ptrRaw[cIdx] = clip<unsigned short>(val * USHRT_MAX); // for conversion to 16U
|
|
}
|
|
}
|
|
|
|
// no demosaicing
|
|
if(m_isChromatic)
|
|
{
|
|
unsigned long type = static_cast<unsigned long>(iProcessor.imgdata.idata.filters);
|
|
type = type & 255;
|
|
|
|
cv::Mat rgbImg;
|
|
|
|
//define bayer pattern
|
|
if(type == 180)
|
|
cvtColor(rawMat, rgbImg, CV_BayerBG2RGB); //bitmask 10 11 01 00 -> 3(G) 2(B) 1(G) 0(R) -> RG RG RG
|
|
else if(type == 30)
|
|
cvtColor(rawMat, rgbImg, CV_BayerRG2RGB); //bitmask 00 01 11 10 -> 0 1 3 2
|
|
else if(type == 225)
|
|
cvtColor(rawMat, rgbImg, CV_BayerGB2RGB); //bitmask 11 10 00 01
|
|
else if(type == 75)
|
|
cvtColor(rawMat, rgbImg, CV_BayerGR2RGB); //bitmask 01 00 10 11
|
|
else
|
|
{
|
|
qWarning() << "Wrong Bayer Pattern (not BG, RG, GB, GR)\n";
|
|
return cv::Mat();
|
|
}
|
|
|
|
rawMat = rgbImg;
|
|
}
|
|
|
|
// 16U (1 or 3 channeled) Mat
|
|
return(rawMat);
|
|
}
|
|
|
|
cv::Mat cImage::prepareImg(const LibRaw & iProcessor)
|
|
{
|
|
cv::Mat rawMat = cv::Mat(iProcessor.imgdata.sizes.height, iProcessor.imgdata.sizes.width, CV_16UC3, cv::Scalar(0));
|
|
double dynamicRange = static_cast<double>(iProcessor.imgdata.color.maximum - iProcessor.imgdata.color.black);
|
|
|
|
// normalization function
|
|
auto normalize = [&](double val)
|
|
{
|
|
val = (val - iProcessor.imgdata.color.black) / dynamicRange;
|
|
return clip<unsigned short>(val * USHRT_MAX);
|
|
};
|
|
|
|
for (int rIdx = 0; rIdx < rawMat.rows; rIdx++)
|
|
{
|
|
unsigned short *ptrI = rawMat.ptr<unsigned short>(rIdx);
|
|
|
|
for (int cIdx = 0; cIdx < rawMat.cols; cIdx++)
|
|
{
|
|
|
|
*ptrI = normalize(iProcessor.imgdata.image[rawMat.cols*rIdx + cIdx][0]);
|
|
ptrI++;
|
|
*ptrI = normalize(iProcessor.imgdata.image[rawMat.cols*rIdx + cIdx][1]);
|
|
ptrI++;
|
|
*ptrI = normalize(iProcessor.imgdata.image[rawMat.cols*rIdx + cIdx][2]);
|
|
ptrI++;
|
|
}
|
|
}
|
|
|
|
return rawMat;
|
|
}
|
|
|
|
void cImage::whiteBalance(const LibRaw & iProcessor, cv::Mat & img)
|
|
{
|
|
// white balance must not be empty at this point
|
|
cv::Mat wb = whiteMultipliers(iProcessor);
|
|
const float* wbp = wb.ptr<float>();
|
|
assert(wb.cols == 4);
|
|
|
|
for(int rIdx = 0; rIdx < img.rows; rIdx++)
|
|
{
|
|
unsigned short *ptr = img.ptr<unsigned short>(rIdx);
|
|
|
|
for (int cIdx = 0; cIdx < img.cols; cIdx++)
|
|
{
|
|
//apply white balance correction
|
|
unsigned short r = clip<unsigned short>(*ptr * wbp[0]);
|
|
unsigned short g = clip<unsigned short>(*(ptr+1) * wbp[1]);
|
|
unsigned short b = clip<unsigned short>(*(ptr+2) * wbp[2]);
|
|
|
|
//apply color correction
|
|
int cr = qRound(iProcessor.imgdata.color.rgb_cam[0][0] * r +
|
|
iProcessor.imgdata.color.rgb_cam[0][1] * g +
|
|
iProcessor.imgdata.color.rgb_cam[0][2] * b);
|
|
int cg = qRound(iProcessor.imgdata.color.rgb_cam[1][0] * r +
|
|
iProcessor.imgdata.color.rgb_cam[1][1] * g +
|
|
iProcessor.imgdata.color.rgb_cam[1][2] * b);
|
|
int cb = qRound(iProcessor.imgdata.color.rgb_cam[2][0] * r +
|
|
iProcessor.imgdata.color.rgb_cam[2][1] * g +
|
|
iProcessor.imgdata.color.rgb_cam[2][2] * b);
|
|
|
|
// clip & save color corrected values
|
|
*ptr = clip<unsigned short>(cr);
|
|
ptr++;
|
|
*ptr = clip<unsigned short>(cg);
|
|
ptr++;
|
|
*ptr = clip<unsigned short>(cb);
|
|
ptr++;
|
|
}
|
|
}
|
|
}
|
|
|
|
cv::Mat cImage::whiteMultipliers(const LibRaw & iProcessor)
|
|
{
|
|
// get camera white balance multipliers
|
|
cv::Mat wm(1, 4, CV_32FC1);
|
|
|
|
float* wmp = wm.ptr<float>();
|
|
|
|
for(int idx = 0; idx < wm.cols; idx++)
|
|
wmp[idx] = iProcessor.imgdata.color.cam_mul[idx];
|
|
|
|
if(wmp[3] == 0.0f)
|
|
wmp[3] = wmp[1]; // take green (usually its RGBG)
|
|
|
|
// normalize white balance multipliers
|
|
float w = static_cast<float>(cv::sum(wm)[0] / 4.0f);
|
|
float maxW = 1.0f;
|
|
|
|
//clipping according the camera model
|
|
//if w > 2.0 maxW is 256, otherwise 512
|
|
//tested empirically
|
|
//check if it can be defined by some metadata settings?
|
|
if(w > 2.0f)
|
|
maxW = 255.0f;
|
|
if(w > 2.0f && m_camType == camera_canon)
|
|
maxW = 511.0f; // some cameras would even need ~800 - why?
|
|
|
|
//normalize white point
|
|
wm /= maxW;
|
|
|
|
// 1 x 4 32FC1 white balance vector
|
|
return wm;
|
|
}
|
|
|
|
void cImage::gammaCorrection(const LibRaw & iProcessor, cv::Mat& img)
|
|
{
|
|
// white balance must not be empty at this point
|
|
cv::Mat gt = gammaTable(iProcessor);
|
|
const unsigned short* gammaLookup = gt.ptr<unsigned short>();
|
|
assert(gt.cols == USHRT_MAX);
|
|
|
|
for(int rIdx = 0; rIdx < img.rows; rIdx++)
|
|
{
|
|
unsigned short *ptr = img.ptr<unsigned short>(rIdx);
|
|
|
|
for(int cIdx = 0; cIdx < img.cols * img.channels(); cIdx++)
|
|
{
|
|
// values close to 0 are treated linear
|
|
if (ptr[cIdx] <= 5) // 0.018 * 255
|
|
ptr[cIdx] = static_cast<unsigned short>(qRound(ptr[cIdx] * static_cast<double>(iProcessor.imgdata.params.gamm[1]) / 255.0));
|
|
else
|
|
ptr[cIdx] = gammaLookup[ptr[cIdx]];
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
cv::Mat cImage::gammaTable(const LibRaw & iProcessor)
|
|
{
|
|
// OK this is an instance of reverse engineering:
|
|
// we found out that the values of (at least) the PhaseOne's achromatic back have to be doubled
|
|
// our images are no close to what their software (Capture One does) - only the gamma correction
|
|
// seems to be slightly different... -> now we can load compressed IIQs that are not supported by PS : )
|
|
double cameraHackMlp = (QString(iProcessor.imgdata.idata.model) == "IQ260 Achromatic") ? 2.0 : 1.0;
|
|
|
|
//read gamma value and create gamma table
|
|
double gamma = static_cast<double>(iProcessor.imgdata.params.gamm[0]);
|
|
|
|
cv::Mat gmt(1, USHRT_MAX, CV_16UC1);
|
|
unsigned short* gmtp = gmt.ptr<unsigned short>();
|
|
|
|
for (int idx = 0; idx < gmt.cols; idx++) {
|
|
gmtp[idx] = clip<unsigned short>(qRound((1.099*std::pow(static_cast<double>(idx) / USHRT_MAX, gamma) - 0.099) * 255 * cameraHackMlp));
|
|
}
|
|
|
|
// a 1 x 65535 U16 gamma table
|
|
return gmt;
|
|
}
|
|
|
|
QImage cImage::raw2Img(const LibRaw & iProcessor, cv::Mat & img)
|
|
{
|
|
//check the pixel aspect ratio of the raw image
|
|
if(iProcessor.imgdata.sizes.pixel_aspect != 1.0f)
|
|
cv::resize(img, img, cv::Size(), static_cast<double>(iProcessor.imgdata.sizes.pixel_aspect), 1.0f);
|
|
|
|
// revert back to 8-bit image
|
|
img.convertTo(img, CV_8U);
|
|
|
|
// TODO: for now - fix this!
|
|
if(img.channels() == 1)
|
|
cv::cvtColor(img, img, CV_GRAY2RGB);
|
|
|
|
return(mat2QImage(img));
|
|
}
|
|
|
|
QImage cImage::mat2QImage(cv::Mat img)
|
|
{
|
|
QImage qImg;
|
|
|
|
// since Mat header is copied, a new buffer should be allocated (check this!)
|
|
if(img.depth() == CV_32F)
|
|
img.convertTo(img, CV_8U, 255);
|
|
|
|
if(img.type() == CV_8UC1)
|
|
qImg = QImage(img.data, static_cast<int>(img.cols), static_cast<int>(img.rows), static_cast<int>(img.step), QImage::Format_Indexed8); // opencv uses size_t for scaling in x64 applications
|
|
if(img.type() == CV_8UC3)
|
|
qImg = QImage(img.data, static_cast<int>(img.cols), static_cast<int>(img.rows), static_cast<int>(img.step), QImage::Format_RGB888);
|
|
if(img.type() == CV_8UC4)
|
|
qImg = QImage(img.data, static_cast<int>(img.cols), static_cast<int>(img.rows), static_cast<int>(img.step), QImage::Format_ARGB32);
|
|
|
|
qImg = qImg.copy();
|
|
|
|
return qImg;
|
|
}
|
|
|
|
bool cImage::loadRAW(const QString &fileName)
|
|
{
|
|
QSharedPointer<QByteArray> ba;
|
|
LibRaw iProcessor;
|
|
|
|
if(!openBuffer(fileName, ba, iProcessor))
|
|
{
|
|
qDebug() << "could not open buffer";
|
|
return(false);
|
|
}
|
|
|
|
detectSpecialCamera(iProcessor);
|
|
|
|
int error = iProcessor.unpack();
|
|
|
|
if(strcmp(iProcessor.version(), "0.13.5") != 0) // fixes a bug specific to libraw 13 - version call is UNTESTED
|
|
iProcessor.raw2image();
|
|
|
|
if(error != LIBRAW_SUCCESS)
|
|
return(false);
|
|
|
|
cv::Mat rawMat;
|
|
|
|
if(iProcessor.imgdata.idata.filters)
|
|
rawMat = demosaic(iProcessor);
|
|
else
|
|
rawMat = prepareImg(iProcessor);
|
|
|
|
// color correction + white balance
|
|
if(m_isChromatic)
|
|
whiteBalance(iProcessor, rawMat);
|
|
|
|
gammaCorrection(iProcessor, rawMat);
|
|
|
|
// reduce color noise
|
|
// if (DkSettingsManager::param().resources().filterRawImages && mIsChromatic)
|
|
// reduceColorNoise(iProcessor, rawMat);
|
|
|
|
*this = raw2Img(iProcessor, rawMat);
|
|
|
|
iProcessor.recycle();
|
|
rawMat.release();
|
|
|
|
m_raw = true;
|
|
|
|
return(true);
|
|
}
|
|
|
|
bool cImage::isRaw()
|
|
{
|
|
return(m_raw);
|
|
}
|