// -*- C++ -*-
// $RCSfile: image.C,v $
// $Revision: 1.9 $
// $Author: langer $
// $Date: 2002/10/15 18:31:31 $
/* This software was produced by NIST, an agency of the U.S. government,
* and by statute is not subject to copyright in the United States.
* Recipients of this software assume all responsibilities associated
* with its operation, modification and maintenance. However, to
* facilitate maintenance we ask that before distributing modifed
* versions of this software, you first contact the authors at
* oof_manager@ctcms.nist.gov.
*/
#include "stdlib.h"
#include <stdio.h>
#include <string.h>
#include "ctype.h"
#include "array.h"
#include "charstring.h"
#include "colorutils.h"
#include "image.h"
#include "ppm2oof.h"
#include "readbinary.h"
Image::Image(bool gray)
: grayscale(gray)
{
}
Image::Image(int w, int h, bool gray)
: Array<Color>(h, w),
changed(1),
grayscale(gray)
{
}
Image::~Image() {}
//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//
// stuff for reading images from ppm files
// Return next white-space delimited word from file, ignoring
// everything between '#' and end of line. Return "" at end of file.
static char *nextword(istream &file) {
// have to read character by character so that binary data at the
// end of the header isn't accidentally read too soon.
char c;
static char buf[100];
char *ptr = buf;
while(file.get(c) && isspace(c)); // skip initial whitespace
if(!file) {
buf[0] = '\0';
return buf;
}
if(c == '#') { // skip comment
while(file.get(c) && c != '\n');
return nextword(file);
}
*ptr++ = c;
while(file.get(c) && !isspace(c) && c != '#') {
*ptr++ = c;
}
if(c == '#') // skip comment
while(file.get(c) && c != '\n');
*ptr = '\0'; // terminate the string
return buf;
}
int Image::read_image(istream &file) {
char filetype = '\0';
// find what kind of PPM/PBM/PGM file this is:
char *word = nextword(file);
if(word[0] == '\0') {
garcon()->msout << ms_error << "Can't read file!" << endl << ms_normal;
return 0;
}
if(word[0] != 'P') {
garcon()->msout << ms_error << "File is not a PPM/PGM/PBM file!"
<< endl << ms_normal;
return 0;
}
filetype = word[1];
// find size
int w = atoi(nextword(file));
int h = atoi(nextword(file));
if(h <= 0 || w <= 0) {
garcon()->msout << ms_error << "Invalid image size!" << endl
<< ms_normal;
return 0;
}
changed = 1;
resize(Cell_coordinate(w, h));
switch(filetype) {
case '1':
return read_pbm(file);
case '2':
return read_pgm(file);
case '3':
return read_ppm(file);
case '4':
return read_pbmraw(file);
case '5':
return read_pgmraw(file);
case '6':
return read_ppmraw(file);
default:
garcon()->msout << ms_error << "Unknown file type!" << endl
<< ms_normal;
return 0;
}
// not reached
}
int Image::read_ppmraw(istream &file) {
int maxpixval = atoi(nextword(file));
if(maxpixval == 0) {
garcon()->msout << ms_error << "Invalid maxpixval in raw ppm image file!"
<< endl << ms_normal;
return 0;
}
double scale = 1./maxpixval;
for(int i=0; i<height; i++)
for(int j=0; j<width; j++) {
unsigned char rgb[3];
file.read(reinterpret_cast<char*>(rgb), 3);
if(file.gcount() != 3) {
garcon()->msout << ms_error << "Incomplete raw ppm image file! "
<< endl << ms_normal;
return 0;
}
(*this)[Cell_coordinate(j, height-i-1)] =
Color(scale*rgb[0], scale*rgb[1], scale*rgb[2]);
}
return 1;
}
int Image::read_ppm(istream &file) {
int maxpixval = atoi(nextword(file));
if(maxpixval == 0) {
garcon()->msout << ms_error << "Invalid maxpixval in ppm image file!"
<< endl << ms_normal;
return 0;
}
double scale = 1./maxpixval;
for(int i=0; i<height; i++)
for(int j=0; j<width; j++) {
int r = atoi(nextword(file));
int g = atoi(nextword(file));
int b = atoi(nextword(file));
if(!file) {
garcon()->msout << ms_error << "Incomplete ppm image file!"
<< endl << ms_normal;
return 0;
}
(*this)[Cell_coordinate(j, height-i-1)] =
Color(scale*r, scale*g, scale*b);
}
return 1;
}
int Image::read_pgmraw(istream &file) {
int maxpixval = atoi(nextword(file));
if(maxpixval == 0) {
garcon()->msout << ms_error << "Invalid maxpixval in raw pgm image file!"
<< endl << ms_normal;
return 0;
}
double scale = 1./maxpixval;
for(int i=0; i<height; i++)
for(int j=0; j<width; j++) {
unsigned char gray;
file.read((char*)&gray, 1);
if(file.gcount() != 1) {
garcon()->msout << ms_error << "Incomplete raw pgm image file!"
<< endl << ms_normal;
return 0;
}
double g = scale*gray;
(*this)[Cell_coordinate(j, height-i-1)] = Color(g, g, g);
}
grayscale = 1;
return 1;
}
int Image::read_pgm(istream &file) {
int maxpixval = atoi(nextword(file));
if(maxpixval == 0) {
garcon()->msout << ms_error << "Invalid maxpixval in pgm image file!"
<< endl << ms_normal;
return 0;
}
double scale = 1./maxpixval;
for(int i=0; i<height; i++)
for(int j=0; j<width; j++) {
int g = atoi(nextword(file));
if(!file) {
garcon()->msout << ms_error << "Incomplete pgm image file!"
<< endl << ms_normal;
return 0;
}
double gg = scale*g;
(*this)[Cell_coordinate(j, height-i-1)] = Color(gg, gg, gg);
}
grayscale = 1;
return 1;
}
int Image::read_pbm(istream &file) {
for(int i=0; i<height; i++)
for(int j=0; j<width; j++) {
char c;
while(file.get(c) && isspace(c));
if(!file) {
garcon()->msout << ms_error << "Incomplete pbm image file!"
<< endl << ms_normal;
return 0;
}
if(c == '1')
(*this)[Cell_coordinate(j, height-i-1)] = Color(0.0, 0.0, 0.0);
else if(c == '0')
(*this)[Cell_coordinate(j, height-i-1)] = Color(1.0, 1.0, 1.0);
else {
garcon()->msout << ms_error << "Incomprehensible pbm file!"
<< endl << ms_normal;
return 0;
}
}
grayscale = 1;
return 1;
}
int Image::read_pbmraw(istream &file) {
Color black(0.0, 0.0, 0.0);
Color white(1.0, 1.0, 1.0);
#ifdef WRONG
/* This section, I think, does what the pbm manual says it should,
* but doesn't read files written by xv. It assumes that the images
* bits are stored sequentially, with no padding, even if the width
* isn't a multiple of 8.
*/
int nbytes = height*width/8;
if(height*width > 8*nbytes) nbytes++;
unsigned char *raster = new unsigned char[nbytes];
file.read(raster, nbytes);
if(file.gcount() != nbytes) {
garcon()->msout << ms_error << "Incomplete raw pbm file!"
<< endl << ms_normal;
delete [] raster;
return 0;
}
int i = 0;
int j = 0;
for(int k=0; k<nbytes; k++) { // loop over bytes
for(unsigned char mask = 1 << 7; mask != 0; mask >>= 1) { // loop over bits
(*this)[Cell_coordinate(j, height-i-1)] =
(mask & raster[k]) ? black : white;
j++;
if(j == width) { // go to next line of image
j = 0;
i++;
if(i == height) { // done!
delete [] raster;
return 1;
}
}
}
}
garcon()->msout << ms_error << "This shouldn't happen!" << endl << ms_normal;
delete [] raster;
grayscale = 1;
return 0;
#endif
/* This section assumes that each horizontal line is padded to
* contain an integer number of bytes. The pbm manual page doesn't
* say that lines should be padded, but that appears to be what xv does.
*/
int nbytes = width/8;
if(width > 8*nbytes) nbytes++;
unsigned char *raster = new unsigned char[nbytes];
for(int i=0; i<height; i++) {
// read a line
file.read((char*)raster, nbytes);
if(file.gcount() != nbytes) {
garcon()->msout << ms_error << "Incomplete raw pbm file!"
<< endl << ms_normal;
delete [] raster;
return 0;
}
int j = 0; // bit counter
for(int k=0; k<nbytes; k++) // loop over bytes
for(unsigned char mask = 1<<7; mask!=0 && j<width; mask >>= 1, j++)
(*this)[Cell_coordinate(j, height-i-1)] =
(mask & raster[k]) ? black : white;
}
delete [] raster;
grayscale = 1;
return 1;
}
//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//
void Image::save(const CharString &filename) const {
ofstream file(filename);
if(!file) {
garcon()->msout << ms_error << "Can't save image to \""
<< filename << "\"!" << endl << ms_normal;
return;
}
save(file);
}
void Image::save(ostream &file) const {
if(!grayscale) {
// write raw PPM format
file << "P6" << endl;
file << "# CREATOR: ppm2oof" << endl;
file << width << " " << height << endl;
file << "255" << endl;
for(int i=0; i<height; i++) {
for(int j=0; j<width; j++) {
file.put((*this)[Cell_coordinate(j, height-i-1)].query_red());
file.put((*this)[Cell_coordinate(j, height-i-1)].query_green());
file.put((*this)[Cell_coordinate(j, height-i-1)].query_blue());
}
}
}
else { // gray scale image
// write raw PGM format
file << "P5" << endl;
file << "# CREATOR: ppm2oof" << endl;
file << width << " " << height << endl;
file << "255" << endl;
for(int i=0; i<height; i++)
for(int j=0; j<width; j++)
file.put((*this)[Cell_coordinate(j, height-i-1)].query_red());
}
}
//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//
void Image::copy_image(const Image &initimage) {
changed = 1;
// resize(Cell_coordinate(initimage.query_width(), initimage.query_height()));
// ArrayIterator iter(*this);
// Cell_coordinate cell;
// while(iter(cell))
// (*this)[cell] = initimage[cell];
this->Array<Color>::operator=(initimage);
colortree = initimage.colortree;
grayscale = initimage.grayscale;
}
//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//
//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//
void Image::getpixels(int nreserved, Colormap colormap) {
if(!changed) return;
changed = 0;
ArrayIterator iter(*this);
Cell_coordinate cell;
colortree.clear();
while(iter(cell))
colortree.add(cell, (*this)[cell]);
if(gfxinfo.c_class() == PseudoColor) {
// reduce number of colors so that it will fit in colormap
if(restricted_colors != 0)
colortree.reduce(restricted_colors);
else
colortree.reduce(gfxinfo.colormap_size() - nreserved);
// Make a list of XColors
XColor *xcolor = new XColor[colortree.Nreducedcolors()];
int ncolors = 0; // number of colors being set
for(int i=0; i<colortree.Nreducedcolors(); i++) {
ColorTreeNode<Cell_coordinate> &node = colortree[i];
if(node.elected) {
xcolor[ncolors] = node.color.xcolor();
xcolor[ncolors].pixel = nreserved + ncolors;
xcolor[ncolors].flags = DoRed | DoGreen | DoBlue;
node.color.pixel = xcolor[ncolors].pixel;
ncolors++;
}
}
XStoreColors(gfxinfo.display(), colormap, xcolor, ncolors);
delete [] xcolor;
}
else if(gfxinfo.c_class() == TrueColor) {
if(restricted_colors != 0)
colortree.reduce(restricted_colors);
else
colortree.electall();
for(int i=0; i<colortree.Nreducedcolors(); i++) {
if(colortree[i].elected)
colortree[i].color.RO_pixel();
}
}
else {
cerr << "Image::getpixels --- bad X visual!\n" << endl;
exit(1);
}
// copy pixel values from colortree to image
for(int i=0; i<colortree.Ncolors(); i++) {
ColorTreeNode<Cell_coordinate> &node = colortree[i];
unsigned long pixel;
if(node.elected)
pixel = node.color.pixel;
else
pixel = colortree[node.nearest].color.pixel;
for(LinkListIterator<Cell_coordinate> j=node.list.begin();!j.end();++j)
(*this)[node.list[j]].pixel = pixel;
}
}
//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//=\\=//
// ostream &operator<<(ostream &os, const Image &image) {
// ArrayIterator iter(image);
// Cell_coordinate cell;
// while(iter(cell))
// os << cell << ": " << image[cell] << endl;
// return os;
// }
syntax highlighted by Code2HTML, v. 0.9.1