WSL/SLF GitLab Repository

Commit ed0e449c authored by Mathias Bavay's avatar Mathias Bavay
Browse files

Indexed gradients are now handled by the Gradient class and used by the PNGIO...

Indexed gradients are now handled by the Gradient class and used by the PNGIO plugin to generate indexed PNGs (some consts in the code can toggle it back to non-indexed images). This leads to a significant speedup and large file size reduction. On a DEM test case, while reading a 2D ARC grid and writing it back, the following have been measured:

After commit 758, support for indexed PNGs. This leads to major speed and size improvements:

version                  duration                   file size
full color                 0.530                       617k    
reduced to 30        0.460   13%             156k   75%
indexed, 30           0.352   34%               90k   85%
indexed, 30           0.320   40%             123k   80%
(optimized for speed)
parent f33d31cf
......@@ -279,11 +279,13 @@ void Color::HSVtoRGB(const double& h, const double& s, const double& v, double &
// Gradient class
/////////////////////////////////////////////////////////////////////////////////////////////////
const unsigned char Gradient::channel_max_color = 255;
const unsigned int Gradient::reserved_idx = 5;
const unsigned int Gradient::reserved_cols = 2;
Gradient::Gradient(const Type& type, const double& i_min, const double& i_max, const bool& i_autoscale)
{
set(type, i_min, i_max, i_autoscale);
nr_unique_levels = 0;
nr_unique_cols = 0;
}
void Gradient::set(const Type& type, const double& i_min, const double& i_max, const bool& i_autoscale)
......@@ -305,7 +307,13 @@ void Gradient::set(const Type& type, const double& i_min, const double& i_max, c
}
void Gradient::setNrOfLevels(const unsigned int& i_nr_unique_levels) {
nr_unique_levels = i_nr_unique_levels;
if(i_nr_unique_levels<=reserved_idx) {
stringstream ss;
ss << "Insufficient number of colors requested for gradient: ask for more than ";
ss << reserved_idx << " colors!";
throw InvalidArgumentException(ss.str(), AT);
}
nr_unique_cols = i_nr_unique_levels - reserved_idx;
}
//val between min_val and max_val
......@@ -338,14 +346,14 @@ void Gradient::getColor(const double& val, unsigned char& r, unsigned char& g, u
a=false;
double r_d,g_d,b_d;
double val_norm;
if(nr_unique_levels==0) {
if(nr_unique_cols==0) {
if(autoscale && val<min) val_norm=0.;
else if(autoscale && val>max) val_norm=1.;
else val_norm = (val-min)/delta;
} else {
if(autoscale && val<min) val_norm=0.;
else if(autoscale && val>max) val_norm=1.;
else val_norm = (static_cast<unsigned int>( (val-min)/delta*(double)nr_unique_levels )) / (double)nr_unique_levels;
else val_norm = (static_cast<unsigned int>( (val-min)/delta*(double)nr_unique_cols )) / (double)nr_unique_cols;
}
model->getColor(val_norm, r_d, g_d, b_d);
r = static_cast<unsigned char>(r_d*channel_max_color);
......@@ -353,6 +361,81 @@ void Gradient::getColor(const double& val, unsigned char& r, unsigned char& g, u
b = static_cast<unsigned char>(b_d*channel_max_color);
}
void Gradient::getColor(const double& val, unsigned int& index) const
{
if(model==NULL) {
throw UnknownValueException("Please set the color gradient before using it!", AT);
}
if(val==IOUtils::nodata) {
index=0;
return;
}
if(val==legend::bg_color) {
index=1;
return;
}
if(val==legend::text_color) {
index=2;
return;
}
if(autoscale && delta==0) { //constant data throughout the grid & autoscale are no friends...
index=nr_unique_cols/2 + reserved_idx;
return;
}
if(nr_unique_cols==0) {
throw UnknownValueException("Please define the number of colors for indexed gradients!", AT);
}
//watch out!! the palette contains some reserved values at the begining
if(val<min) index=reserved_idx-2;
else if(val>max) index=reserved_idx-1;
else index = static_cast<unsigned int>( (val-min)/delta*(double)nr_unique_cols ) + reserved_idx;
}
void Gradient::getPalette(std::vector<unsigned char> &r, std::vector<unsigned char> &g, std::vector<unsigned char> &b) const
{
if(model==NULL) {
throw UnknownValueException("Please set the color gradient before using it!", AT);
}
if(nr_unique_cols==0) {
throw UnknownValueException("Please define the number of colors for indexed gradients!", AT);
}
r.clear(); g.clear(); b.clear();
//transparent color
r.push_back(channel_max_color); g.push_back(channel_max_color); b.push_back(channel_max_color);
//legend background color
r.push_back(channel_max_color-1); g.push_back(channel_max_color-1); b.push_back(channel_max_color-1);
//legend text color
r.push_back(0); g.push_back(0); b.push_back(0);
double r_d, g_d, b_d;
//under-range data color
model->getColor(-0.1, r_d, g_d, b_d);
r.push_back( static_cast<unsigned char>(r_d*channel_max_color) );
g.push_back( static_cast<unsigned char>(g_d*channel_max_color) );
b.push_back( static_cast<unsigned char>(b_d*channel_max_color) );
//over-range data color
model->getColor(1.1, r_d, g_d, b_d);
r.push_back( static_cast<unsigned char>(r_d*channel_max_color) );
g.push_back( static_cast<unsigned char>(g_d*channel_max_color) );
b.push_back( static_cast<unsigned char>(b_d*channel_max_color) );
//all normal colors
for(unsigned int ii=0; ii<=nr_unique_cols; ii++) {
const double val_norm = (double)ii/(double)nr_unique_cols;
model->getColor(val_norm, r_d, g_d, b_d);
r.push_back( static_cast<unsigned char>(r_d*channel_max_color) );
g.push_back( static_cast<unsigned char>(g_d*channel_max_color) );
b.push_back( static_cast<unsigned char>(b_d*channel_max_color) );
}
}
//we assume that the vectors are sorted by X
double Gradient_model::getInterpol(const double& val, const std::vector<double>& X, const std::vector<double>& Y) const
{
......
......@@ -195,7 +195,7 @@ class Gradient {
* @brief Default Constructor.
* This should be followed by a call to set() before calling getColor
*/
Gradient() {model=NULL; min=max=delta=0.; nr_unique_levels=0;};
Gradient() {model=NULL; min=max=delta=0.; nr_unique_cols=0;};
/**
* @brief Constructor.
......@@ -226,7 +226,7 @@ class Gradient {
* The given argument is an upper bound for the number of unique levels in the generated
* gradient (leading to a reduced number of colors). This is a specially easy and useful way of reducing a file size with
* no run time overhead (and even a small benefit) and little visible impact if
* the number of levels/colors remains large enough (say, at least 30)
* the number of levels/colors remains large enough (say, at least 20-30)
* @param i_nr_unique_levels maximum number of unique levels
*/
void setNrOfLevels(const unsigned int& i_nr_unique_levels);
......@@ -242,11 +242,31 @@ class Gradient {
*/
void getColor(const double &val, unsigned char &r, unsigned char &g, unsigned char &b, bool &a) const;
/**
* @brief Get palette index values for a given numeric value
* See class description for more explanations on the implementation/behavior
* @param val numerical value to convert
* @param index palette index for the given value
*/
void getColor(const double& val, unsigned int& index) const;
/**
* @brief Get palette colors for the selected gradient
* When building an indexed image, one needs to first retrieve the palette using this method. Afterwards, getColor(val, index)
* will be called for each pixel in order to retrieve its palette index.
* @param r red components
* @param g green components
* @param b blue components
*/
void getPalette(std::vector<unsigned char> &r, std::vector<unsigned char> &g, std::vector<unsigned char> &b) const;
static const unsigned char channel_max_color; ///< nr of colors per channel of the generated gradients
private:
double min, max, delta;
bool autoscale;
unsigned char nr_unique_levels;
unsigned int nr_unique_cols; ///< number of unique colors to generate. The same discretization is performed for indexed or not
static const unsigned int reserved_idx; ///< for indexed gradients, number of reserved indexes
static const unsigned int reserved_cols; ///< for non-indexed gradients, number of reserved colors
Gradient_model *model;
};
......
......@@ -75,6 +75,9 @@ const double PNGIO::plugin_nodata = -999.; //plugin specific nodata value. It ca
const unsigned char PNGIO::channel_depth = 8;
const unsigned char PNGIO::channel_max_color = 255;
const unsigned char PNGIO::transparent_grey = channel_max_color;
const bool PNGIO::indexed_png = true;
const bool PNGIO::optimize_for_speed = true;
const unsigned int PNGIO::nr_levels = 30;
PNGIO::PNGIO(void (*delObj)(void*), const Config& i_cfg) : IOInterface(delObj), cfg(i_cfg)
{
......@@ -285,13 +288,27 @@ void PNGIO::setFile(const std::string& filename, png_structp& png_ptr, png_infop
png_init_io(png_ptr, fp);
// Write header (8 bit colour depth). Alpha channel with PNG_COLOR_TYPE_RGB_ALPHA
png_set_IHDR(png_ptr, info_ptr, width, height,
channel_depth, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
//set transparent color (ie: cheap transparency: leads to smaller files and shorter run times)
png_color_16 trans_rgb_value = {transparent_grey, transparent_grey, transparent_grey, transparent_grey, transparent_grey};
png_set_tRNS(png_ptr, info_ptr, 0, 0, &trans_rgb_value);
//png_set_compression_level(png_ptr, Z_BEST_COMPRESSION);
if(optimize_for_speed) png_set_compression_level(png_ptr, Z_BEST_SPEED);
png_set_filter(png_ptr, PNG_FILTER_TYPE_BASE, PNG_FILTER_SUB); //any other filter is costly and brings close to nothing...
// Write header (8 bit colour depth). Full alpha channel with PNG_COLOR_TYPE_RGB_ALPHA
if(indexed_png) {
png_set_IHDR(png_ptr, info_ptr, width, height,
channel_depth, PNG_COLOR_TYPE_PALETTE, PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
//set transparent color (ie: cheap transparency: leads to smaller files and shorter run times)
png_byte trans = 0; //by convention, the gradient define it as color 0
png_set_tRNS(png_ptr, info_ptr, &trans, 1, 0);
} else {
png_set_IHDR(png_ptr, info_ptr, width, height,
channel_depth, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
//set transparent color (ie: cheap transparency: leads to smaller files and shorter run times)
png_color_16 trans_rgb_value = {transparent_grey, transparent_grey, transparent_grey, transparent_grey, transparent_grey};
png_set_tRNS(png_ptr, info_ptr, 0, 0, &trans_rgb_value);
}
//set background color to help applications show the picture when no background is present
png_color_16 background = {channel_max_color, channel_max_color, channel_max_color, channel_max_color, channel_max_color};
png_set_background(png_ptr, &background, PNG_BACKGROUND_GAMMA_SCREEN, true, 1.0);
......@@ -316,47 +333,87 @@ void PNGIO::writeDataSection(const Grid2DObject &grid, const Array2D<double> &le
const double nrows = grid.nrows;
// Allocate memory for one row (3 bytes per pixel - RGB)
const unsigned char channels = 3;
unsigned char channels;
if(indexed_png)
channels = 1;
else
channels = 3;
png_bytep row = (png_bytep)calloc(channels*sizeof(png_byte), full_width);
if(row==NULL) {
throw IOException("Can not allocate row memory in PNGIO!", AT);
}
// Write image data
for(int y=nrows-1 ; y>=0 ; y--) {
unsigned int x=0;
for(; x<ncols ; x++) {
const unsigned int i=x*channels;
unsigned char r,g,b;
bool a;
gradient.getColor(grid(x,y), r,g,b,a);
if(a==true) {
row[i]=transparent_grey; row[i+1]=transparent_grey; row[i+2]=transparent_grey;
} else {
row[i]=r; row[i+1]=g; row[i+2]=b;
if(indexed_png) {
for(int y=nrows-1 ; y>=0 ; y--) {
unsigned int x=0;
for(; x<ncols ; x++) {
const unsigned int i=x*channels;
unsigned int index;
gradient.getColor(grid(x,y), index);
row[i]=index;
}
for(; x<full_width; x++) {
const unsigned int i=x*channels;
unsigned int index;
gradient.getColor(legend_array(x-ncols,y), index);
row[i]=index;
}
png_write_row(png_ptr, row);
}
for(; x<full_width; x++) {
const unsigned int i=x*channels;
unsigned char r,g,b;
bool a;
gradient.getColor(legend_array(x-ncols,y), r,g,b,a);
if(a==true) {
row[i]=transparent_grey; row[i+1]=transparent_grey; row[i+2]=transparent_grey;
} else {
row[i]=r; row[i+1]=g; row[i+2]=b;
} else {
for(int y=nrows-1 ; y>=0 ; y--) {
unsigned int x=0;
for(; x<ncols ; x++) {
const unsigned int i=x*channels;
unsigned char r,g,b;
bool a;
gradient.getColor(grid(x,y), r,g,b,a);
if(a==true) {
row[i]=transparent_grey; row[i+1]=transparent_grey; row[i+2]=transparent_grey;
} else {
row[i]=r; row[i+1]=g; row[i+2]=b;
}
}
for(; x<full_width; x++) {
const unsigned int i=x*channels;
unsigned char r,g,b;
bool a;
gradient.getColor(legend_array(x-ncols,y), r,g,b,a);
if(a==true) {
row[i]=transparent_grey; row[i+1]=transparent_grey; row[i+2]=transparent_grey;
} else {
row[i]=r; row[i+1]=g; row[i+2]=b;
}
}
png_write_row(png_ptr, row);
}
png_write_row(png_ptr, row);
}
png_write_flush(png_ptr);
png_free(png_ptr, row);
}
void PNGIO::setPalette(const Gradient &gradient, png_structp& png_ptr, png_infop& info_ptr)
{
std::vector<unsigned char> r, g, b;
gradient.getPalette(r,g,b);
const size_t nr_colors = r.size();
png_color *palette = (png_color*)calloc(sizeof (png_color), nr_colors);
for(size_t ii=0; ii<nr_colors; ii++) {
palette[ii].red = r[ii];
palette[ii].green = g[ii];
palette[ii].blue = b[ii];
}
png_set_PLTE(png_ptr, info_ptr, palette, nr_colors);
}
void PNGIO::closePNG(png_structp& png_ptr, png_infop& info_ptr)
{
fclose(fp);
png_free_data(png_ptr, info_ptr, PNG_FREE_ALL, -1);
if(info_ptr->palette!=NULL) free(info_ptr->palette);
png_destroy_write_struct(&png_ptr, &info_ptr);
free(info_ptr);
free(png_ptr);
......@@ -365,7 +422,6 @@ void PNGIO::closePNG(png_structp& png_ptr, png_infop& info_ptr)
void PNGIO::write2DGrid(const Grid2DObject& grid_in, const std::string& filename)
{
string full_name = grid2dpath+"/"+filename;
//FILE *fp=NULL;
fp=NULL;
png_structp png_ptr=NULL;
png_infop info_ptr=NULL;
......@@ -377,12 +433,13 @@ void PNGIO::write2DGrid(const Grid2DObject& grid_in, const std::string& filename
const double max = grid.grid2D.getMax();
Gradient gradient(Gradient::heat, min, max, autoscale);
gradient.setNrOfLevels(50);
gradient.setNrOfLevels(nr_levels);
Array2D<double> legend_array; //it will remain empty if there is no legend
const unsigned int full_width = setLegend(ncols, nrows, min, max, legend_array);
setFile(full_name, png_ptr, info_ptr, full_width, nrows);
if(indexed_png) setPalette(gradient, png_ptr, info_ptr);
if(has_world_file) writeWorldFile(grid, full_name);
createMetadata(grid);
......@@ -404,7 +461,6 @@ void PNGIO::write2DGrid(const Grid2DObject& grid_in, const MeteoGrids::Parameter
else
filename = grid2dpath + "/" + date.toString(Date::NUM) + "_" + MeteoGrids::getParameterName(parameter) + ".png";
//FILE *fp=NULL;
fp=NULL;
png_structp png_ptr=NULL;
png_infop info_ptr=NULL;
......@@ -472,12 +528,13 @@ void PNGIO::write2DGrid(const Grid2DObject& grid_in, const MeteoGrids::Parameter
} else {
gradient.set(Gradient::heat, min, max, autoscale);
}
gradient.setNrOfLevels(30);
gradient.setNrOfLevels(nr_levels);
Array2D<double> legend_array; //it will remain empty if there is no legend
const unsigned int full_width = setLegend(ncols, nrows, min, max, legend_array);
setFile(filename, png_ptr, info_ptr, full_width, nrows);
if(indexed_png) setPalette(gradient, png_ptr, info_ptr);
if(has_world_file) writeWorldFile(grid, filename);
createMetadata(grid);
......@@ -493,11 +550,6 @@ void PNGIO::write2DGrid(const Grid2DObject& grid_in, const MeteoGrids::Parameter
png_write_end(png_ptr, NULL);
closePNG(png_ptr, info_ptr);
/*fclose(fp);
png_free_data(png_ptr, info_ptr, PNG_FREE_ALL, -1);
png_destroy_write_struct(&png_ptr, &info_ptr);
free(info_ptr);
free(png_ptr);*/
}
void PNGIO::writeWorldFile(const Grid2DObject& grid_in, const std::string& filename)
......
......@@ -73,6 +73,7 @@ class PNGIO : public IOInterface {
void writeWorldFile(const Grid2DObject& grid_in, const std::string& filename);
unsigned int setLegend(const unsigned int &ncols, const unsigned int &nrows, const double &min, const double &max, Array2D<double> &legend_array);
void writeDataSection(const Grid2DObject &grid, const Array2D<double> &legend_array, const Gradient &gradient, const unsigned int &full_width, const png_structp &png_ptr);
void setPalette(const Gradient &gradient, png_structp& png_ptr, png_infop& info_ptr);
void closePNG(png_structp& png_ptr, png_infop& info_ptr);
std::string decimal_to_dms(const double& decimal);
......@@ -93,6 +94,9 @@ class PNGIO : public IOInterface {
static const unsigned char channel_depth;
static const unsigned char channel_max_color;
static const unsigned char transparent_grey;
static const bool optimize_for_speed; ///< optimize for speed instead of compression?
static const bool indexed_png; ///< write an indexed png?
static const unsigned int nr_levels; ///< number of levels to represent? (less-> smaller file size and faster)
};
} //namespace
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment