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;
}

My Supercomp front page.
This page was created by Gary Sivek for 7th Period Supercomp.