Go Back
#include <fstream.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
// Named after Russ Cox of Harvard; a little safety mechanism
// to prevent reaching beyond the limits of an array.
#define RUSS_FACTOR 5
// My header for all PPM outputs.
#define COMMENT "# CREATOR: TildeMan Version 3.14159a"
typedef struct image image;
struct image
{
// image variables: the names are self-explanatory.
char type[2+RUSS_FACTOR];
int width, height, colorscale;
int *rdata, *gdata, *bdata;
// Create an image from file.
void load(char *file)
{
int i, j, k;
unsigned char r, g, b;
// The dummy variables are for the purpose of wasting the header.
char dummy, dummyl[128];
ifstream infile(file, ios::in | ios::binary);
infile >> type;
while (infile.peek() <= 13) infile.get(dummy);
while (infile.peek() == '#') infile.getline(dummyl,128,'\n');
infile >> width >> height;
infile >> colorscale;
// Create a place to store the color data.
rdata = new int[width*height+RUSS_FACTOR];
gdata = new int[width*height+RUSS_FACTOR];
bdata = new int[width*height+RUSS_FACTOR];
for (j = 0; j < height; j++)
for (i = 0; i < width; i++)
{
k = pos(i,j);
// If P3, read in ASCII values.
if (type[1]=='3')
infile >> rdata[k]
>> gdata[k]
>> bdata[k];
// If P6, read in binary chars and convert to ASCII.
else
{
// Yes, I could just do infile >> b etc., but
// this is left over from when I was not opening
// the file as binary but wanted to include the
// hard return character as valid data.
b = infile.peek();
infile.ignore(1);
r = infile.peek();
infile.ignore(1);
g = infile.peek();
infile.ignore(1);
rdata[k] = int(r);
gdata[k] = int(g);
bdata[k] = int(b);
}
}
infile.close();
}
// Given a column and row number, return the absolute position
// of the pixel in the data arrays.
long pos(int w, int h)
{
return (width*long(h))+long(w);
}
// Filter 1: Negative image.
void negative()
{
int i, j;
long k;
for (i = 0; i < width; i++)
for (j = 0; j < height; j++)
{
k = pos(i,j);
// Find the corresponding negative attributes.
rdata[k] = colorscale-rdata[k];
gdata[k] = colorscale-gdata[k];
bdata[k] = colorscale-bdata[k];
}
}
// Filter 2: Simple Colors.
void simpleColor()
{
int i, j, mid = colorscale/2;
int quart1 = mid/2, quart2 = mid+quart1;
long k;
for (i = 0; i < width; i++)
for (j = 0; j < height; j++)
{
k = pos(i,j);
// For each attribute, reduce it to one of three values:
// if the value is in the first quarter of the range of
// possible values, set it to 0. If it's in the upper
// quarter, set it to the maximum value. Otherwise, set
// it to half the maximum value.
if (rdata[k] < quart1) rdata[k]=0;
else if (rdata[k] > quart2) rdata[k]=colorscale;
else rdata[k]=mid;
if (gdata[k] < quart1) gdata[k]=0;
else if (gdata[k] > quart2) gdata[k]=colorscale;
else gdata[k]=mid;
if (bdata[k] < quart1) bdata[k]=0;
else if (bdata[k] > quart2) bdata[k]=colorscale;
else bdata[k]=mid;
}
}
void getCol(int i, int j, int a, int b, int *vals, int &c)
{
// Helper function for blur. Blur wants an average of the
// pixels around a given one, so if point (i,j) is a valid
// point we add it to the running total and increment the
// variable (c) that counts the number of pixels added.
if (i < 0 || j < 0 || i >= width || j >= height)
return;
else
{
long k = pos(i,j);
vals[0] += rdata[k];
vals[1] += gdata[k];
vals[2] += bdata[k];
c++;
}
}
// Filter 3: Blur
void blur(int radius)
{
int a,b,c, i,j, vals[3];
long k;
// We'll assign the new data without overwriting the old values so
// they can be used for calculation all the way through. Then when
// we finish, we'll deallocate the old data and set the data
// pointers to these new arrays.
int *newr = new int[width*height+RUSS_FACTOR];
int *newg = new int[width*height+RUSS_FACTOR];
int *newb = new int[width*height+RUSS_FACTOR];
for (i = 0; i < width; i++)
for (j = 0; j < height; j++)
{
k = pos(i,j);
// Initialize the sum of the attributes to 0
vals[0]=vals[1]=vals[2]=c=0;
// and add all the values of the surrounding attributes.
for (a = -1*radius; a <= radius; a++)
for (b = -1*radius; b <= radius; b++)
getCol(i+a, j+b, i,j, vals, c);
// Divide our totals by the number of pixels used in the total
vals[0] /= c;
vals[1] /= c;
vals[2] /= c;
// and set the new pixel values to those averages.
newr[k] = vals[0];
newg[k] = vals[1];
newb[k] = vals[2];
}
// Deallocate and replace, as described above.
delete rdata;
rdata = newr;
delete gdata;
gdata = newg;
delete bdata;
bdata = newb;
}
// Filter 4: Smear
void smear(float sfactor)
{
int i,j,k;
double *newr = new double[width*height+RUSS_FACTOR];
double *newg = new double[width*height+RUSS_FACTOR];
double *newb = new double[width*height+RUSS_FACTOR];
// First smear left-to-right
for (i = 0; i < height; i++)
{
k = pos(0,i);
newr[k] = rdata[k];
newg[k] = gdata[k];
newb[k] = bdata[k];
for (j = 1; j < width; j++)
{
k++;
// The new value is the weighted average of the new value of
// the previous pixel and the old value of the current one.
// The weight is given by the parameter, and the lower that
// parameter is, the less the new pixel depends on the old one -
// that is, the more smeared it seems.
newr[k] = (newr[k-1]+sfactor*rdata[k])/(1+sfactor);
newg[k] = (newg[k-1]+sfactor*gdata[k])/(1+sfactor);
newb[k] = (newb[k-1]+sfactor*bdata[k])/(1+sfactor);
}
k = pos(0,i);
// Rewrite the data with our new calculated values.
for (j = 0; j < width; j++)
{
rdata[k] = int(newr[k]);
gdata[k] = int(newg[k]);
bdata[k] = int(newb[k]);
k++;
}
}
// Then smear right-to-left.
for (i = 0; i < height; i++)
{
k = pos(width-1,i);
newr[k] = rdata[k];
newg[k] = gdata[k];
newb[k] = bdata[k];
for (j = width-2; j >= 0; j--)
{
k--;
// Same stuff as above.
newr[k] = (newr[k+1]+sfactor*rdata[k])/(1+sfactor);
newg[k] = (newg[k+1]+sfactor*gdata[k])/(1+sfactor);
newb[k] = (newb[k+1]+sfactor*bdata[k])/(1+sfactor);
}
k = pos(width-1,i);
for (j = width-1; j >=0; j--)
{
rdata[k] = int(newr[k]);
gdata[k] = int(newg[k]);
bdata[k] = int(newb[k]);
k--;
}
}
// Delete the temporary storage arrays to free more memory.
delete newr;
delete newg;
delete newb;
}
int differ(int a, int b, float factor)
{
// A helper function for outline. If the ratio of the attributes
// for two pixels are out of a certain range, return 1/12 the
// maximum possible attribute value. Since this function is
// called 12 times per pixel, if a pixel is completely different
// from the four surrounding it the corresponding new pixel will
// be black. If it's the same as the others, it will be white, and
// otherwise it will be some intermediate shade of gray.
double ratio = double(a)/double(b);
if (ratio >= (1+factor) || ratio <= 1/(1+factor))
return (colorscale/12);
else return 0;
}
void outline(float dfactor)
{
int i, j, k;
int *bw = new int[width*height+RUSS_FACTOR];
// We only need one array; for grayscale images, all RGB components
// are the same. Initialize all values to 0 (black).
for (k = 0; k < width*height+RUSS_FACTOR; k++)
bw[k]=0;
for (i = 0; i < width; i++)
{
for (j = 0; j < height; j++)
{
k = pos(i,j);
// Check all three attributes to the left
if (i > 0)
{
bw[k] += differ(rdata[k],rdata[k-1],dfactor);
bw[k] += differ(gdata[k],gdata[k-1],dfactor);
bw[k] += differ(bdata[k],bdata[k-1],dfactor);
}
// and to the right
if (i < width-1)
{
bw[k] += differ(rdata[k],rdata[k+1],dfactor);
bw[k] += differ(gdata[k],gdata[k+1],dfactor);
bw[k] += differ(bdata[k],bdata[k+1],dfactor);
}
// and up one row
if (j > 0)
{
bw[k] += differ(rdata[k],rdata[k-width],dfactor);
bw[k] += differ(gdata[k],gdata[k-width],dfactor);
bw[k] += differ(bdata[k],bdata[k-width],dfactor);
}
// and down one row.
if (j < height-1)
{
bw[k] += differ(rdata[k],rdata[k+width],dfactor);
bw[k] += differ(gdata[k],gdata[k+width],dfactor);
bw[k] += differ(bdata[k],bdata[k+width],dfactor);
}
}
}
// Set all RGB components to the grayscale value.
for (k = 0; k < width*height; k++)
rdata[k] = gdata[k] = bdata[k] = (colorscale-bw[k]);
// Free unneeded memory.
delete bw;
}
// Print the iamge to a file.
void output(char *file)
{
int i, j;
long k;
ofstream outfile(file);
// Headers! It's a P3 image with the same width and height
// but a different header (the one specified at the top of
// this code). The colorscale is the same as well.
outfile << "P3" << endl;
outfile << COMMENT << endl;
outfile << width << " " << height << endl;
outfile << colorscale;
for (j = 0; j < height; j++)
for (i = 0; i < width; i++)
{
k = pos(i,j);
// 5 Pixels to a line (I don't know why either, but that
// seems to be a default).
if (k % 5 == 0) outfile << endl;
// Print 3 characters per attribute plus an additional
// whitespace at the end of the value.
if (rdata[k] < 100) outfile << " ";
if (rdata[k] < 10) outfile << " ";
outfile << rdata[k] << " ";
if (gdata[k] < 100) outfile << " ";
if (gdata[k] < 10) outfile << " ";
outfile << gdata[k] << " ";
if (bdata[k] < 100) outfile << " ";
if (bdata[k] < 10) outfile << " ";
outfile << bdata[k] << " ";
}
outfile << endl;
outfile.close();
}
};
int main(int argc, char **argv)
{
int param, bluriosity;
float smearness, dfactor;
char inp[50], outp[50];
image cow;
// If we're missing the parameter that says which filter to use,
// say that and give the list of possible filters.
if (argc < 2)
{
cout << "Error: Required parameter missing.\n"
<< " 0: Nothing (boring...)\n"
<< " 1: Negative\n"
<< " 2: Simple Colors\n"
<< " 3: Blur\n"
<< " 4: Smear\n"
<< " 5: Outline\n"
<< "\n";
return 0;
}
// Read the desired filter type.
sscanf(argv[1], "%d", ¶m);
// If it's a valid number, ask for input and output files.
if (param >= 0 && param <= 5)
{
cout << "Image to read from: ";
cin >> inp;
cout << "Image to write to: ";
cin >> outp;
cow.load(inp);
}
switch(param)
{
// Nothing; return the original image.
case 0:
break;
// Get the negative.
case 1:
cow.negative();
break;
// Simplify the colors.
case 2:
cow.simpleColor();
break;
// Blur the image
case 3:
// If the blur radius is not specified, say so and exit.
if (argc < 3)
{
cout << "Error: Blur radius missing\n\n";
return 0;
}
// If the blur radius is not a valid value, say so and exit.
sscanf(argv[2], "%d", &bluriosity);
if (bluriosity < 0)
{
cout << "Error: Blur radius must be a nonnegative integer.\n\n";
return 0;
}
cow.blur(bluriosity);
break;
// Smear the image.
case 4:
// If the smear factor is missing, say so and exit.
if (argc < 3)
{
cout << "Error: Smear factor missing\n\n";
return 0;
}
// If the smear factor is not valid, say so and exit.
sscanf(argv[2], "%fd", &smearness);
if (smearness <= 0 || smearness >= 1)
{
cout << "Error: Smear factor must be between 0 and 1, exclusive.\n\n";
return 0;
}
cow.smear(smearness);
break;
// Get a shaded pencil-ish outline of the image.
case 5:
// If the sensitivity is missing, say so and exit.
if (argc < 3)
{
cout << "Error: Pixel color ratio missing\n\n";
return 0;
}
sscanf(argv[2], "%fd", &dfactor);
cow.outline(dfactor);
break;
// If the desired filter does not exit, say so, give the list
// of possible choices, and exit.
default:
cout << "Error: Bad parameter. Possible options are:\n"
<< " 0: Nothing (boring...)\n"
<< " 1: Negative\n"
<< " 2: Simple Colors\n"
<< " 3: Blur\n"
<< " 4: Smear\n"
<< " 5: Outline\n"
<< "\n";
return 0;
}
// Print the filtered image to the output file.
cow.output(outp);
cout << endl;
return 0;
}