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.

664 lines
22 KiB

/***************************************************************************
qsimage.cpp
-------------------
begin : 01-January-2000
copyright : (C) 2000 by Kamil Dobkowski
email : kamildobk@poczta.onet.pl
***************************************************************************/
/***************************************************************************
* *
* 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 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "qsimage.h"
#include <math.h>
#include <assert.h>
struct QSImage::image_runtime_data {
enum Type { RGBImage, GrayImage, IndexedImage } type;
struct {
QSPt2 p1;
QSPt2 p2;
} rarea; // area to refresh
int pi;
int pj;
int lines;
int curr_x_index;
int curr_y_index;
QSAxis *xaxis;
QSAxis *yaxis;
QSAxis *vaxis;
QSMatrix *rm;
QSMatrix *gm;
QSMatrix *bm;
QSMatrix *pm;
int w, h, ph;
int *xbuff;
int *ybuff;
bool is_x_vector;
bool is_y_vector;
QSGFill fill;
QSDrv::PixmapBuffer buff;
};
#define CHANNELS_NUM 6
//-------------------------------------------------------------//
QSImage::QSImage(QSAxes* parent, const char * name)
:QSPlot2D(parent,name)
{
assert( parent );
m_use_gradient = true;
m_axes = parent;
m_evalid = false;
m_rawmode = false;
m_title_str = tr("Untitled image");
m_dmin = 0.0;
m_dmax = 0.0;
initChannelTable( CHANNELS_NUM );
initAttributeTables( 0, 0, 0, 0 );
}
//-------------------------------------------------------------//
QSImage::~QSImage()
{
}
//-------------------------------------------------------------//
void QSImage::setUseGradient( bool enable )
{
if ( m_use_gradient != enable ) {
parametersChanging();
m_use_gradient = enable;
parametersChanged();
}
}
//-------------------------------------------------------------//
void QSImage::setRawMode( bool enabled )
{
if ( m_rawmode != enabled ) {
parametersChanging();
m_rawmode = enabled;
m_evalid = false;
parametersChanged();
}
}
//-------------------------------------------------------------//
void QSImage::allocRuntimeData()
{
QSPlot2D::allocRuntimeData();
d = new image_runtime_data();
d->is_x_vector = false;
d->is_y_vector = false;
d->xbuff = NULL;
d->ybuff = NULL;
d->pi = 0;
d->pj = 0;
d->xaxis = defaultAxis(QSAxis::XAxisType);
d->yaxis = defaultAxis(QSAxis::YAxisType);
d->vaxis = defaultAxis(QSAxis::VAxisType);
d->w = matrixCols( DataRed );
d->h = matrixRows( DataRed );
d->ph = matrixRows( Palette );
d->rm = matrix( DataRed );
d->gm = matrix( DataGreen );
d->bm = matrix( DataBlue );
d->pm = matrix( Palette );
// detect image type
d->type = image_runtime_data::GrayImage;
if ( matrixCols( Palette ) == 3 &&
matrixRows( Palette ) > 0 ) d->type = image_runtime_data::IndexedImage;
if ( matrixCols( DataGreen ) == d->w &&
matrixRows( DataGreen ) == d->h &&
matrixCols( DataBlue ) == d->w &&
matrixRows( DataBlue ) == d->h ) d->type = image_runtime_data::RGBImage;
if ( matrixCols( XVector ) == d->w+1 &&
matrixRows( XVector ) > 0 ) d->is_x_vector = true;
else d->is_x_vector = false;
if ( matrixRows( YVector ) == d->h+1 &&
matrixCols( YVector ) > 0 ) d->is_y_vector = true;
else d->is_y_vector = false;
if ( m_rawmode ) { m_dmin = 0.0; m_dmax = 255.0; m_evalid = true; }
}
//-------------------------------------------------------------//
void QSImage::freeRuntimeData()
{
delete[] d->xbuff; d->xbuff = NULL;
delete[] d->ybuff; d->ybuff = NULL;
delete d; d = NULL;
QSPlot2D::freeRuntimeData();
}
//-------------------------------------------------------------//
bool QSImage::getAxisRange( QSAxis *axis, double& min, double& max )
{
allocRuntimeData();
if ( d->w == 0 || d->h == 0 ) { freeRuntimeData(); return false; }
if ( !m_evalid ) {
for( int j=0; j<d->h; j++ ) {
for( int i=0; i<d->w; i++ ) {
double vmax;
double vmin;
vmax = vmin = value(j,i,DataRed);
if ( d->type == image_runtime_data::RGBImage ) {
double vg = value( j, i, DataGreen );
double vb = value( j, i, DataBlue );
vmax = QMAX( vmax, QMAX( vg, vb ) );
vmin = QMIN( vmin, QMIN( vg, vb ) );
}
if ( i == 0 && j == 0 ) {
m_dmax = vmax;
m_dmin = vmin;
} else {
m_dmin = QMIN( m_dmin, vmin );
m_dmax = QMAX( m_dmax, vmax );
}
} // for ( i= ..
} // for ( j= ...
m_evalid = true;
}
double xmin = d->is_x_vector ? matrix(XVector)->value(0,0) : 0.0;
double xmax = d->is_x_vector ? matrix(XVector)->value(0,d->w) : d->w;
double ymin = d->is_y_vector ? matrix(YVector)->value(0,0) : 0.0;
double ymax = d->is_y_vector ? matrix(YVector)->value(d->h,0) : d->h;
if ( xmin > xmax ) { double xtemp = xmin; xmin = xmax; xmax = xtemp; }
if ( ymin > ymax ) { double ytemp = ymin; ymin = ymax; ymax = ytemp; }
if ( axis == d->xaxis ) { min = xmin; max = xmax; }
else if ( axis == d->yaxis ) { min = ymin; max = ymax; }
else if ( axis == d->vaxis && m_rawmode ) { min = 0.0; max = 255.0; }
else if ( axis == d->vaxis && !m_rawmode ) { min = m_dmin; max = m_dmax; }
else { freeRuntimeData(); return false; }
freeRuntimeData();
return true;
}
//-------------------------------------------------------------//
bool QSImage::start()
{
QSPlot2D::start();
if ( !init_buffers() ) return false;
// Init loops
d->pi = d->rarea.p1.x;
d->pj = d->rarea.p1.y;
d->curr_x_index = -999999; // see set_rgb
d->curr_y_index = -999999;
d->lines = 0;
return true;
}
//-------------------------------------------------------------//
bool QSImage::init_buffers()
{
// detect
QSPt2f p1;
QSPt2f p2;
p1.x = d->is_x_vector ? matrix(XVector)->value(0,0) : 0.0;
p1.y = d->is_y_vector ? matrix(YVector)->value(0,0) : 0.0;
p2.x = d->is_x_vector ? matrix(XVector)->value(0,d->w) : d->w;
p2.y = d->is_y_vector ? matrix(YVector)->value(d->h,0) : d->h;
p1 = m_axes->dataToCanvas(p1,d->xaxis,d->yaxis);
p2 = m_axes->dataToCanvas(p2,d->xaxis,d->yaxis);
QSPt2 cp1( int(p1.x+0.5), int(p1.y+0.5) );
QSPt2 cp2( int(p2.x+0.5), int(p2.y+0.5) );
if ( cp1.x > cp2.x ) { int temp = cp1.x; cp1.x = cp2.x; cp2.x = temp; }
if ( cp1.y > cp2.y ) { int temp = cp1.y; cp1.y = cp2.y; cp2.y = temp; }
// Intersect refresh area with clipping area
// clipping area is set in plot2d as an interior
// of the axis box.
if ( !clip_rect(cp1,cp2) ) return false;
// where bitmap starts
d->rarea.p1 = cp1;
d->rarea.p2 = cp2;
// X index buffer
// index buffer tells, for each screen pixel between rarea.x1 and
// rarea.x2, which data column is to be drawn at this position
d->xbuff = new int[d->rarea.p2.x-d->rarea.p1.x+1];
for ( int i=0; i<d->w; i++ ) { // for each data column
double dx1 = d->is_x_vector ? matrix(XVector)->value(0,i) : i;
double dx2 = d->is_x_vector ? matrix(XVector)->value(0,i+1) : i+1;
int cx1 = int(m_axes->dataToCanvas( QSPt2f(dx1,0.0), d->xaxis, d->yaxis ).x+0.5); //start pos of this column on the screen
int cx2 = int(m_axes->dataToCanvas( QSPt2f(dx2,0.0), d->xaxis, d->yaxis ).x+0.5); // end pos of this column on the screen
if ( cx2 < cx1 ) { int temp = cx1; cx1 = cx2; cx2 = temp; }
for ( int j=cx1; j<=cx2; j++ ) // for ach screen pixel beetween cx2 an cx1
if ( j>=d->rarea.p1.x && j<=d->rarea.p2.x ) d->xbuff[j-d->rarea.p1.x] = i;
}
// Y index buffer
d->ybuff = new int[d->rarea.p2.y-d->rarea.p1.y+1];
for ( int i=0; i<d->h; i++ ) {
double dy1 = d->is_y_vector ? matrix(YVector)->value(i,0) : i;
double dy2 = d->is_y_vector ? matrix(YVector)->value(i+1,0) : i+1;
int cy1 = int(m_axes->dataToCanvas( QSPt2f(0.0,dy1), d->xaxis, d->yaxis ).y+0.5);
int cy2 = int(m_axes->dataToCanvas( QSPt2f(0.0,dy2), d->xaxis, d->yaxis ).y+0.5);
if ( cy2 < cy1 ) { int temp = cy1; cy1 = cy2; cy2 = temp; }
for ( int j=cy1; j<=cy2; j++ )
if ( j>=d->rarea.p1.y && j<=d->rarea.p2.y ) d->ybuff[j-d->rarea.p1.y] = i;
}
return true;
}
//-------------------------------------------------------------//
bool QSImage::clip_rect( QSPt2& p1, QSPt2& p2 )
{
int cx1;
int cy1;
int cx2;
int cy2;
//dynamic_cast<const QSProjection2D*>(m_axes->proj())->getClipRect( &cx1, &cy1, &cx2, &cy2 );
QSPt2f pos1 = m_axes->proj()->world2DToCanvas( QSPt2f(0.0,0.0) );
QSPt2f pos2 = m_axes->proj()->world2DToCanvas( QSPt2f(1.0,1.0) );
cx1 = QMIN( int(pos1.x+0.5), int(pos2.x+0.5) );
cy1 = QMIN( int(pos1.y+0.5), int(pos2.y+0.5) );
cx2 = QMAX( int(pos1.x+0.5), int(pos2.x+0.5) );
cy2 = QMAX( int(pos1.y+0.5), int(pos2.y+0.5) );
int x01;
int y01;
int x02;
int y02;
if ( cx1 > p1.x ) x01 = cx1; else x01 = p1.x;
if ( cy1 > p1.y ) y01 = cy1; else y01 = p1.y;
if ( cx2 < p2.x ) x02 = cx2; else x02 = p2.x;
if ( cy2 < p2.y ) y02 = cy2; else y02 = p2.y;
x02 -= 1;
y02 -= 1;
if ( x02-x01 > 0 && y02-y01 > 0 ) {
p1.x = x01; p1.y = y01;
p2.x = x02; p2.y = y02;
return true;
}
return false;
}
//-------------------------------------------------------------//
void QSImage::end()
{
// all clean-up work is done in freeRuntimeData()
QSPlot2D::end();
}
//-------------------------------------------------------------//
void QSImage::dataChanged( int channel )
// Ouu. We need to calculate new extremes in data
// Refresh data on screen also.
{
m_evalid = false;
m_dmin = m_dmax = 0.0;
QSPlot2D::dataChanged( channel );
}
//-------------------------------------------------------------//
bool QSImage::step()
{
int curr_step = 0;
while( d->pj <= d->rarea.p2.y ) {
// Get pixmap buffer for the next buff.lines lines
if ( d->lines == 0 ) {
d->buff.ptr = NULL;
m_drv->getPixmapBuffer( &d->buff, d->rarea.p2.x-d->rarea.p1.x+1, d->rarea.p2.y-d->pj+1 );
if ( d->buff.ptr == NULL || d->buff.lines == 0 ) { return false; }
m_ptr = d->buff.ptr;
d->lines = d->buff.lines;
}
int ypos = d->ybuff[d->pj-d->rarea.p1.y];
// Write scanlines to the buffer
while( d->pi <= d->rarea.p2.x ) {
set_rgb( m_ptr, d->xbuff[d->pi-d->rarea.p1.x], ypos );
d->pi ++; m_ptr += d->buff.po;
if ( curr_step++ > (work_steps<<5) && m_bkg_handler ) return true;
}
d->pj ++;
// Draw pixmap
if ( --d->lines == 0 ) {
QSPt2 ppos( d->rarea.p1.x, d->pj-d->buff.lines );
m_drv->drawPixmap( QSPt2f( ppos.x, ppos.y ), &d->buff );
}
// Set pointer to the next scanline
m_ptr = d->buff.ptr + (d->buff.lines-d->lines) * d->buff.lo;
d->pi = d->rarea.p1.x;
}
return false;
}
//-------------------------------------------------------------//
void QSImage::set_rgb( unsigned char* p, int x_index, int y_index )
{
double dr = 0.0;
double dg = 0.0;
double db = 0.0;
if ( d->curr_x_index != x_index ||
d->curr_y_index != y_index ) {
dr = d->rm->value(y_index,x_index);//c
if ( d->type == image_runtime_data::RGBImage ) {
dg = d->gm->value(y_index,x_index);//c
db = d->bm->value(y_index,x_index);//c
}
// convert from data to world
if ( !m_rawmode ) {
if ( !m_use_gradient ) {
dr = d->vaxis->dataToWorld(dr)*255.0+0.5;
if ( d->type == image_runtime_data::RGBImage ) {
dg = d->vaxis->dataToWorld(dg)*255.0+0.5;
db = d->vaxis->dataToWorld(db)*255.0+0.5;
}
} else {
m_gradient.fill( d->vaxis->dataToWorld(dr), d->fill );
m_r = d->fill.color.r;
m_g = d->fill.color.g;
m_b = d->fill.color.b;
}
}
// take a palette value
if ( !m_use_gradient )
if ( d->type == image_runtime_data::IndexedImage ) {
if ( dr < 0 ) dr = 0;
if ( dr > d->ph-1 ) dr = d->ph-1;
int pindex = int(dr);
dr = d->pm->value( pindex,0 ); //c
dg = d->pm->value( pindex,1 );
db = d->pm->value( pindex,2 );
}
if ( !m_use_gradient ) {
if ( dr < 0.0 ) dr = 0.0;
if ( dr > 255.0 ) dr = 255.0;
}
// finally set m_r, m_g, m_b values
if ( !m_use_gradient )
if ( d->type == image_runtime_data::GrayImage ) {
m_r = m_g = m_b = (unsigned char )dr;
} else {
if ( dg < 0.0 ) dg = 0.0;
if ( db < 0.0 ) db = 0.0;
if ( dg > 255.0 ) dg = 255.0;
if ( db > 255.0 ) db = 255.0;
m_r = (unsigned char )dr;
m_g = (unsigned char )dg;
m_b = (unsigned char )db;
}
d->curr_x_index = x_index;
d->curr_y_index = y_index;
}
*p = 255; p += d->buff.co;// alpha
*p = m_r; p += d->buff.co;
*p = m_g; p += d->buff.co;
*p = m_b;
}
//-------------------------------------------------------------//
QString QSImage::posInfo( QSPt2f& p )
{
if ( m_busy ) return QString::null;
int pindex;
QString result;
allocRuntimeData();
if ( !init_buffers() ) { freeRuntimeData(); return QString::null; }
QSPt2 pos( int(p.x+0.5), int(p.y+0.5) );
if ( pos.x >= d->rarea.p1.x && pos.x <= d->rarea.p2.x &&
pos.y >= d->rarea.p1.y && pos.y <= d->rarea.p2.y ) {
QSPt2 index( d->xbuff[pos.x-d->rarea.p1.x], d->ybuff[pos.y-d->rarea.p1.y] );
result = QString(tr(" row = ")) + QString::number(index.y) + "\n";
result += QString(tr(" col = ")) + QString::number(index.x) + "\n";
//QSPt2f p = m_proj->canvasToWorld2D( pos );
//p = worldToData( p );
//result += QString(tr(" X = ")) + QString::number(p.x) + "\n";
//result += QString(tr(" Y = ")) + QString::number(p.y) + "\n";
switch( d->type ) {
case image_runtime_data::GrayImage:
result += QString(tr(" value = ")) + QString::number(d->rm->value(index.y,index.x)) + "\n";
break;
case image_runtime_data::RGBImage:
result += QString(tr(" R = ")) + QString::number(d->rm->value(index.y,index.x)) + "\n";
result += QString(tr(" G = ")) + QString::number(d->gm->value(index.y,index.x)) + "\n";
result += QString(tr(" B = ")) + QString::number(d->bm->value(index.y,index.x)) + "\n";
break;
case image_runtime_data::IndexedImage:
pindex = (int )d->rm->value(index.y,index.x); pindex = QMAX( pindex, 0 ); pindex = QMIN( pindex, d->ph-1 );
result += QString(tr(" palette index = ")) + QString::number(pindex) + "\n";
result += QString(tr(" R = ")) + QString::number(d->pm->value(pindex,0)) + "\n";
result += QString(tr(" G = ")) + QString::number(d->pm->value(pindex,1)) + "\n";
result += QString(tr(" B = ")) + QString::number(d->pm->value(pindex,2)) + "\n";
/*
result += QString(tr(" G = ")) + QString::number(d->pm->ncol()) + "\n";
result += QString(tr(" B = ")) + QString::number(d->pm->ncol()) + "\n";
*/
default: break;
}
}
freeRuntimeData();
return result;
}
//-------------------------------------------------------------//
bool QSImage::isClicked( const QSPt2f& pos )
{
if ( m_busy ) return false;
allocRuntimeData();
if ( !init_buffers() ) { freeRuntimeData(); return false; }
if ( pos.x >= d->rarea.p1.x && pos.x <= d->rarea.p2.x &&
pos.y >= d->rarea.p1.y && pos.y <= d->rarea.p2.y ) return true;
freeRuntimeData();
return false;
}
//-------------------------------------------------------------//
#define BOX_SPACE 2.0
#define BOX_WIDTH 20.0
#define BOX_HEIGHT 100.0
QSPt2f QSImage::legendItemSize( QSDrv *drv )
{
double boxSpace = drv->toPixels(BOX_SPACE);
QSPt2f boxSize( drv->toPixels(BOX_WIDTH),
drv->toPixels(BOX_HEIGHT) );
QSPt2f tsize = drv->rTextSize( 270, title() );
QSPt2f lsize;
int nr_labels = 0;
QSAxis *axis = defaultAxis(QSAxis::VAxisType);
const list<QSAxisTic> *tics = axis->tics();
list<QSAxisTic>::const_iterator curr = tics->begin();
list<QSAxisTic>::const_iterator last = tics->end();
while( curr != last ) {
if ( !(*curr).m_major ) { curr++; continue; }
if ( !(*curr).m_label.isEmpty() ) {
QSPt2f size = drv->rTextSize( (*curr).m_angle, (*curr).m_label );
lsize.x = QMAX(lsize.x, size.x);
lsize.y = QMAX(lsize.y, size.y);
}
curr++;
nr_labels++;
}
boxSize.y = QMAX(boxSize.y,nr_labels*lsize.y);
return QSPt2f( tsize.x+boxSpace+boxSize.x+boxSpace+boxSpace+boxSpace+lsize.x, QMAX(tsize.y,boxSize.y) );
}
//-------------------------------------------------------------//
void QSImage::drawLegendItem( const QSPt2f& pos, QSDrv *drv )
{
double boxSpace = drv->toPixels(BOX_SPACE);
QSPt2f boxSize( drv->toPixels(BOX_WIDTH),
drv->toPixels(BOX_HEIGHT) );
// detect image type and number of colors in gradient
int h = 256;
int type = image_runtime_data::GrayImage;
if ( matrixCols( Palette ) == 3 &&
matrixRows( Palette ) > 0 ) { type = image_runtime_data::IndexedImage; h = matrixRows( Palette ); }
if ( matrixCols( DataGreen ) == matrixCols( DataRed ) &&
matrixRows( DataGreen ) == matrixRows( DataRed ) &&
matrixCols( DataBlue ) == matrixCols( DataRed ) &&
matrixRows( DataBlue ) == matrixRows( DataRed ) ) type = image_runtime_data::RGBImage;
// title
QSPt2f tsize = drv->rTextSize( 270, title() );
double height = boxSize.y = legendItemSize(drv).y;
drv->drawRText( QSPt2f(pos.x,pos.y+height/2), 270, title(), AlignHCenter | AlignTop );
// gradient
QSAxis *axis = defaultAxis(QSAxis::VAxisType);
QSPt2f cpos( pos.x+tsize.x+boxSpace, pos.y+(height-boxSize.y)/2.0 );
drv->setLine( QSGLine::invisibleLine );
for ( int i=0; i<h; i++ ) {
int index = h-i-1;
QSGFill f;
QSPt2f p1( cpos.x, cpos.y+i*boxSize.y/h);
QSPt2f p2( cpos.x+boxSize.x, cpos.y+(i+1)*boxSize.y/h);
double x01 = cpos.x+boxSize.x/3.0;
double x02 = cpos.x+boxSize.x*2.0/3.0;
switch ( type ) {
case image_runtime_data::GrayImage:
if ( m_use_gradient ) m_gradient.fill( double(h-i)/h, f );
else f.color = QSGColor( index, index, index);
drv->setFill( f );
drv->drawRect( p1, p2 );
break;
case image_runtime_data::IndexedImage:
/*
f.color = QSGColor( (unsigned char )matrix(Palette)->value(index,0),
(unsigned char )matrix(Palette)->ncol(),
(unsigned char )matrix(Palette)->ncol() );
*/
f.color = QSGColor( (unsigned char )matrix(Palette)->value(index,0),
(unsigned char )matrix(Palette)->value(index,1),
(unsigned char )matrix(Palette)->value(index,2) );
drv->setFill( f );
drv->drawRect( p1, p2 );
break;
case image_runtime_data::RGBImage:
f.color = QSGColor( index, 0, 0 ); drv->setFill( f );
drv->drawRect( QSPt2f(p1.x,p1.y), QSPt2f(x01,p2.y) );
f.color = QSGColor( 0, index, 0 ); drv->setFill( f );
drv->drawRect( QSPt2f(x01,p1.y), QSPt2f(x02,p2.y) );
f.color = QSGColor( 0, 0, index ); drv->setFill( f );
drv->drawRect( QSPt2f(x02,p1.y), QSPt2f(p2.x,p2.y) );
break;
}
}
/*
// frame
drv->setFill( QSGFill::transparentFill );
drv->drawRect( cpos, cpos+boxSize );
*/
QSGLine l; drv->setLine( l );
cpos.x = cpos.x+boxSize.x;
// labels
const list<QSAxisTic> *tics = axis->tics();
list<QSAxisTic>::const_iterator curr = tics->begin();
list<QSAxisTic>::const_iterator last = tics->end();
while( curr != last ) {
if ( !(*curr).m_major ) { curr++; continue; }
double ypos = cpos.y+ (1.0-(*curr).m_pos)*boxSize.y+0.5;
drv->drawLine( QSPt2f(cpos.x,ypos), QSPt2f(cpos.x+boxSpace,ypos) );
drv->drawRText( QSPt2f(cpos.x+boxSpace+boxSpace+boxSpace,ypos), (*curr).m_angle, (*curr).m_label, AlignLeft | AlignVCenter );
curr++;
}
}
//-------------------------------------------------------------//
void QSImage::loadStateFromStream( QDataStream& stream, QSObjectFactory *factory )
{
QSPlot2D::loadStateFromStream( stream, factory );
}
//-------------------------------------------------------------//
void QSImage::saveStateToStream( QDataStream& stream, QSObjectFactory *factory )
{
QSPlot2D::saveStateToStream( stream, factory );
}
//-------------------------------------------------------------//
QString QSImage::channelVariable( int channel ) const
{
switch( channel ) {
case DataRed: return "r";
case DataGreen: return "g";
case DataBlue: return "b";
case Palette: return "p";
case XVector: return "x";
case YVector: return "y";
}
return QString::null;
}
//-------------------------------------------------------------//
QSImage::ColumnType QSImage::columnType( int channel, int column ) const
{
}