1570 lines
54 KiB
C++

/*
* This file is part of RawTherapee.
*
* Copyright (c) 2004-2010 Gabor Horvath <hgabor@rawtherapee.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef _IIMAGE_
#define _IIMAGE_
#include <glibmm.h>
#include <vector>
#include "rt_math.h"
#include "alignedbuffer.h"
#include "imagedimensions.h"
#include "LUT.h"
#include "coord2d.h"
#include "procparams.h"
#include "color.h"
#define TR_NONE 0
#define TR_R90 1
#define TR_R180 2
#define TR_R270 3
#define TR_VFLIP 4
#define TR_HFLIP 8
#define TR_ROT 3
#define CHECK_BOUNDS 0
namespace rtengine {
extern const char sImage8[];
extern const char sImage16[];
extern const char sImagefloat[];
int getCoarseBitMask( const procparams::CoarseTransformParams &coarse);
class ProgressListener;
class Color;
enum TypeInterpolation { TI_Nearest, TI_Bilinear };
// --------------------------------------------------------------------
// Generic classes
// --------------------------------------------------------------------
class ImageDatas : virtual public ImageDimensions {
public:
template <class S, class D >
void convertTo (S srcValue, D &dstValue) {
dstValue = static_cast<D>(srcValue);
}
// parameters that will never be used, replaced by the subclasses r, g and b parameters!
// they are still necessary to implement operator() in this parent class
virtual ~ImageDatas() {}
virtual void allocate (int W, int H) {}
virtual void rotate (int deg) {}
// free the memory allocated for the image data without deleting the object.
virtual void flushData () { allocate(0,0); }
virtual void hflip () {}
virtual void vflip () {}
// Read the raw dump of the data
void readData (FILE *fh) {}
// Write a raw dump of the data
void writeData (FILE *fh) {}
virtual void normalizeInt (int srcMinVal, int srcMaxVal) {};
virtual void normalizeFloat (float srcMinVal, float srcMaxVal) {};
virtual void computeHistogramAutoWB (double &avg_r, double &avg_g, double &avg_b, int &n, LUTu &histogram, int compression) {}
virtual void getSpotWBData (double &reds, double &greens, double &blues, int &rn, int &gn, int &bn,
std::vector<Coord2D> &red, std::vector<Coord2D> &green, std::vector<Coord2D> &blue,
int tran) {}
virtual void getAutoWBMultipliers (double &rm, double &gm, double &bm) { rm=gm=bm=1.0; }
virtual const char* getType () const { return "unknown"; }
};
template <>
inline void ImageDatas::convertTo<unsigned short, unsigned char> (const unsigned short srcValue, unsigned char &dstValue) {
dstValue = (unsigned char)(srcValue >> 8);
}
template <>
inline void ImageDatas::convertTo<unsigned char, int> (const unsigned char srcValue, int &dstValue) {
dstValue = (int)(srcValue) << 8;
}
template <>
inline void ImageDatas::convertTo<unsigned char, unsigned short> (const unsigned char srcValue, unsigned short &dstValue) {
dstValue = (unsigned short)(srcValue) << 8;
}
template <>
inline void ImageDatas::convertTo<float, unsigned char> (const float srcValue, unsigned char &dstValue) {
dstValue = (unsigned char)( (unsigned short)(srcValue) >> 8 );
}
template <>
inline void ImageDatas::convertTo<unsigned char, float> (const unsigned char srcValue, float &dstValue) {
dstValue = float( (unsigned short)(srcValue) << 8 );
}
// --------------------------------------------------------------------
// Planar order classes
// --------------------------------------------------------------------
template <class T>
class PlanarPtr {
protected:
AlignedBuffer<T*> ab;
public:
#if CHECK_BOUNDS
int width_, height_;
#endif
T** ptrs;
#if CHECK_BOUNDS
PlanarPtr() : width_(0), height_(0), ptrs (NULL) {}
#else
PlanarPtr() : ptrs (NULL){}
#endif
bool resize(int newSize) {
if (ab.resize(newSize)) {
ptrs=ab.data;
return true;
}
else {
ptrs=NULL;
return false;
}
}
void swap (PlanarPtr<T> &other) {
ab.swap(other.ab);
T** tmpsPtrs = other.ptrs;
other.ptrs = ptrs;
ptrs = tmpsPtrs;
#if CHECK_BOUNDS
int tmp = other.width_;
other.width_ = width_;
width_ = tmp;
tmp = other.height_;
other.height_ = height_;
height_ = tmp;
#endif
}
T*& operator() (unsigned row) {
#if CHECK_BOUNDS
assert (row < height_);
#endif
return ptrs[row];
}
// Will send back the start of a row, starting with a red, green or blue value
T* operator() (unsigned row) const {
#if CHECK_BOUNDS
assert (row < height_);
#endif
return ptrs[row];
}
// Will send back a value at a given row, col position
T& operator() (unsigned row, unsigned col) {
#if CHECK_BOUNDS
assert (row < height_ && col < width_);
#endif
return ptrs[row][col];
}
const T operator() (unsigned row, unsigned col) const {
#if CHECK_BOUNDS
assert (row < height_ && col < width_);
#endif
return ptrs[row][col];
}
};
template <class T>
class PlanarWhateverData : virtual public ImageDatas {
private:
AlignedBuffer<T> abData;
int rowstride; // Plan size, in bytes (all padding bytes included)
public:
T* data;
PlanarPtr<T> v; // v stands for "value", whatever it represent
PlanarWhateverData() : rowstride(0), data (NULL) {}
PlanarWhateverData(int w, int h) : rowstride(0), data (NULL) {
allocate(w, h);
}
// Send back the row stride. WARNING: unit = byte, not element!
int getRowStride () { return rowstride; }
void swap(PlanarWhateverData<T> &other) {
abData.swap(other.abData);
v.swap(other.v);
T* tmpData = other.data;
other.data = data;
data = tmpData;
int tmpWidth = other.width;
other.width = width;
width = tmpWidth;
int tmpHeight = other.height;
other.height = height;
height = tmpHeight;
#if CHECK_BOUNDS
v.width_ = width;
v.height_ = height;
#endif
}
// use as pointer to data
//operator void*() { return data; };
/* If any of the required allocation fails, "width" and "height" are set to -1, and all remaining buffer are freed
* Can be safely used to reallocate an existing image */
void allocate (int W, int H) {
if (W==width && H==height)
return;
width=W;
height=H;
#if CHECK_BOUNDS
v.width_ = width;
v.height_ = height;
#endif
if (sizeof(T) > 1) {
// 128 bits memory alignment for >8bits data
rowstride = ( width*sizeof(T)+15 )/16*16;
}
else {
// No memory alignment for 8bits data
rowstride = width*sizeof(T);
}
// find the padding length to ensure a 128 bits alignment for each row
size_t size = rowstride * height;
if (!width) {
size = 0;
rowstride = 0;
}
if (size && abData.resize(size, 1)
&& v.resize(height) )
{
data = abData.data;
}
else {
// asking for a new size of 0 is safe and will free memory, if any!
abData.resize(0);
data = NULL;
v.resize(0);
width = height = -1;
#if CHECK_BOUNDS
v.width_ = v.height_ = -1;
#endif
return;
}
char *start = (char*)(data);
for (int i=0; i<height; ++i) {
int k = i*rowstride;
v(i) = (T*)(start + k);
}
}
/** Copy the data to another PlanarWhateverData */
void copyData(PlanarWhateverData<T> *dest) {
assert (dest!=NULL);
// Make sure that the size is the same, reallocate if necessary
dest->allocate(width, height);
if (dest->width == -1) {
return;
}
for (int i=0; i<height; i++) {
memcpy (dest->v(i), v(i), width*sizeof(T));
}
}
void rotate (int deg) {
if (deg==90) {
PlanarWhateverData<T> rotatedImg(height, width); // New image, rotated
for (int ny=0; ny<rotatedImg.height; ny++) {
int ox = ny;
int oy = height-1;
for (int nx=0; nx<rotatedImg.width; nx++) {
rotatedImg.v(ny,nx) = v(oy,ox);
--oy;
}
}
swap(rotatedImg);
}
else if (deg==270) {
PlanarWhateverData<T> rotatedImg(height, width); // New image, rotated
for (int nx=0; nx<rotatedImg.width; nx++) {
int oy = nx;
int ox = width-1;
for (int ny=0; ny<rotatedImg.height; ny++) {
rotatedImg.v(ny,nx) = v(oy,ox);
--ox;
}
}
swap(rotatedImg);
}
else if (deg==180) {
int height2 = height/2 + (height & 1);
#ifdef _OPENMP
// difficult to find a cutoff value where parallelization is counter productive because of processor's data cache collision...
bool bigImage = width>32 && height>50;
#pragma omp parallel for schedule(static) if(bigImage)
#endif
for (int i=0; i<height2; i++) {
for (int j=0; j<width; j++) {
T tmp;
int x = width-1-j;
int y = height-1-i;
tmp = v(i,j);
v(i,j) = v(y,x);
v(y,x) = tmp;
}
}
}
}
template <class IC>
void resizeImgTo (int nw, int nh, TypeInterpolation interp, PlanarWhateverData<IC> *imgPtr) {
//printf("resizeImgTo: resizing %s image data (%d x %d) to %s (%d x %d)\n", getType(), width, height, imgPtr->getType(), imgPtr->width, imgPtr->height);
if (width==nw && height==nh) {
// special case where no resizing is necessary, just type conversion....
for (int i=0; i<height; i++) {
for (int j=0; j<width; j++) {
convertTo(v(i,j), imgPtr->v(i,j));
}
}
}
else if (interp == TI_Nearest) {
for (int i=0; i<nh; i++) {
int ri = i*height/nh;
for (int j=0; j<nw; j++) {
int ci = j*width/nw;
convertTo(v(ri,ci), imgPtr->v(i,j));
}
}
}
else if (interp == TI_Bilinear) {
for (int i=0; i<nh; i++) {
int sy = i*height/nh;
if (sy>=height) sy = height-1;
float dy = float(i)*float(height)/float(nh) - float(sy);
int ny = sy+1;
if (ny>=height) ny = sy;
for (int j=0; j<nw; j++) {
int sx = j*width/nw;
if (sx>=width) sx = width;
float dx = float(j)*float(width)/float(nw) - float(sx);
int nx = sx+1;
if (nx>=width) nx = sx;
convertTo(v(sy,sx)*(1.f-dx)*(1.f-dy) + v(sy,nx)*dx*(1.f-dy) + v(ny,sx)*(1.f-dx)*dy + v(ny,nx)*dx*dy, imgPtr->v(i,j));
}
}
}
else {
// This case should never occur!
for (int i=0; i<nh; i++) {
for (int j=0; j<nw; j++) {
v(i,j) = 0;
}
}
}
}
void hflip () {
int width2 = width/2;
#ifdef _OPENMP
// difficult to find a cutoff value where parallelization is counter productive because of processor's data cache collision...
bool bigImage = width>32 && height>50;
#pragma omp parallel for schedule(static) if(bigImage)
#endif
for (int i=0; i<height; i++)
for (int j=0; j<width2; j++) {
float temp;
int x = width-1-j;
temp = v(i,j);
v(i,j) = v(i,x);
v(i,x) = temp;
}
}
void vflip () {
int height2 = height/2;
#ifdef _OPENMP
// difficult to find a cutoff value where parallelization is counter productive because of processor's data cache collision...
bool bigImage = width>32 && height>50;
#pragma omp parallel for schedule(static) if(bigImage)
#endif
for (int i=0; i<height2; i++)
for (int j=0; j<width; j++) {
T temp;
int y = height-1-i;
temp = v(i,j);
v(i,j) = v(y,j);
v(y,j) = temp;
}
}
void calcHist(unsigned int *hist16) {
for (int row=0; row<height; row++)
for (int col=0; col<width; col++) {
unsigned short idx;
convertTo(v(row,col), idx);
hist16[idx]++;
}
}
void transformPixel (int x, int y, int tran, int& tx, int& ty) {
if (!tran) {
tx = x;
ty = y;
return;
}
int W = width;
int H = 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;
}
}
void getPipetteData (T &value, int posX, int posY, int squareSize, int tran)
{
int x; int y;
float accumulator = 0.f; // using float to avoid range overflow; -> please creates specialization if necessary
unsigned long int n = 0;
int halfSquare = squareSize/2;
transformPixel (posX, posY, tran, x, y);
for (int iy=y-halfSquare; iy<y-halfSquare+squareSize; ++iy) {
for (int ix=x-halfSquare; ix<x-halfSquare+squareSize; ++ix) {
if (ix>=0 && iy>=0 && ix<width && iy<height) {
accumulator += float(this->v(iy, ix));
++n;
}
}
}
value = n ? T(accumulator/float(n)) : T(0);
}
void readData (FILE *f) {
for (int i=0; i<height; i++)
fread (v(i), sizeof(T), width, f);
}
void writeData (FILE *f) {
for (int i=0; i<height; i++)
fwrite (v(i), sizeof(T), width, f);
}
};
template <class T>
class PlanarRGBData : virtual public ImageDatas {
private:
AlignedBuffer<T> abData;
int rowstride; // Plan size, in bytes (all padding bytes included)
int planestride; // Row length, in bytes (padding bytes included)
protected:
T* data;
public:
PlanarPtr<T> r;
PlanarPtr<T> g;
PlanarPtr<T> b;
PlanarRGBData() : rowstride(0), planestride(0), data (NULL) {}
PlanarRGBData(int w, int h) : rowstride(0), planestride(0), data (NULL) {
allocate(w, h);
}
// Send back the row stride. WARNING: unit = byte, not element!
int getRowStride () { return rowstride; }
// Send back the plane stride. WARNING: unit = byte, not element!
int getPlaneStride () { return planestride; }
void swap(PlanarRGBData<T> &other) {
abData.swap(other.abData);
r.swap(other.r);
g.swap(other.g);
b.swap(other.b);
T* tmpData = other.data;
other.data = data;
data = tmpData;
int tmpWidth = other.width;
other.width = width;
width = tmpWidth;
int tmpHeight = other.height;
other.height = height;
height = tmpHeight;
#if CHECK_BOUNDS
r.width_ = width; r.height_ = height;
g.width_ = width; g.height_ = height;
b.width_ = width; b.height_ = height;
#endif
}
// use as pointer to data
//operator void*() { return data; };
/* If any of the required allocation fails, "width" and "height" are set to -1, and all remaining buffer are freed
* Can be safely used to reallocate an existing image */
void allocate (int W, int H) {
if (W==width && H==height)
return;
width=W;
height=H;
#if CHECK_BOUNDS
r.width_ = width; r.height_ = height;
g.width_ = width; g.height_ = height;
b.width_ = width; b.height_ = height;
#endif
if (sizeof(T) > 1) {
// 128 bits memory alignment for >8bits data
rowstride = ( width*sizeof(T)+15 )/16*16;
planestride = rowstride * height;
}
else {
// No memory alignment for 8bits data
rowstride = width*sizeof(T);
planestride = rowstride * height;
}
// find the padding length to ensure a 128 bits alignment for each row
size_t size = (size_t)rowstride*3*(size_t)height;
if (!width) {
size = 0;
rowstride = 0;
}
if (size && abData.resize(size, 1)
&& r.resize(height)
&& g.resize(height)
&& b.resize(height) )
{
data = abData.data;
}
else {
// asking for a new size of 0 is safe and will free memory, if any!
abData.resize(0);
data = NULL;
r.resize(0);
g.resize(0);
b.resize(0);
width = height = -1;
#if CHECK_BOUNDS
r.width_ = r.height_ = -1;
g.width_ = g.height_ = -1;
b.width_ = b.height_ = -1;
#endif
return;
}
char *redstart = (char*)(data);
char *greenstart = (char*)(data) + planestride;
char *bluestart = (char*)(data) + 2*planestride;
for (int i=0; i<height; ++i) {
int k = i*rowstride;
r(i) = (T*)(redstart + k);
g(i) = (T*)(greenstart + k);
b(i) = (T*)(bluestart + k);
}
}
/** Copy the data to another PlanarRGBData */
void copyData(PlanarRGBData<T> *dest) {
assert (dest!=NULL);
// Make sure that the size is the same, reallocate if necessary
dest->allocate(width, height);
if (dest->width == -1) {
printf("ERROR: PlanarRGBData::copyData >>> allocation failed!\n");
return;
}
for (int i=0; i<height; i++) {
memcpy (dest->r(i), r(i), width*sizeof(T));
memcpy (dest->g(i), g(i), width*sizeof(T));
memcpy (dest->b(i), b(i), width*sizeof(T));
}
}
void rotate (int deg) {
if (deg==90) {
PlanarRGBData<T> rotatedImg(height, width); // New image, rotated
for (int ny=0; ny<rotatedImg.height; ny++) {
int ox = ny;
int oy = height-1;
for (int nx=0; nx<rotatedImg.width; nx++) {
rotatedImg.r(ny,nx) = r(oy,ox);
rotatedImg.g(ny,nx) = g(oy,ox);
rotatedImg.b(ny,nx) = b(oy,ox);
--oy;
}
}
swap(rotatedImg);
}
else if (deg==270) {
PlanarRGBData<T> rotatedImg(height, width); // New image, rotated
for (int nx=0; nx<rotatedImg.width; nx++) {
int oy = nx;
int ox = width-1;
for (int ny=0; ny<rotatedImg.height; ny++) {
rotatedImg.r(ny,nx) = r(oy,ox);
rotatedImg.g(ny,nx) = g(oy,ox);
rotatedImg.b(ny,nx) = b(oy,ox);
--ox;
}
}
swap(rotatedImg);
}
else if (deg==180) {
int height2 = height/2 + (height & 1);
#ifdef _OPENMP
// difficult to find a cutoff value where parallelization is counter productive because of processor's data cache collision...
bool bigImage = width>32 && height>50;
#pragma omp parallel for schedule(static) if(bigImage)
#endif
for (int i=0; i<height2; i++) {
for (int j=0; j<width; j++) {
T tmp;
int x = width-1-j;
int y = height-1-i;
tmp = r(i,j);
r(i,j) = r(y,x);
r(y,x) = tmp;
tmp = g(i,j);
g(i,j) = g(y,x);
g(y,x) = tmp;
tmp = b(i,j);
b(i,j) = b(y,x);
b(y,x) = tmp;
}
}
}
}
template <class IC>
void resizeImgTo (int nw, int nh, TypeInterpolation interp, IC *imgPtr) {
//printf("resizeImgTo: resizing %s image data (%d x %d) to %s (%d x %d)\n", getType(), width, height, imgPtr->getType(), imgPtr->width, imgPtr->height);
if (width==nw && height==nh) {
// special case where no resizing is necessary, just type conversion....
for (int i=0; i<height; i++) {
for (int j=0; j<width; j++) {
convertTo(r(i,j), imgPtr->r(i,j));
convertTo(g(i,j), imgPtr->g(i,j));
convertTo(b(i,j), imgPtr->b(i,j));
}
}
}
else if (interp == TI_Nearest) {
for (int i=0; i<nh; i++) {
int ri = i*height/nh;
for (int j=0; j<nw; j++) {
int ci = j*width/nw;
convertTo(r(ri,ci), imgPtr->r(i,j));
convertTo(g(ri,ci), imgPtr->g(i,j));
convertTo(b(ri,ci), imgPtr->b(i,j));
}
}
}
else if (interp == TI_Bilinear) {
for (int i=0; i<nh; i++) {
int sy = i*height/nh;
if (sy>=height) sy = height-1;
float dy = float(i)*float(height)/float(nh) - float(sy);
int ny = sy+1;
if (ny>=height) ny = sy;
for (int j=0; j<nw; j++) {
int sx = j*width/nw;
if (sx>=width) sx = width;
float dx = float(j)*float(width)/float(nw) - float(sx);
int nx = sx+1;
if (nx>=width) nx = sx;
convertTo(r(sy,sx)*(1.f-dx)*(1.f-dy) + r(sy,nx)*dx*(1.f-dy) + r(ny,sx)*(1.f-dx)*dy + r(ny,nx)*dx*dy, imgPtr->r(i,j));
convertTo(g(sy,sx)*(1.f-dx)*(1.f-dy) + g(sy,nx)*dx*(1.f-dy) + g(ny,sx)*(1.f-dx)*dy + g(ny,nx)*dx*dy, imgPtr->g(i,j));
convertTo(b(sy,sx)*(1.f-dx)*(1.f-dy) + b(sy,nx)*dx*(1.f-dy) + b(ny,sx)*(1.f-dx)*dy + b(ny,nx)*dx*dy, imgPtr->b(i,j));
}
}
}
else {
// This case should never occur!
for (int i=0; i<nh; i++) {
for (int j=0; j<nw; j++) {
r(i,j) = 0;
g(i,j) = 0;
b(i,j) = 0;
}
}
}
}
void hflip () {
int width2 = width/2;
#ifdef _OPENMP
// difficult to find a cutoff value where parallelization is counter productive because of processor's data cache collision...
bool bigImage = width>32 && height>50;
#pragma omp parallel for schedule(static) if(bigImage)
#endif
for (int i=0; i<height; i++)
for (int j=0; j<width2; j++) {
float temp;
int x = width-1-j;
temp = r(i,j);
r(i,j) = r(i,x);
r(i,x) = temp;
temp = g(i,j);
g(i,j) = g(i,x);
g(i,x) = temp;
temp = b(i,j);
b(i,j) = b(i,x);
b(i,x) = temp;
}
}
void vflip () {
int height2 = height/2;
#ifdef _OPENMP
// difficult to find a cutoff value where parallelization is counter productive because of processor's data cache collision...
bool bigImage = width>32 && height>50;
#pragma omp parallel for schedule(static) if(bigImage)
#endif
for (int i=0; i<height2; i++)
for (int j=0; j<width; j++) {
T tempR, tempG, tempB;
int y = height-1-i;
tempR = r(i,j);
r(i,j) = r(y,j);
r(y,j) = tempR;
tempG = g(i,j);
g(i,j) = g(y,j);
g(y,j) = tempG;
tempB = b(i,j);
b(i,j) = b(y,j);
b(y,j) = tempB;
}
}
void calcGrayscaleHist(unsigned int *hist16) {
for (int row=0; row<height; row++)
for (int col=0; col<width; col++) {
unsigned short rIdx, gIdx, bIdx;
convertTo(r(row,col), rIdx);
convertTo(g(row,col), gIdx);
convertTo(b(row,col), bIdx);
hist16[rIdx]++;
hist16[gIdx]+=2; // Bayer 2x green correction
hist16[bIdx]++;
}
}
void computeAutoHistogram (LUTu & histogram, int& histcompr) {
histcompr = 3;
histogram(65536>>histcompr);
histogram.clear();
for (int i=0; i<height; i++)
for (int j=0; j<width; j++) {
float r_, g_, b_;
convertTo<T, float>(r(i,j), r_);
convertTo<T, float>(g(i,j), g_);
convertTo<T, float>(b(i,j), b_);
histogram[(int)Color::igamma_srgb (r_)>>histcompr]++;
histogram[(int)Color::igamma_srgb (g_)>>histcompr]++;
histogram[(int)Color::igamma_srgb (b_)>>histcompr]++;
}
}
void computeHistogramAutoWB (double &avg_r, double &avg_g, double &avg_b, int &n, LUTu &histogram, const int compression) {
histogram.clear();
avg_r = avg_g = avg_b = 0.;
n=0;
for (unsigned int i=0; i<(unsigned int)(height); i++)
for (unsigned int j=0; j<(unsigned int)(width); j++) {
float r_, g_, b_;
convertTo<T, float>(r(i,j), r_);
convertTo<T, float>(g(i,j), g_);
convertTo<T, float>(b(i,j), b_);
int rtemp = Color::igamma_srgb (r_);
int gtemp = Color::igamma_srgb (g_);
int btemp = Color::igamma_srgb (b_);
histogram[rtemp>>compression]++;
histogram[gtemp>>compression]+=2;
histogram[btemp>>compression]++;
// autowb computation
if (r_>64000.f || g_>64000.f || b_>64000.f) continue;
avg_r += double(r_);
avg_g += double(g_);
avg_b += double(b_);
n++;
}
}
void getAutoWBMultipliers (double &rm, double &gm, double &bm) {
double avg_r = 0.;
double avg_g = 0.;
double avg_b = 0.;
int n = 0;
//int p = 6;
for (unsigned int i=0; i<(unsigned int)(height); i++)
for (unsigned int j=0; j<(unsigned int)(width); j++) {
float r_, g_, b_;
convertTo<T, float>(r(i,j), r_);
convertTo<T, float>(g(i,j), g_);
convertTo<T, float>(b(i,j), b_);
if (r_>64000.f || g_>64000.f || b_>64000.f) continue;
avg_r += double(r_);
avg_g += double(g_);
avg_b += double(b_);
/*avg_r += intpow( (double)r(i, j), p);
avg_g += intpow( (double)g(i, j), p);
avg_b += intpow( (double)b(i, j), p);*/
n++;
}
rm = avg_r/double(n);
gm = avg_g/double(n);
bm = avg_b/double(n);
}
void transformPixel (int x, int y, int tran, int& tx, int& ty) {
if (!tran) {
tx = x;
ty = y;
return;
}
int W = width;
int H = 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;
}
}
virtual void getSpotWBData (double &reds, double &greens, double &blues, int &rn, int &gn, int &bn,
std::vector<Coord2D> &red, std::vector<Coord2D> &green, std::vector<Coord2D> &blue,
int tran)
{
int x; int y;
reds = 0, greens = 0, blues = 0;
rn = 0, gn = 0, bn = 0;
for (size_t i=0; i<red.size(); i++) {
transformPixel (red[i].x, red[i].y, tran, x, y);
if (x>=0 && y>=0 && x<width && y<height) {
float v;
convertTo<T, float>(this->r(y, x), v);
reds += double(v);
rn++;
}
transformPixel (green[i].x, green[i].y, tran, x, y);
if (x>=0 && y>=0 && x<width && y<height) {
float v;
convertTo<T, float>(this->g(y, x), v);
greens += double(v);
gn++;
}
transformPixel (blue[i].x, blue[i].y, tran, x, y);
if (x>=0 && y>=0 && x<width && y<height) {
float v;
convertTo<T, float>(this->b(y, x), v);
blues += double(v);
bn++;
}
}
}
void getPipetteData (T &valueR, T &valueG, T &valueB, int posX, int posY, int squareSize, int tran)
{
int x; int y;
float accumulatorR = 0.f; // using float to avoid range overflow; -> please creates specialization if necessary
float accumulatorG = 0.f; // "
float accumulatorB = 0.f; // "
unsigned long int n = 0;
int halfSquare = squareSize/2;
transformPixel (posX, posY, tran, x, y);
for (int iy=y-halfSquare; iy<y-halfSquare+squareSize; ++iy) {
for (int ix=x-halfSquare; ix<x-halfSquare+squareSize; ++ix) {
if (ix>=0 && iy>=0 && ix<width && iy<height) {
accumulatorR += float(this->r(iy, ix));
accumulatorG += float(this->g(iy, ix));
accumulatorB += float(this->b(iy, ix));
++n;
}
}
}
valueR = n ? T(accumulatorR/float(n)) : T(0);
valueG = n ? T(accumulatorG/float(n)) : T(0);
valueB = n ? T(accumulatorB/float(n)) : T(0);
}
void readData (FILE *f) {
for (int i=0; i<height; i++)
fread (r(i), sizeof(T), width, f);
for (int i=0; i<height; i++)
fread (g(i), sizeof(T), width, f);
for (int i=0; i<height; i++)
fread (b(i), sizeof(T), width, f);
}
void writeData (FILE *f) {
for (int i=0; i<height; i++)
fwrite (r(i), sizeof(T), width, f);
for (int i=0; i<height; i++)
fwrite (g(i), sizeof(T), width, f);
for (int i=0; i<height; i++)
fwrite (b(i), sizeof(T), width, f);
}
};
// --------------------------------------------------------------------
// Chunky order classes
// --------------------------------------------------------------------
template <class T>
class ChunkyPtr {
private:
T* ptr;
int width;
public:
#if CHECK_BOUNDS
int width_, height_;
#endif
#if CHECK_BOUNDS
ChunkyPtr() : ptr (NULL), width(-1), width_(0), height_(0) {}
#else
ChunkyPtr() : ptr (NULL), width(-1) {}
#endif
void init(T* base, int w=-1) { ptr = base; width=w; }
void swap (ChunkyPtr<T> &other) {
T* tmpsPtr = other.ptr;
other.ptr = ptr;
ptr = tmpsPtr;
int tmpWidth = other.width;
other.width = width;
width = tmpWidth;
#if CHECK_BOUNDS
int tmp = other.width_;
other.width_ = width_;
width_ = tmp;
tmp = other.height_;
other.height_ = height_;
height_ = tmp;
#endif
}
// Will send back the start of a row, starting with a red, green or blue value
T* operator() (unsigned row) const {
#if CHECK_BOUNDS
assert (row < height_);
#endif
return &ptr[3*(row*width)];
}
// Will send back a value at a given row, col position
T& operator() (unsigned row, unsigned col) {
#if CHECK_BOUNDS
assert (row < height_ && col < width_);
#endif
return ptr[3*(row*width+col)];
}
const T operator() (unsigned row, unsigned col) const {
#if CHECK_BOUNDS
assert (row < height_ && col < width_);
#endif
return ptr[3*(row*width+col)];
}
};
template <class T>
class ChunkyRGBData : virtual public ImageDatas {
private:
AlignedBuffer<T> abData;
public:
T* data;
ChunkyPtr<T> r;
ChunkyPtr<T> g;
ChunkyPtr<T> b;
ChunkyRGBData() : data (NULL) {}
ChunkyRGBData(int w, int h) : data (NULL) {
allocate(w, h);
}
/** Returns the pixel data, in r/g/b order from top left to bottom right continuously.
* @return a pointer to the pixel data */
const T* getData () { return data; }
void swap(ChunkyRGBData<T> &other) {
abData.swap(other.abData);
r.swap(other.r);
g.swap(other.g);
b.swap(other.b);
T* tmpData = other.data;
other.data = data;
data = tmpData;
int tmpWidth = other.width;
other.width = width;
width = tmpWidth;
int tmpHeight = other.height;
other.height = height;
height = tmpHeight;
#if CHECK_BOUNDS
r.width_ = width; r.height_ = height;
g.width_ = width; g.height_ = height;
b.width_ = width; b.height_ = height;
#endif
}
/*
* If any of the required allocation fails, "width" and "height" are set to -1, and all remaining buffer are freed
* Can be safely used to reallocate an existing image or to free up it's memory with "allocate (0,0);"
*/
void allocate (int W, int H) {
if (W==width && H==height)
return;
width=W;
height=H;
#if CHECK_BOUNDS
r.width_ = width; r.height_ = height;
g.width_ = width; g.height_ = height;
b.width_ = width; b.height_ = height;
#endif
abData.resize(width*height*3);
if (!abData.isEmpty()) {
data = abData.data;
r.init(data, width);
g.init(data+1, width);
b.init(data+2, width);
}
else {
data = NULL;
r.init(NULL);
g.init(NULL);
b.init(NULL);
width = height = -1;
#if CHECK_BOUNDS
r.width_ = r.height_ = -1;
g.width_ = g.height_ = -1;
b.width_ = b.height_ = -1;
#endif
}
}
/** Copy the data to another ChunkyRGBData */
void copyData(ChunkyRGBData<T> *dest) {
assert (dest!=NULL);
// Make sure that the size is the same, reallocate if necessary
dest->allocate(width, height);
if (dest->width == -1) {
printf("ERROR: ChunkyRGBData::copyData >>> allocation failed!\n");
return;
}
memcpy (dest->data, data, 3*width*height*sizeof(T));
}
void rotate (int deg) {
if (deg==90) {
ChunkyRGBData<T> rotatedImg(height, width); // New image, rotated
for (int ny=0; ny<rotatedImg.height; ny++) {
int ox = ny;
int oy = height-1;
for (int nx=0; nx<rotatedImg.width; nx++) {
rotatedImg.r(ny,nx) = r(oy,ox);
rotatedImg.g(ny,nx) = g(oy,ox);
rotatedImg.b(ny,nx) = b(oy,ox);
--oy;
}
}
swap(rotatedImg);
}
else if (deg==270) {
ChunkyRGBData<T> rotatedImg(height, width); // New image, rotated
for (int nx=0; nx<rotatedImg.width; nx++) {
int oy = nx;
int ox = width-1;
for (int ny=0; ny<rotatedImg.height; ny++) {
rotatedImg.r(ny,nx) = r(oy,ox);
rotatedImg.g(ny,nx) = g(oy,ox);
rotatedImg.b(ny,nx) = b(oy,ox);
--ox;
}
}
swap(rotatedImg);
}
else if (deg==180) {
int height2 = height/2 + (height & 1);
// Maybe not sufficiently optimized, but will do what it has to do
for (int i=0; i<height2; i++) {
for (int j=0; j<width; j++) {
T tmp;
int x = width-1-j;
int y = height-1-i;
tmp = r(i,j);
r(i,j) = r(y,x);
r(y,x) = tmp;
tmp = g(i,j);
g(i,j) = g(y,x);
g(y,x) = tmp;
tmp = b(i,j);
b(i,j) = b(y,x);
b(y,x) = tmp;
}
}
}
}
template <class IC>
void resizeImgTo (int nw, int nh, TypeInterpolation interp, IC *imgPtr) {
//printf("resizeImgTo: resizing %s image data (%d x %d) to %s (%d x %d)\n", getType(), width, height, imgPtr->getType(), imgPtr->width, imgPtr->height);
if (width==nw && height==nh) {
// special case where no resizing is necessary, just type conversion....
for (int i=0; i<height; i++) {
for (int j=0; j<width; j++) {
convertTo(r(i,j), imgPtr->r(i,j));
convertTo(g(i,j), imgPtr->g(i,j));
convertTo(b(i,j), imgPtr->b(i,j));
}
}
}
else if (interp == TI_Nearest) {
for (int i=0; i<nh; i++) {
int ri = i*height/nh;
for (int j=0; j<nw; j++) {
int ci = j*width/nw;
convertTo(r(ri,ci), imgPtr->r(i,j));
convertTo(g(ri,ci), imgPtr->g(i,j));
convertTo(b(ri,ci), imgPtr->b(i,j));
}
}
}
else if (interp == TI_Bilinear) {
for (int i=0; i<nh; i++) {
int sy = i*height/nh;
if (sy>=height) sy = height-1;
float dy = float(i)*float(height)/float(nh) - float(sy);
int ny = sy+1;
if (ny>=height) ny = sy;
for (int j=0; j<nw; j++) {
int sx = j*width/nw;
if (sx>=width) sx = width;
float dx = float(j)*float(width)/float(nw) - float(sx);
int nx = sx+1;
if (nx>=width) nx = sx;
T valR = r(sy,sx)*(1.f-dx)*(1.f-dy) + r(sy,nx)*dx*(1.f-dy) + r(ny,sx)*(1.f-dx)*dy + r(ny,nx)*dx*dy;
T valG = g(sy,sx)*(1.f-dx)*(1.f-dy) + g(sy,nx)*dx*(1.f-dy) + g(ny,sx)*(1.f-dx)*dy + g(ny,nx)*dx*dy;
T valB = b(sy,sx)*(1.f-dx)*(1.f-dy) + b(sy,nx)*dx*(1.f-dy) + b(ny,sx)*(1.f-dx)*dy + b(ny,nx)*dx*dy;
convertTo(valR, imgPtr->r(i,j));
convertTo(valG, imgPtr->g(i,j));
convertTo(valB, imgPtr->b(i,j));
}
}
}
else {
// This case should never occur!
for (int i=0; i<nh; i++) {
for (int j=0; j<nw; j++) {
r(i,j) = 0;
g(i,j) = 0;
b(i,j) = 0;
}
}
}
}
void hflip () {
int width2 = width/2;
for (int i=0; i<height; i++) {
int offsetBegin = 0;
int offsetEnd = 3*(width-1);
for (int j=0; j<width2; j++) {
T temp;
temp = data[offsetBegin];
data[offsetBegin] = data[offsetEnd];
data[offsetEnd] = temp;
++offsetBegin;
++offsetEnd;
temp = data[offsetBegin];
data[offsetBegin] = data[offsetEnd];
data[offsetEnd] = temp;
++offsetBegin;
++offsetEnd;
temp = data[offsetBegin];
data[offsetBegin] = data[offsetEnd];
data[offsetEnd] = temp;
++offsetBegin;
offsetEnd -= 5;
}
}
}
void vflip () {
AlignedBuffer<T> lBuffer(3*width);
T* lineBuffer = lBuffer.data;
size_t size = 3*width*sizeof(T);
for (int i=0; i<height/2; i++) {
T *lineBegin1 = r(i);
T *lineBegin2 = r(height-1-i);
memcpy (lineBuffer, lineBegin1, size);
memcpy (lineBegin1, lineBegin2, size);
memcpy (lineBegin2, lineBuffer, size);
}
}
void calcGrayscaleHist(unsigned int *hist16) {
for (int row=0; row<height; row++)
for (int col=0; col<width; col++) {
unsigned short rIdx, gIdx, bIdx;
convertTo(r(row,col), rIdx);
convertTo(g(row,col), gIdx);
convertTo(b(row,col), bIdx);
hist16[rIdx]++;
hist16[gIdx]+=2; // Bayer 2x green correction
hist16[bIdx]++;
}
}
void computeAutoHistogram (LUTu & histogram, int& histcompr) {
histcompr = 3;
histogram(65536>>histcompr);
histogram.clear();
for (int i=0; i<height; i++)
for (int j=0; j<width; j++) {
float r_, g_, b_;
convertTo<T, float>(r(i,j), r_);
convertTo<T, float>(g(i,j), g_);
convertTo<T, float>(b(i,j), b_);
histogram[(int)Color::igamma_srgb (r_)>>histcompr]++;
histogram[(int)Color::igamma_srgb (g_)>>histcompr]++;
histogram[(int)Color::igamma_srgb (b_)>>histcompr]++;
}
}
void computeHistogramAutoWB (double &avg_r, double &avg_g, double &avg_b, int &n, LUTu &histogram, const int compression) {
histogram.clear();
avg_r = avg_g = avg_b = 0.;
n=0;
for (unsigned int i=0; i<(unsigned int)(height); i++)
for (unsigned int j=0; j<(unsigned int)(width); j++) {
float r_, g_, b_;
convertTo<T, float>(r(i,j), r_);
convertTo<T, float>(g(i,j), g_);
convertTo<T, float>(b(i,j), b_);
int rtemp = Color::igamma_srgb (r_);
int gtemp = Color::igamma_srgb (g_);
int btemp = Color::igamma_srgb (b_);
histogram[rtemp>>compression]++;
histogram[gtemp>>compression]+=2;
histogram[btemp>>compression]++;
// autowb computation
if (r_>64000.f || g_>64000.f || b_>64000.f) continue;
avg_r += double(r_);
avg_g += double(g_);
avg_b += double(b_);
n++;
}
}
void getAutoWBMultipliers (double &rm, double &gm, double &bm) {
double avg_r = 0.;
double avg_g = 0.;
double avg_b = 0.;
int n = 0;
//int p = 6;
for (unsigned int i=0; i<(unsigned int)(height); i++)
for (unsigned int j=0; j<(unsigned int)(width); j++) {
float r_, g_, b_;
convertTo<T, float>(r(i,j), r_);
convertTo<T, float>(g(i,j), g_);
convertTo<T, float>(b(i,j), b_);
if (r_>64000.f || g_>64000.f || b_>64000.f) continue;
avg_r += double(r_);
avg_g += double(g_);
avg_b += double(b_);
/*avg_r += intpow( (double)r(i, j), p);
avg_g += intpow( (double)g(i, j), p);
avg_b += intpow( (double)b(i, j), p);*/
n++;
}
rm = avg_r/double(n);
gm = avg_g/double(n);
bm = avg_b/double(n);
}
void transformPixel (int x, int y, int tran, int& tx, int& ty) {
if (!tran) {
tx = x;
ty = y;
return;
}
int W = width;
int H = 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;
}
}
virtual void getSpotWBData (double &reds, double &greens, double &blues, int &rn, int &gn, int &bn,
std::vector<Coord2D> &red, std::vector<Coord2D> &green, std::vector<Coord2D> &blue,
int tran)
{
int x; int y;
reds = 0, greens = 0, blues = 0;
rn = 0, gn = 0, bn = 0;
for (size_t i=0; i<red.size(); i++) {
transformPixel (red[i].x, red[i].y, tran, x, y);
if (x>=0 && y>=0 && x<width && y<height) {
float v;
convertTo<T, float>(this->r(y, x), v);
reds += double(v);
rn++;
}
transformPixel (green[i].x, green[i].y, tran, x, y);
if (x>=0 && y>=0 && x<width && y<height) {
float v;
convertTo<T, float>(this->g(y, x), v);
greens += double(v);
gn++;
}
transformPixel (blue[i].x, blue[i].y, tran, x, y);
if (x>=0 && y>=0 && x<width && y<height) {
float v;
convertTo<T, float>(this->b(y, x), v);
blues += double(v);
bn++;
}
}
}
void readData (FILE *f) {
for (int i=0; i<height; i++)
fread (r(i), sizeof(T), 3*width, f);
}
void writeData (FILE *f) {
for (int i=0; i<height; i++)
fwrite (r(i), sizeof(T), 3*width, f);
}
};
// --------------------------------------------------------------------
/** @brief This class represents an image (the result of the image processing) */
class IImage : virtual public ImageDimensions {
public:
virtual ~IImage() {}
/** @brief Returns a mutex that can is useful in many situations. No image operations shuold be performed without locking this mutex.
* @return The mutex */
virtual MyMutex& getMutex ()=0;
virtual cmsHPROFILE getProfile ()=0;
/** @brief Returns the bits per pixel of the image.
* @return The bits per pixel of the image */
virtual int getBitsPerPixel ()=0;
/** @brief Saves the image to file. It autodetects the format (jpg, tif, png are supported).
* @param fname is the name of the file
@return the error code, 0 if none */
virtual int saveToFile (Glib::ustring fname)=0;
/** @brief Saves the image to file in a png format.
* @param fname is the name of the file
* @param compression is the amount of compression (0-6), -1 corresponds to the default
* @param bps can be 8 or 16 depending on the bits per pixels the output file will have
@return the error code, 0 if none */
virtual int saveAsPNG (Glib::ustring fname, int compression = -1, int bps = -1)=0;
/** @brief Saves the image to file in a jpg format.
* @param fname is the name of the file
* @param quality is the quality of the jpeg (0...100), set it to -1 to use default
@return the error code, 0 if none */
virtual int saveAsJPEG (Glib::ustring fname, int quality = 100, int subSamp = 3 )=0;
/** @brief Saves the image to file in a tif format.
* @param fname is the name of the file
* @param bps can be 8 or 16 depending on the bits per pixels the output file will have
@return the error code, 0 if none */
virtual int saveAsTIFF (Glib::ustring fname, int bps = -1, bool uncompressed = false)=0;
/** @brief Sets the progress listener if you want to follow the progress of the image saving operations (optional).
* @param pl is the pointer to the class implementing the ProgressListener interface */
virtual void setSaveProgressListener (ProgressListener* pl)=0;
/** @brief Free the image */
virtual void free ()=0;
};
/** @brief This class represents an image having a float pixel planar representation.
The planes are stored as two dimensional arrays. All the rows have a 128 bits alignment. */
class IImagefloat : public IImage, public PlanarRGBData<float> {
public:
virtual ~IImagefloat() {}
};
/** @brief This class represents an image having a classical 8 bits/pixel representation */
class IImage8 : public IImage, public ChunkyRGBData<unsigned char> {
public:
virtual ~IImage8() {}
};
/** @brief This class represents an image having a 16 bits/pixel planar representation.
The planes are stored as two dimensional arrays. All the rows have a 128 bits alignment. */
class IImage16 : public IImage, public PlanarRGBData<unsigned short> {
public:
virtual ~IImage16() {}
};
}
#endif