1
//---------------------------------------------------------------------------------
2
// Simple framebuffer for display, uses CImg library by David Tschumperle
3
// Currently the whole image exists at once, but it should be modified to
4
// write just a single bucket, using some appropriate image format like exr.
5
// Similarly, drawing to the display should be one bucket at a time,
6
// currently redraws the whole image for every bucket, which is quite 'expensive'.
7
// But while there are functions to draw a subimage in another image in CImg,
8
// I just can't seem to find (or figure out) something similar for a subrect
9
// *display* update. So for now this is taken care of by the hider,
10
// which passes a flag to only draw every column/row.
11
//---------------------------------------------------------------------------------
13
#include "FrameBuffer.h"
16
#include "Transform.h"
23
#pragma warning(disable : 4996)
24
#pragma warning(disable : 4312)
25
#pragma warning(disable : 4311)
31
// though here only used for its multiplatform display features,
32
// the CImg library by David Tschumperle is an extremely nice
33
// general image processing library, all in one include file
34
#if defined (__GNUC__) && !defined(__SVR4)
35
// somewhat faster display update
47
// framebuffer display
48
static cimg_library::CImgDisplay* fbdisplay = NULL;
49
static cimg_library::CImg<unsigned char>* fbdisplay_img = NULL;
53
FrameBuffer::FrameBuffer(const Options& options)
56
ortho = (options.projection == Options::PROJ_ORTHOGRAPHIC);
58
// start coords with respect to cropwindow
59
xstart = CLAMP(CEILI(options.xRes*options.xMin), 0, options.xRes-1);
60
ystart = CLAMP(CEILI(options.yRes*options.yMin), 0, options.yRes-1);
61
const int xend = CLAMP(CEILI(options.xRes*options.xMax-1), 0, options.xRes-1);
62
const int yend = CLAMP(CEILI(options.yRes*options.yMax-1), 0, options.yRes-1);
63
// width & height with respect to cropwindow, one pixel minimum
64
width = xend - xstart + 1;
65
height = yend - ystart + 1;
67
// mode, as above, default is RI_RGB, error check during parse
70
if (options.displayMode == Options::DM_Z)
71
{ mode=MD_Z; dataPerPix=1; }
72
else if (options.displayMode == Options::DM_A)
73
{ mode=MD_A; dataPerPix=1; }
74
else if (options.displayMode == Options::DM_AZ)
75
{ mode=MD_AZ; dataPerPix=2; }
76
else if (options.displayMode == Options::DM_RGBA)
77
{ mode=MD_RGBA; dataPerPix=4; }
78
else if (options.displayMode == Options::DM_RGBZ)
79
{ mode=MD_RGBZ; dataPerPix=4; }
80
else if (options.displayMode == Options::DM_RGBAZ)
81
{ mode=MD_RGBAZ; dataPerPix=5; }
84
if (options.displayType == Options::DT_FILE)
86
else if (options.displayType == Options::DT_ZFILE)
88
else if (options.displayType == Options::DT_FRAMEBUFFER)
89
type = TP_FRAMEBUFFER;
91
// display name, 80 max chars
92
strncpy(dispname, options.displayName, 80);
96
gamma = options.gamma;
99
colQ.one = options.cqOne;
100
colQ.minimum = options.cqMin;
101
colQ.maximum = options.cqMax;
102
colQ.dither_amplitude = options.cqDitherAmplitude;
104
// depth quantizer, actually not used at all, might as well remove...
105
depthQ.one = options.zqOne;
106
depthQ.minimum = options.zqMin;
107
depthQ.maximum = options.zqMax;
108
depthQ.dither_amplitude = options.zqDitherAmplitude;
110
// buffer init, float per data element
111
unsigned int sz = width * height * dataPerPix;
112
fbdata = new float[sz];
114
memset(fbdata, 0, sz*sizeof(float));
116
// the quantized color buffer, only used for file mode
120
// init. display if 'framebuffer'
121
if (type == TP_FRAMEBUFFER) {
122
// always just rgb, conversion is handled in toImage() below
123
fbdisplay_img = new cimg_library::CImg<unsigned char>(width, height, 1, 3, 0);
124
fbdisplay = new cimg_library::CImgDisplay(*fbdisplay_img, dispname, 0);
125
// the ubiquitous checkerboard background pattern, white & grey bucketsize rectangles
126
const int bucketsize = options.bucketsize;
127
for (unsigned int y=0; y<=(height/bucketsize); ++y) {
128
for (unsigned int x=0; x<=(width/bucketsize); ++x) {
129
unsigned char col = (unsigned char)(((x & 1) ^ (y & 1)) ? 128 : 255);
130
for (unsigned int sy=y*bucketsize; sy<(y+1)*bucketsize; ++sy) {
131
if (sy >= height) continue;
132
for (unsigned int sx=x*bucketsize; sx<(x+1)*bucketsize; ++sx) {
133
if (sx >= width) continue;
134
(*fbdisplay_img)(sx, sy, 0) = (*fbdisplay_img)(sx, sy, 1) = (*fbdisplay_img)(sx, sy, 2) = col;
139
fbdisplay->display(*fbdisplay_img);
142
if (type == TP_FRAMEBUFFER)
143
std::cout << "Cannot display, CImg not enabled, writing to file instead." << std::endl;
149
FrameBuffer::~FrameBuffer()
151
// free alloc'ed data
153
if (fbdisplay_img) delete fbdisplay_img;
159
if (qdata) delete[] qdata;
160
if (fbdata) delete[] fbdata;
163
void FrameBuffer::operator()(int x, int y, float rgbaz[5])
165
x -= xstart, y -= ystart;
166
if ((x < 0) or (y < 0) or (x >= (int)width) or (y >= (int)height)) return;
167
unsigned int ofs = (x + y*width) * dataPerPix;
170
fbdata[ofs] = rgbaz[3];
173
fbdata[ofs] = rgbaz[4];
176
fbdata[ofs++] = rgbaz[3];
177
fbdata[ofs] = rgbaz[4];
180
fbdata[ofs++] = rgbaz[0];
181
fbdata[ofs++] = rgbaz[1];
182
fbdata[ofs++] = rgbaz[2];
183
fbdata[ofs] = rgbaz[3];
186
fbdata[ofs++] = rgbaz[0];
187
fbdata[ofs++] = rgbaz[1];
188
fbdata[ofs++] = rgbaz[2];
189
fbdata[ofs] = rgbaz[4];
192
fbdata[ofs++] = rgbaz[0];
193
fbdata[ofs++] = rgbaz[1];
194
fbdata[ofs++] = rgbaz[2];
195
fbdata[ofs++] = rgbaz[3];
196
fbdata[ofs] = rgbaz[4];
200
fbdata[ofs++] = rgbaz[0];
201
fbdata[ofs++] = rgbaz[1];
202
fbdata[ofs] = rgbaz[2];
206
// Quantize image or given subrect with start coords (xs,ys) & end coords (xe,ye)
207
void FrameBuffer::quantizeImage()
209
if (fbdata==NULL) return;
211
int dpx = (dataPerPix <= 3) ? 3 : 4;
212
qdata = new unsigned char[width*height*dpx];
214
const float igam = (gamma==0.f) ? 1.f : (1.f/ABS(gamma));
216
for (unsigned int y=0; y<height; ++y) {
217
for (unsigned int x=0; x<width; ++x) {
219
float* data = &fbdata[(x + y*width) * dataPerPix];
220
float fr=0, fg=0, fb=0, fa=1;
221
switch (dataPerPix) {
222
// Z case is handled separately
226
// MD_A or MD_AZ (Z is not saved in image, handled separately)
227
fr = fg = fb = 0, fa = 1.f-data[0];
240
// rgba, composite on top of background
242
fr = data[0] + fa*data[0];
243
fg = data[1] + fa*data[1];
244
fb = data[2] + fa*data[2];
248
// 'exposure' & gamma
250
fr *= gain; fg *= gain; fb *= gain;
253
fr = pow(CLAMP0(fr), igam);
254
fg = pow(CLAMP0(fg), igam);
255
fb = pow(CLAMP0(fb), igam);
259
unsigned char* qd = &qdata[(x + y*width)*dpx];
260
qd[0] = (unsigned char)CLAMP((int)(0.5f + colQ.one*fr + colQ.dither_amplitude*frand()), colQ.minimum, colQ.maximum);
261
qd[1] = (unsigned char)CLAMP((int)(0.5f + colQ.one*fg + colQ.dither_amplitude*frand()), colQ.minimum, colQ.maximum);
262
qd[2] = (unsigned char)CLAMP((int)(0.5f + colQ.one*fb + colQ.dither_amplitude*frand()), colQ.minimum, colQ.maximum);
264
qd[3] = (unsigned char)CLAMP((int)(0.5f + colQ.one*fa + colQ.dither_amplitude*frand()), colQ.minimum, colQ.maximum);
270
// save the image as a raw targa file
271
bool FrameBuffer::saveImage()
273
if (qdata==NULL) return false;
274
// raw tga output for now
275
// name is assigned by default
276
std::cout << "Saving Targa file as " << dispname << std::endl;
278
const unsigned char TGAHDR[12] = {0, 0, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0};
281
unsigned short w, h, x, y;
282
unsigned char* yscan;
283
unsigned char btsdesc[2];
285
int dpx = (dataPerPix <= 3) ? 3 : 4;
287
btsdesc[0] = 0x20; // 32 bits
288
btsdesc[1] = 0x28; // topleft / 8 bit alpha
291
btsdesc[0] = 0x18; // 24 bits
292
btsdesc[1] = 0x20; // topleft / no alpha
294
w = (unsigned short)width;
295
h = (unsigned short)height;
296
fp = fopen(dispname, "wb");
298
std::cout << "Could not open file for writing!\n";
301
fwrite(&TGAHDR, 12, 1, fp);
306
fwrite(&btsdesc, 2, 1, fp);
307
for (y=0; y<h; y++) {
308
// swap R & B channels
310
yscan = &qdata[dto*dpx];
311
for (x=0; x<w; x++, yscan+=dpx) {
312
fputc(*(yscan+2), fp);
313
fputc(*(yscan+1), fp);
315
if (dpx==4) fputc(*(yscan+3), fp);
319
std::cout << "OK" << std::endl;
324
bool FrameBuffer::saveZ()
326
// handles Z data only, MD_AZ/MD_RGBAZ not yet
327
if ((fbdata==NULL) || (mode!=MD_Z)) return false;
328
std::cout << "Saving Z file as " << dispname << std::endl;
329
Transform w2c = *State::Instance()->getNamedCoordSys("world"),
330
c2r = State::Instance()->cam.getCam2Ras();
331
//char fname[512] = {0};
332
//Options options = State::Instance()->topOptions();
333
//snprintf(fname, 512, "%s%s", options.basepath, dispname);
334
writeTiledZ(dispname, fbdata, width, height, (ortho ? 1 : 0), *w2c.getRtMatrixPtr(), *c2r.getRtMatrixPtr());
339
void FrameBuffer::finalize()
343
// draw final scanline block too
344
fbdisplay->display(*fbdisplay_img);
345
// framebuffer, wait until window closed
346
std::cout << "Close display window or press ESC key to quit...\n";
347
while (!fbdisplay->is_closed && (fbdisplay->key!=cimg_library::cimg::keyESC))
352
// file, z file is saved as float
353
if (type == TP_ZFILE)
356
// this still includes the AZ & RGBAZ cases, not sure what to do about that yet,
357
// will be very straightforward with exr support though... TODO
363
// bucket to display, only used in framebuffer mode
364
void FrameBuffer::toImage(int bx, int by, int bw, int bh, bool draw)
367
if (fbdisplay==NULL) return;
369
// just exit if window closed
370
if (fbdisplay->is_closed)
374
const float igam = (gamma==0.f) ? 1.f : (1.f/ABS(gamma));
376
const unsigned int chanofs = width*height;
378
for (int y=by; y<(by+bh); y++) {
379
if (y >= (int)height)continue;
380
const unsigned int yx = y*width;
381
for (int x=bx; x<(bx+bw); x++) {
382
if (x >= (int)width) continue;
384
const float* data = &fbdata[(x + yx) * dataPerPix];
386
// display color channel indices
387
const unsigned int idxR = x + y*width;
388
const unsigned int idxG = idxR + chanofs;
389
const unsigned int idxB = idxG + chanofs;
390
switch (dataPerPix) {
393
fr = fg = fb = data[0]*0.0001f; // scale to make depth more visible, temporary
396
fr = fg = fb = data[0];
405
// rgba, composite on top of display background.
406
// side effect here is that background will also be affected by gam/exp
407
// though this might actually be useful as progress visual feedback.
408
// and only display is affected anyway, not actual picture
409
const float ia = (1.f - data[3])*(1.f/255.f);
410
fr = data[0] + ia*fbdisplay_img->data[idxR];
411
fg = data[1] + ia*fbdisplay_img->data[idxG];
412
fb = data[2] + ia*fbdisplay_img->data[idxB];
415
// 'exposure' & gamma
417
fr = (fr < 0.f) ? 0.f : powf(fr*gain, igam);
418
fg = (fg < 0.f) ? 0.f : powf(fg*gain, igam);
419
fb = (fb < 0.f) ? 0.f : powf(fb*gain, igam);
422
// quantize and set pixel
424
fbdisplay_img->data[idxR] = (unsigned char)CLAMP((int)(0.5f + colQ.one*fr + colQ.dither_amplitude*frand()), colQ.minimum, colQ.maximum);
426
fbdisplay_img->data[idxG] = (unsigned char)CLAMP((int)(0.5f + colQ.one*fg + colQ.dither_amplitude*frand()), colQ.minimum, colQ.maximum);
428
fbdisplay_img->data[idxB] = (unsigned char)CLAMP((int)(0.5f + colQ.one*fb + colQ.dither_amplitude*frand()), colQ.minimum, colQ.maximum);
432
// only redraw image if draw == true
433
if (draw) fbdisplay->display(*fbdisplay_img);