Image of UCI’s Engineering Hall building gone through the edge-detection filter.

Image Manipulation: PhotoLab

Image of UCI’s Engineering Hall building gone through the edge-detection filter.

Image Manipulation: PhotoLab

This project is split into two sub-projects: PhotoLab and MovieLab. Each project uses similar image processing techniques and is utilized in the traditional manner: the command line.

The main purpose of each project was to learn how to effectively implement C programming, utilize debugging tools like GDB and Valgrind, learn the core foundations of creating and building using GCC and make, learn some simple software algorithms, and learn data structures. No GUI was made for any project because the focus of each project was to focus on the core foundations of C.

PhotoLab’s goal is to be able to apply various photo filters on a given .ppm image, e.g., image rotation, cropping, edge-detection, and posterizing. This was the end goal, however building to it would require knowledge of how to use employ GCC, GNU Make and makefiles, and compilation libraries.

PhotoLab

Base image. A photo of UC Irvine’s Engineering Hall Building.

Once properly built from the source code using make all, usage of PhotoLab (with the debugging features on) appears like the following:

Script started on Thu 17 Nov 2016 04:53:53 PM PST
[atec@crystalcove hw4]$ make all

gcc -Wall -ansi -c PhotoLab.c -o PhotoLab.o
gcc -Wall -ansi -c FileIO.c -o FileIO.o
gcc -Wall -ansi -c Image.c -o Image.o
gcc -Wall -ansi -c Test.c -o Test.o
ar rc libFileIO.a FileIO.o Image.o Test.o
ranlib libFileIO.a 
gcc -Wall -ansi -c DIPs.c -o DIPs.o
gcc -Wall -ansi -c Advanced.c -o Advanced.o
ar rc libFilter.a DIPs.o Advanced.o 
ranlib libFilter.a 
gcc PhotoLab.o  -L. -lFileIO -lFilter -o PhotoLab
gcc -g -Wall -ansi -c Test.c -o DTest.o -DDEBUG
gcc -g -Wall -ansi -c FileIO.c -o DFileIO.o -DDEBUG
gcc -g -Wall -ansi -c DIPs.c -o DDIPs.o -DDEBUG
gcc -g -Wall -ansi -c Advanced.c -o DAdvanced.o -DDEBUG
gcc -g -Wall -ansi -c Image.c -o DImage.o -DDEBUG
gcc -g -Wall -ansi -c PhotoLab.c -o DPhotoLab.o -DDEBUG
gcc -g -Wall DPhotoLab.o DTest.o DFileIO.o DDIPs.o DAdvanced.o DImage.o -o PhotoLabTest

atec@crystalcove hw4]$ ./PhotoLab


--------------------------------
 1:  Load a PPM image
 2:  Save an image in PPM and JPEG format
 3:  Make a negative of an image
 4:  Color filter an image
 5:  Sketch the edge of an image
 6:  Flip an image horizontally
 7:  Mirror an image vertically
 8:  Zoom an image
 9:  Add noise to an image
10:  Shuffle an image
11:  Posterize an image
12:  Overlay
13:  Rotate clockwise
14:  Crop
15:  Resize
16:  MetalCircle
17:  Test all functions
18:  Exit
please make your choice: 17

--------------------------------
 1:  Load a PPM image
 2:  Save an image in PPM and JPEG format
 3:  Make a negative of an image
 4:  Color filter an image
 5:  Sketch the edge of an image
 6:  Flip an image horizontally
 7:  Mirror an image vertically
 8:  Zoom an image
 9:  Add noise to an image
10:  Shuffle an image
11:  Posterize an image
12:  Overlay
13:  Rotate clockwise
14:  Crop
15:  Resize
16:  MetalCircle
17:  Test all functions
18:  Exit
please make your choice: 18
[atec@crystalcove hw4]$ valgrind --leak-check=full ./PhotoLabTest

==26654== Memcheck, a memory error detector
==26654== Copyright (C) 2002-2012, and GNU GPLd, by Julian Seward et al.
==26654== Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info
==26654== Command: ./PhotoLabTest
==26654== 
EH.ppm was read successfully!
negative.ppm was saved successfully. 
negative.jpg was stored for viewing. 
Negative tested!

EH.ppm was read successfully!
colorfilter.ppm was saved successfully. 
colorfilter.jpg was stored for viewing. 
Color Filter tested!

EH.ppm was read successfully!
edge.ppm was saved successfully. 
edge.jpg was stored for viewing. 
Edge Detection tested!

EH.ppm was read successfully!
hflip.ppm was saved successfully. 
hflip.jpg was stored for viewing. 
HFlip tested!

EH.ppm was read successfully!
Vmirror.ppm was saved successfully. 
Vmirror.jpg was stored for viewing. 
Vmirror tested!

EH.ppm was read successfully!
zoom.ppm was saved successfully. 
zoom.jpg was stored for viewing. 
Zoom tested!

EH.ppm was read successfully!
noise.ppm was saved successfully. 
noise.jpg was stored for viewing. 
AddNoise tested!

EH.ppm was read successfully!
shuffle.ppm was saved successfully. 
shuffle.jpg was stored for viewing. 
Shuffle tested!

EH.ppm was read successfully!
posterize.ppm was saved successfully. 
posterize.jpg was stored for viewing. 
Posterize tested!

EH.ppm was read successfully!
balloon.ppm was read successfully!
overlay.ppm was saved successfully. 
overlay.jpg was stored for viewing. 
Overlay tested!

EH.ppm was read successfully!
rotate.ppm was saved successfully. 
rotate.jpg was stored for viewing. 
Rotate tested!

EH.ppm was read successfully!
crop.ppm was saved successfully. 
crop.jpg was stored for viewing. 
Crop tested!

EH.ppm was read successfully!
smallresize.ppm was saved successfully. 
smallresize.jpg was stored for viewing. 
EH.ppm was read successfully!
==26654== Conditional jump or move depends on uninitialised value(s)
==26654==    at 0x3394A7228B: _IO_file_overflow@@GLIBC_2.2.5 (fileops.c:872)
==26654==    by 0x3394A6E766: fputc (fputc.c:39)
==26654==    by 0x4018E6: SaveImage (FileIO.c:142)
==26654==    by 0x4012F9: AutoTest (Test.c:176)
==26654==    by 0x400A2C: main (PhotoLab.c:25)
==26654== 
==26654== Syscall param write(buf) points to uninitialised byte(s)
==26654==    at 0x3394ADB6D0: __write_nocancel (syscall-template.S:82)
==26654==    by 0x3394A71A92: _IO_file_write@@GLIBC_2.2.5 (fileops.c:1268)
==26654==    by 0x3394A73044: _IO_do_write@@GLIBC_2.2.5 (fileops.c:522)
==26654==    by 0x3394A72442: _IO_file_overflow@@GLIBC_2.2.5 (fileops.c:876)
==26654==    by 0x3394A6E766: fputc (fputc.c:39)
==26654==    by 0x4018E6: SaveImage (FileIO.c:142)
==26654==    by 0x4012F9: AutoTest (Test.c:176)
==26654==    by 0x400A2C: main (PhotoLab.c:25)
==26654==  Address 0x4c0e016 is not stackd, mallocd or (recently) freed
==26654== 
==26654== Conditional jump or move depends on uninitialised value(s)
==26654==    at 0x3394A722B3: _IO_file_overflow@@GLIBC_2.2.5 (fileops.c:879)
==26654==    by 0x3394A6E766: fputc (fputc.c:39)
==26654==    by 0x4018E6: SaveImage (FileIO.c:142)
==26654==    by 0x4012F9: AutoTest (Test.c:176)
==26654==    by 0x400A2C: main (PhotoLab.c:25)
==26654== 
==26654== Conditional jump or move depends on uninitialised value(s)
==26654==    at 0x3394A7228B: _IO_file_overflow@@GLIBC_2.2.5 (fileops.c:872)
==26654==    by 0x3394A6E766: fputc (fputc.c:39)
==26654==    by 0x40190E: SaveImage (FileIO.c:143)
==26654==    by 0x4012F9: AutoTest (Test.c:176)
==26654==    by 0x400A2C: main (PhotoLab.c:25)
==26654== 
==26654== Conditional jump or move depends on uninitialised value(s)
==26654==    at 0x3394A722B3: _IO_file_overflow@@GLIBC_2.2.5 (fileops.c:879)
==26654==    by 0x3394A6E766: fputc (fputc.c:39)
==26654==    by 0x40190E: SaveImage (FileIO.c:143)
==26654==    by 0x4012F9: AutoTest (Test.c:176)
==26654==    by 0x400A2C: main (PhotoLab.c:25)
==26654== 
==26654== Conditional jump or move depends on uninitialised value(s)
==26654==    at 0x3394A7228B: _IO_file_overflow@@GLIBC_2.2.5 (fileops.c:872)
==26654==    by 0x3394A6E766: fputc (fputc.c:39)
==26654==    by 0x401936: SaveImage (FileIO.c:144)
==26654==    by 0x4012F9: AutoTest (Test.c:176)
==26654==    by 0x400A2C: main (PhotoLab.c:25)
==26654== 
==26654== Conditional jump or move depends on uninitialised value(s)
==26654==    at 0x3394A722B3: _IO_file_overflow@@GLIBC_2.2.5 (fileops.c:879)
==26654==    by 0x3394A6E766: fputc (fputc.c:39)
==26654==    by 0x401936: SaveImage (FileIO.c:144)
==26654==    by 0x4012F9: AutoTest (Test.c:176)
==26654==    by 0x400A2C: main (PhotoLab.c:25)
==26654== 
==26654== Syscall param write(buf) points to uninitialised byte(s)
==26654==    at 0x3394ADB6D0: __write_nocancel (syscall-template.S:82)
==26654==    by 0x3394A71A92: _IO_file_write@@GLIBC_2.2.5 (fileops.c:1268)
==26654==    by 0x3394A73044: _IO_do_write@@GLIBC_2.2.5 (fileops.c:522)
==26654==    by 0x3394A7281F: _IO_file_close_it@@GLIBC_2.2.5 (fileops.c:169)
==26654==    by 0x3394A664B7: fclose@@GLIBC_2.2.5 (iofclose.c:62)
==26654==    by 0x401986: SaveImage (FileIO.c:153)
==26654==    by 0x4012F9: AutoTest (Test.c:176)
==26654==    by 0x400A2C: main (PhotoLab.c:25)
==26654==  Address 0x4c0e000 is not stackd, mallocd or (recently) freed
==26654== 
bigresize.ppm was saved successfully. 
bigresize.jpg was stored for viewing. 
Resize tested!

EH.ppm was read successfully!
circle.ppm was saved successfully. 
circle.jpg was stored for viewing. 
MetalCircle tested!

==26654== 
==26654== HEAP SUMMARY:
==26654==     in use at exit: 0 bytes in 0 blocks
==26654==   total heap usage: 123 allocs, 123 frees, 20,852,756 bytes allocated
==26654== 
==26654== All heap blocks were freed -- no leaks are possible
==26654== 
==26654== For counts of detected and suppressed errors, rerun with: -v
==26654== Use --track-origins=yes to see where uninitialised values come from
==26654== ERROR SUMMARY: 95 errors from 8 contexts (suppressed: 6 from 6)

atec@crystalcove hw4]$ make clean

rm -f *.o
rm -f *.a
rm -f PhotoLab
rm -f PhotoLabTest
rm -f  negative.ppm  hflip.ppm  colorfilter.ppm  vmirror.ppm  edge.ppm  zoom.ppm  posterize.ppm  overlay.ppm  crop.ppm  smallresize.ppm  bigresize.ppm  rotate.ppm  circle.ppm  border.ppm  noise.ppm  shuffle.ppm  watermark.ppm
[atec@crystalcove hw4]$ exit

exit

Script done on Thu 17 Nov 2016 04:55:21 PM PST

The CLI tool is friendly and simple to use, although very limited in terms of features.

For the run shown above, the image, EH.png was already loaded, so option 1. Load a PPM image was not needed. If you follow the instructions shown to the user, however, you can apply the filters to any given .ppm image. Though no extensive UUT testing was done.

The output of a successful build and execution would result in the following:

Negative Filter Colorize Filter
Horizontal Flip Mirror Vertically
Zoom (Enlarge) Apply Noise
Puzzle Shuffle Posterize
Overlay an Image Rotate
Crop Image Resize (small)
Edge Detection

Creating the “UI”

The generated UI is a bit rudimentary for 2016 but figured it was worth learning and implementing. If you take a look at the source code you will find that each input choice is handled by a large switch statement located in PhotoLab.c. The program takes in a user input with scanf() and switches the logic depending on the user’s choice.

PrintMenu();
printf("please make your choice: ");
scanf("%d", &choice);

while (choice != 18) {
switch (choice) {
case 1:
  printf("Please input the file name to load: ");
  scanf("%s", fname);
  image = LoadImage(fname);
  if (image) FileNotRead = 0;
  break;
case 2:
  if (FileNotRead != 0) {
    printf("No Image Read Yet !!\n");
  } else {
    printf("Please input the file name to save: ");
    scanf("%s", fname);
    SaveImage(fname, image);
  }
  break;
case 3:
  if (FileNotRead != 0) {
    printf("No Image Read Yet !!\n");
  } else {
    Negative(image);
    printf("\"Negative\" operation is done!\n");
  }
  break;
...

Nothing crazy. The program calls out PrintMenu() to display the options to the user. It looks like this:

void PrintMenu() {
printf("\n--------------------------------\n");
printf(" 1:  Load a PPM image\n");
printf(" 2:  Save an image in PPM and JPEG format\n");
printf(" 3:  Make a negative of an image\n");
printf(" 4:  Color filter an image\n");
printf(" 5:  Sketch the edge of an image\n");
printf(" 6:  Flip an image horizontally\n");
printf(" 7:  Mirror an image vertically\n");
printf(" 8:  Zoom an image\n");
printf(" 9:  Add noise to an image\n");
printf("10:  Shuffle an image\n");
printf("11:  Posterize an image\n");
printf("12:  Overlay\n");
printf("13:  Rotate clockwise\n");
printf("14:  Crop\n");
printf("15:  Resize\n");
printf("16:  MetalCircle\n");
printf("17:  Test all functions\n");
printf("18:  Exit\n");
}

The magic of this project doesn’t happen here though, it all happens in DIPs.c and Advanced.c

Manipulating The Image

.ppm images, or Netpbm format, organizes image data using a pixel’s intensity (R, G, and B) coupled with its (x,y) position of that pixel. This creates a sort of a nested 2-D array, i.e. 3-D array.

MathWorks has a great representation of this:

RBG Array

With .ppm being relatively simple to manipulate, the program parses the image data and store it into R, G, and B variables/pointers.

Traversing Each Image

One common thing you will see in each filter is a nested for-loop. This loop allows traversal, or iteration, through the image’s entire height and width.

for( y = 0; y < HEIGHT; y ++ ) {
  for( x = 0; x < WIDTH; x ++ ) {
      ...
  }
}

Of course, with HEIGHT and WIDTH being functional arguments.

The next problem to undertake is how to allow the program to organize and handle the image parameters or data.

Data Organization

Within each loop a callout occurs for a function called SetPixel<R,G,B> or GetPixel<R,G,B>. They take in arguments that allow the manipulation the class structure called IMAGE found in Image.h by mapping this into a single-dimensional array value. The structure now has the information needed to efficiently manipulate an image; pixel intensity and position.

typedef struct {
	unsigned int Width;	/* image width */
	unsigned int Height;/* image height */
	unsigned char *R;	/* pointer to the memory storing all the R intensity values */
	unsigned char *G;	/* pointer to the memory storing all the G intensity values */
	unsigned char *B;	/* pointer to the memory storing all the B intensity values */
} IMAGE;
Note that there is no information on pixel position stored within this structure.

2-Dimensional Mapping to 1-Dimensional Mapping

Being able to process and handle images via pointers is ideal, if not required because it is the most efficient method. In order to implement this method the program has to essentially map a 2-D image $[WIDTH, HEIGHT]$ to a single-dimensional location, i.e. a location in memory.

$$\left( \begin{array}{ccc} a_{00} & a_{01} \\
a_{10} & a_{11} \end{array} \right) \rightarrow \left( \begin{array}{ccc} a_{00} & a_{01} & a_{10} & a_{11} \end{array} \right) $$

To map between the two spaces, the decided “transfer matrix” equation was set to the following:

$$ x + y * WIDTH $$

WIDTH was used because of how the C language naturally processes data: in Row-Major Order.

This implementation can be found in the source code under Image.c.

...
/* Set the R intensity of pixel (x, y) in image to r */
void SetPixelR(IMAGE *image, unsigned int x,  unsigned int y, unsigned char r){

	assert(x <= image -> Width);
	assert(y <= image -> Height);
	assert(image);
	assert(image -> R);
	assert(image -> G);
	assert(image -> B);

	image->R[x + y * (image -> Width)] = r;
}

/* Set the G intensity of pixel (x, y) in image to g */
void SetPixelG(IMAGE *image, unsigned int x,  unsigned int y, unsigned char g){

	assert(x <= image -> Width);
	assert(y <= image -> Height);
	assert(image);
	assert(image -> R);
	assert(image -> G);
	assert(image -> B);

	image->G[x + y * (image -> Width)] = g;
}
...

The Filters

Once data organization has been handled and methods to process said data is in place, the final tasks are to then implement the filters.

Negative Filter

It’s well known how to achieve the negative of an image. Each color is on a 0-255 pixel intensity scale, with 255 being the “brightest” and 0 being the “darkest”. So, to achieve image inversion the program subtracts each pixel’s intensity value from the max intensity value.

for( y = 0; y < HEIGHT; y ++ ) {
  for( x = 0; x < WIDTH; x ++ ) {
    SetPixelR( image, x, y,  (255 - GetPixelR( image, x, y)) );
    SetPixelG( image, x, y,  (255 - GetPixelG( image, x, y)) );
    SetPixelB( image, x, y,  (255 - GetPixelB( image, x, y)) );
  }
}

Color Filter

Color filter is done by specifying the target color in RBG format and replacing it with another specified color. Thresholds were also needed to be set in place.

for (y = 0; y < HEIGHT; y++) {
  for (x = 0; x < WIDTH; x++) {

    if (GetPixelR(image, x, y) >= (target_r - threshold) && GetPixelR(image, x, y) <= (target_r + threshold)) {
      if (GetPixelG(image, x, y) >= (target_g - threshold) && GetPixelG(image, x, y) <= (target_g + threshold)) {
        if (GetPixelB(image, x, y) >= (target_b - threshold) && GetPixelB(image, x, y) <= (target_b + threshold)) {

          SetPixelR( image, x, y,  replace_r );
          SetPixelG( image, x, y,  replace_g );
          SetPixelB( image, x, y,  replace_b );

        }
      }
    }

    else{
      SetPixelR(image, x, y, GetPixelR(image, x, y));
      SetPixelG(image, x, y, GetPixelG(image, x, y));
      SetPixelB(image, x, y, GetPixelB(image, x, y));
    }

  }
}

Edge Detection

This is a very rudamentary version of edge detection–meaning the solution approached the trivial problem: the algorithm seeks out large differences in pixel intensity and replaces it with either black or white. Also, instead of dealing directly with the edge cases (borders) and determining what the colors should be the edges were replaced with the black color.

To determine which areas to check, an 8x8 masking array is used to determine the value of a pixel and then a simple summation is done to determine pixel intensity.

$ (-A-B-C-D+8*E-F-G-H-I) $

To ensure no overflow or underflow, a simple IF-ELSE to limit values between 0 and 255 is used (not shown).


	IMAGE* image_temp = NULL;

    int             x, y, m, n, a, b;
    int             tmpR = 0;
    int             tmpG = 0;
    int             tmpB = 0;
	unsigned int HEIGHT, WIDTH;

	HEIGHT = image->Height;
	WIDTH = image->Width;

	image_temp = CreateImage(WIDTH, HEIGHT);

	 for (y = 0; y < HEIGHT; y++){
	        for (x = 0; x < WIDTH; x++) {
	        	SetPixelR(image_temp, x, y, GetPixelR(image, x, y));
	        	SetPixelG(image_temp, x, y, GetPixelG(image, x, y));
	        	SetPixelB(image_temp, x, y, GetPixelB(image, x, y));
	        }
	    }

	    for (y = 1; y < HEIGHT - 1; y++){
	        for (x = 1; x < WIDTH - 1; x++){
	            for (n = -1; n <= 1; n++){
	                for (m = -1; m <= 1; m++) {
	                    a = x + m;
	                    b = y + n;
	                    if (a > WIDTH - 1)
	                        a = WIDTH - 1;
	                    if (a < 0)
	                        a = 0;
	                    if (b > HEIGHT - 1)
	                        b = HEIGHT - 1;
	                    if (b < 0)
	                        b = 0;
	                    if ((n==0)&&(m==0)) {
	                        tmpR += 8*GetPixelR(image_temp, a, b);
	                        tmpG += 8*GetPixelG(image_temp, a, b) ;
	                        tmpB += 8*GetPixelB(image_temp, a, b) ;
	                    }
	                    else {
	                        tmpR -= GetPixelR(image_temp, a, b) ;
	                        tmpG -= GetPixelG(image_temp, a, b) ;
	                        tmpB -= GetPixelB(image_temp, a, b) ;
	                    }
	                }
	            }
	            SetPixelR(image, x, y, (tmpR>MAX_PIXEL)? MAX_PIXEL: (tmpR<0)? 0: tmpR);
	            SetPixelG(image, x, y, (tmpG>MAX_PIXEL)? MAX_PIXEL: (tmpG<0)? 0: tmpG);
	            SetPixelB(image, x, y, (tmpB>MAX_PIXEL)? MAX_PIXEL: (tmpB<0)? 0: tmpB);
	            tmpR = tmpG = tmpB = 0;
	        }
	    }
	/*avoid edge border problems*/
	/*top and bottom*/
	for(x = 0; x < WIDTH; x++ ) {
		SetPixelR(image_temp, x, 0, 0);
		SetPixelG(image_temp, x, 0, 0);
		SetPixelB(image_temp, x, 0, 0);

		SetPixelR(image_temp, x, HEIGHT-1, 0);
		SetPixelG(image_temp, x, HEIGHT-1, 0);
		SetPixelB(image_temp, x, HEIGHT-1, 0);
	}

	/*left and right*/
	for(y = 0; y < HEIGHT; y++ ) {
		SetPixelR(image_temp, 0, y, 0);
		SetPixelG(image_temp, 0, y, 0);
		SetPixelB(image_temp, 0, y, 0);

		SetPixelR(image_temp, WIDTH-1, y, 0);
		SetPixelG(image_temp, WIDTH-1, y, 0);
		SetPixelB(image_temp, WIDTH-1, y, 0);
	}


	DeleteImage(image_temp);
	image_temp = NULL;

Horizontal Flip

This algorithm is trivial: simply swamp pixel position. However the image must be set into a temporary array, otherwise when replacing already replaced pixels the image would result in a mirror image.

/* HFlip */
IMAGE *HFlip(IMAGE *image)
{
	assert(image);

	int x, y;
	unsigned char r, g, b;
	unsigned int HEIGHT, WIDTH;

	HEIGHT = image->Height;
	WIDTH = image->Width;

	for (y = 0; y < HEIGHT; y ++) {
		for (x = 0; x < WIDTH / 2; x ++) {

			r = GetPixelR(image, WIDTH - 1 - x, y);
			g = GetPixelG(image, WIDTH - 1 - x, y);
			b = GetPixelB(image, WIDTH - 1 - x, y);

			SetPixelR(image, WIDTH - 1 - x, y, GetPixelR(image, x, y) );
			SetPixelG(image, WIDTH - 1 - x, y, GetPixelG(image, x, y) );
			SetPixelB(image, WIDTH - 1 - x, y, GetPixelB(image, x, y) );

			SetPixelR(image, x, y, r);
			SetPixelG(image, x, y, g);
			SetPixelB(image, x, y, b);
		}
	}

	return image;
}

Mirroring

Swap the pixel position and don’t worry about already replaced pixels.

/* VMirror */
IMAGE *Vmirror(IMAGE *image)
{
	assert(image);

	int x, y;
	unsigned int HEIGHT, WIDTH;

	HEIGHT = image->Height;
	WIDTH = image->Width;

	for (y = 0; y < HEIGHT / 2; y ++) {
		for (x = 0; x < WIDTH; x ++) {
			SetPixelR(image, x, HEIGHT - 1 - y, GetPixelR(image, x, y) );
			SetPixelG(image, x, HEIGHT - 1 - y, GetPixelG(image, x, y) );
			SetPixelB(image, x, HEIGHT - 1 - y, GetPixelB(image, x, y) );
		}
	}

	return image;
}

Zoom

A zoom factor is needed to be used in order to zoom the image and only zooms into the center of the image.

The algorithm takes a fraction of the image size as an offset to ensure we are indexing from the center of the image, creates a temporary image, replaces the image pixels with a fraction of its pixel intensity (dependant on the zoom factor), and then replaces the image with the new pixel intensities.

/* Zoom */
IMAGE *Zoom(IMAGE *image)
{
	assert(image);

	IMAGE *image_temp = NULL;

    int x, y;
	unsigned int HEIGHT, WIDTH;
	HEIGHT = image->Height;
	WIDTH = image->Width;

    const int X_OFFSET = WIDTH / 4;
    const int Y_OFFSET = HEIGHT / 4;


	image_temp = CreateImage(WIDTH, HEIGHT);

    for (y = 0; y < HEIGHT; y++) {
        for (x = 0; x < WIDTH; x++) {
        	SetPixelR(image_temp, x, y, GetPixelR(image, x, y) );
        	SetPixelG(image_temp, x, y, GetPixelG(image, x, y) );
        	SetPixelB(image_temp, x, y, GetPixelB(image, x, y) );
        }
    }

    for (y = 0; y < HEIGHT; y++) {
        for (x = 0; x < WIDTH; x++) {
        	SetPixelR(image, x, y, GetPixelR(image_temp, x / ZOOM_FACTOR + X_OFFSET, y / ZOOM_FACTOR + Y_OFFSET) );
        	SetPixelG(image, x, y, GetPixelG(image_temp, x / ZOOM_FACTOR + X_OFFSET, y / ZOOM_FACTOR + Y_OFFSET) );
        	SetPixelB(image, x, y, GetPixelB(image_temp, x / ZOOM_FACTOR + X_OFFSET, y / ZOOM_FACTOR + Y_OFFSET) );
        }
    }

	DeleteImage(image_temp);
	image_temp = NULL;
	return image;
}

What is essentially happening is the algorithm is inserting values between pixels into the photo. The pixels are varied in intensity caused by the ZOOM_FACTOR and the offset.

Original       Insert New Pixels         Ensure center index             New Photo
-----------    ----------------------    ---------------------------     ------------
20 30 40 50 -> 20 25 30 35 40 45 50 55 -> 20 25 | 30 35 40 45 | 50 55 -> 30 35 40 45
60 70 80 90 -> 60 65 70 75 80 85 90 95 -> 60 65 | 70 75 80 85 | 90 95 -> 70 75 80 85

Adding Noise

White pixels are set in random places of the image. The Modulo Operator % is important, otherwise the algorithm will index outside the image and cause a seg fault.

IMAGE *AddNoise(IMAGE *image, int n){

	assert(image);

	int x;
	int y;
	int counter;
	unsigned int HEIGHT, WIDTH;

	HEIGHT = image->Height;
	WIDTH = image->Width;

	int limit = (n * WIDTH * HEIGHT ) / 100;

	/* initialize the random number generator with the current time */
	srand(time(NULL));

	for(counter = 0; counter < limit; counter++){
		  /* generate a random pixel */
			x = rand() % WIDTH;
			y = rand() % HEIGHT;

			SetPixelR(image, x, y, 255);
			SetPixelG(image, x, y, 255);
			SetPixelB(image, x, y, 255);

		}

	return image;
}

Puzzling / Shuffling

The image is sliced into a 5x5 collage, effectively making 25 slices of the original image. The difficult part was determining the proper “random” shuffling. The Fischer-Yates shuffle was identified to be a simple solution to the problem. The algorithm essentially starts off at the top of the index, e.g., the 25th photo slice and swaps position with any of the other 24 slices. The next iteration the 24th position swaps with any other of the 23 slices, etc….

Once indexes were properly generated, it was used with an offset to begin copying over parts of original image to the image_output.

IMAGE *Shuffle(IMAGE *image){

	assert(image);
	IMAGE* image_output = NULL;

	int shuffle[25];
	int target_x = 0;
	int target_y =0;
	int target_off_x = 0;
	int target_off_y = 0;
	int from_off_x = 0;
	int from_off_y = 0;;
	int i, x, y, nIndex, temp;
	unsigned int HEIGHT, WIDTH;

	HEIGHT = image->Height;
	WIDTH = image->Width;

	const int X_SLICE = WIDTH/5;
	const int Y_SLICE = HEIGHT/5;

	image_output = CreateImage(WIDTH, HEIGHT);

	/* initialize the random number generator with the current time */
	srand(time(NULL));

	/* initialize from 0 to 24 */
	for(i = 0; i < 25; i++){
		shuffle[i] = i;
	}


	/* shuffle: Fisher-Yates */
	for(i = 24; i > 0; i--){
		nIndex = rand() % i;
		temp = shuffle[i];
		shuffle[i] = shuffle[nIndex];
		shuffle[nIndex] = temp;
	}

	/* assign shuffle boxes to sR,sB,sG */
	for(i = 0; i < 25; i++){

		/* identify cell number */
		from_off_x = (i % 5) * X_SLICE;
		from_off_y = (i / 5) * Y_SLICE;

		target_x = shuffle[i] % 5;
		target_y = shuffle[i] / 5;

		target_off_x = target_x * X_SLICE;
		target_off_y = target_y * Y_SLICE;

		for(y = 0; y < Y_SLICE; y++){
			for(x = 0; x < X_SLICE; x++){
				SetPixelR(image_output, x + target_off_x, y + target_off_y, GetPixelR(image, x + from_off_x, y + from_off_y) );
				SetPixelG(image_output, x + target_off_x, y + target_off_y, GetPixelG(image, x + from_off_x, y + from_off_y) );
				SetPixelB(image_output, x + target_off_x, y + target_off_y, GetPixelB(image, x + from_off_x, y + from_off_y) );

			}
		}

	}

	DeleteImage(image);
	image = NULL;
	return image_output;
}

Posterize

To posterize an image means to essentially reduce the number of colors available to display. One way to do this is by bit manipulation.

The algorithm essentially bit-shifts each image’s pixels to zero-out the available bits used to display a pixel, i.e., 5-bit image ,4-bit image, etc… and then inserts 1 values into the pixel information.

It bit-shifts the pixels to “null” out information, generates proper binary values for masking purposes, and the sets the proper 1s values into the pixel binary information using bit-wise OR.

IMAGE *Posterize(IMAGE *image, int rbits, int gbits, int bbits)
{
	assert(image);

	int x, y;
	unsigned char r_addOnes = 1;
	unsigned char g_addOnes = 1;
	unsigned char b_addOnes = 1;
	unsigned int HEIGHT, WIDTH;

	HEIGHT = image->Height;
	WIDTH = image->Width;

	/* introduce zeros up to indicated bit */
	for (y = 0; y < HEIGHT; y++) {
		for (x = 0; x < WIDTH; x++) {
			SetPixelR(image, x, y, ( GetPixelR(image, x, y) >> rbits ));	/*erase by shifting right*/
			SetPixelR(image, x, y, ( GetPixelR(image, x, y) << rbits ));	/*introduce zeros by shifting left*/

			SetPixelG(image, x, y, ( GetPixelG(image, x, y) >> gbits ));
			SetPixelG(image, x, y, ( GetPixelG(image, x, y) << gbits ));

			SetPixelB(image, x, y, ( GetPixelB(image, x, y) >> bbits ));
			SetPixelB(image, x, y, ( GetPixelB(image, x, y) << bbits ));
		}
	}

	/* prepare adding ones values per R G B  2^(n-1) */
	for (x = 0; x < (rbits - 1); x++) {
		r_addOnes *= 2;
	}

	for (x = 0; x < (gbits - 1); x++) {
		g_addOnes *= 2;
	}

	for (x = 0; x < (bbits - 1); x++) {
		b_addOnes *= 2;
	}

	/* needed value is 2(n-1) - 1 */
	r_addOnes -= 1;
	g_addOnes -= 1;
	b_addOnes -= 1;

	/* return new shifting and addOnes value per cell */
	for (y = 0; y < HEIGHT; y++) {
		for (x = 0; x < WIDTH; x++) {
			SetPixelR(image, x, y, ( GetPixelR(image, x, y) | r_addOnes) );
			SetPixelG(image, x, y, ( GetPixelG(image, x, y) | g_addOnes) );
			SetPixelB(image, x, y, ( GetPixelB(image, x, y) | b_addOnes) );
		}
	}

	return image;

}

Photo Overlaying

This is a bit of a rudimentary implemtation of overlaying. The algorithm requires info of what color of RBG value (0-255) is to be the “background” color to not mask over.

(|r $ backgroundR| <= 5, |g $ backgroundG| <= 5, |b $ backgroundB| <=5) is the criteria used to determine what value is to be considered the background.

Once this is established the algorithm then begins replaces pixels that do not fall into the aforementioned criteria.

IMAGE *Overlay(IMAGE *inputImage, const IMAGE *overlayImage, int x_offset, int y_offset,
  unsigned char backgroundR, unsigned char backgroundG, unsigned char backgroundB){

	assert(inputImage);
	assert(overlayImage);

	int x, y;
	unsigned int HEIGHT, WIDTH;

	HEIGHT = overlayImage->Height;
	WIDTH = overlayImage->Width;

	for (y = 0; y < HEIGHT; y++) {
		for (x = 0; x < WIDTH; x++) {

			if(! ( GetPixelR(overlayImage, x, y) >= (-5 + backgroundR) && GetPixelR(overlayImage, x, y) <= (5 + backgroundR) ) ){
				if(! ( GetPixelG(overlayImage, x, y) >= (-5 + backgroundG) && GetPixelG(overlayImage, x, y) <= (5 + backgroundG) ) ){
					if(! ( GetPixelB(overlayImage, x, y) >= (-5 + backgroundB) && GetPixelB(overlayImage, x, y) <= (5 + backgroundB) ) ){
						SetPixelR(inputImage, x + x_offset, y + y_offset, GetPixelR(overlayImage, x, y) );
						SetPixelG(inputImage, x + x_offset, y + y_offset, GetPixelG(overlayImage, x, y) );
						SetPixelB(inputImage, x + x_offset, y + y_offset, GetPixelB(overlayImage, x, y) );
					}
				}
			}

		}
	}

	return inputImage;
}

Clockwise Rotation

Pixel position transformations are used to rotate the image. The important factor in this algorithm is where the indexing begins.

IMAGE *Rotate(IMAGE *image, int degree){

	assert(image);
	assert(degree == 0 || degree == 90 || degree == 180 || degree == 270);

	IMAGE* image_output = NULL;

	int x, y;
	unsigned int HEIGHT, WIDTH;

	HEIGHT = image->Height;
	WIDTH = image->Width;

	switch(degree){

	case 90:
		image_output = CreateImage(HEIGHT, WIDTH);

		for (y = 0; y < HEIGHT; y++) {
			for (x = 0; x < WIDTH; x++) {
				SetPixelR(image_output, HEIGHT - 1 - y, x,  GetPixelR(image, x, y ) ); /*top left to lower left*/
				SetPixelG(image_output, HEIGHT - 1 - y, x,  GetPixelG(image, x, y ) );
				SetPixelB(image_output, HEIGHT - 1 - y, x,  GetPixelB(image, x, y ) );
			}
		}
		break;
	case 180:
		image_output = CreateImage(WIDTH, HEIGHT);

		for (y = 0; y < HEIGHT; y++) {
			for (x = 0; x < WIDTH; x++) {
				SetPixelR(image_output, WIDTH - 1 - x, HEIGHT - 1 - y,  GetPixelR(image, x, y ) ); /*top left to bottom right*/
				SetPixelG(image_output, WIDTH - 1 - x, HEIGHT - 1 - y,  GetPixelG(image, x, y ) );
				SetPixelB(image_output, WIDTH - 1 - x, HEIGHT - 1 - y,  GetPixelB(image, x, y ) );
			}
		}
		break;
	case 270:
		image_output = CreateImage(HEIGHT, WIDTH);

		for (y = 0; y < HEIGHT; y++) {
			for (x = 0; x < WIDTH; x++) {
				SetPixelR(image_output, y, WIDTH - 1 - x,  GetPixelR(image, x, y ) ); /*top left to bottom left*/
				SetPixelG(image_output, y, WIDTH - 1 - x,  GetPixelG(image, x, y ) );
				SetPixelB(image_output, y, WIDTH - 1 - x,  GetPixelB(image, x, y ) );
			}
		}
		break;
	}

	DeleteImage(image);
	image = NULL;

	return image_output;
}

Cropping

Cropping utilizes many logic statements to ensure that no overflow indexing occurs. When all the proper cases have been accounted for the algorithm generates a new image with the values taken in. Once the new image is generated, the proper pixels are then placed into the new image.

IMAGE *Crop(IMAGE *image, int x, int y, int W, int H){

	assert(image);

	IMAGE* image_output = NULL;

	int i, j;
	unsigned int HEIGHT, WIDTH;

	HEIGHT = image->Height;
	WIDTH = image->Width;

	/*no single parameter exceeds image boundaries*/
	W = (W > WIDTH) ? WIDTH : W;
	H = (H > HEIGHT) ? HEIGHT : H;
	x = (x > WIDTH) ? WIDTH : x;
	y = (y > HEIGHT) ? HEIGHT : y;

	/*when horizontal sum exceeds WIDTH*/
	if((x + W) > WIDTH){
		if((y + H < HEIGHT)){
			image_output = CreateImage(image->Width - x, H);
		}
	}
	/*when vertical sum exceeds HEIGHT*/
	if((x + W) < WIDTH){
		if((y + H > HEIGHT)){
			image_output = CreateImage(W, image->Height - y);
		}
	}

	/*when HOIZONTAL and VERTICAL sum exceeds WIDTH and HEIGHT*/
	if((x + W) > WIDTH){
		if((y + H > HEIGHT)){
			image_output = CreateImage(image->Width - x, image->Height - y);
		}
	}

	/*normal*/
	else{
		image_output = CreateImage(W, H);
		}

	for (j = 0; j <  H; j++) {
		for (i = 0; i < W; i++) {
		SetPixelR( image_output, i, j, GetPixelR(image, i + x, j + y));
		SetPixelG( image_output, i, j, GetPixelG(image, i + x, j + y));
		SetPixelB( image_output, i, j, GetPixelB(image, i + x, j + y));

		}
	}

	DeleteImage(image);
	image = NULL;
	return image_output;
}

Resizing

This algorithm takes in concepts from Zoom and Crop. Like zoom, pixels are inserted between pixels when the image is enlarged. Similarly, pixels are deleted if the image is shrunk. The pixel intensity is dependant on the percentage factor and whether the image is enlarged or shrunk via the equations:

Averaging

$ x1 = x / (percentage / 100.00) $

$ y1 = y / (percentage / 100.00) $

$ x2 = (x + 1) / (percentage / 100.00) $

$ y2 = (y + 1) / (percentage / 100.00) $

For shrinkage, since the algorithm needs to generate less pixels taking the average of the many to be overwritten.

Duplicating

$ x’ = x * (percentage / 100.00) $

$ y’ = y * (percentage / 100.00) $

For enlarging. This is the alternative to generating a gradient of pixel intensities for a simple image, instead of a cleaner image.

IMAGE *Resize(IMAGE *image, int percentage){

	assert(image);
	assert(percentage < 500 && percentage > 1);

	IMAGE* image_output = NULL;

	int x, y;
	unsigned int HEIGHT, WIDTH;
	double nHeight, nWidth;

	HEIGHT = image->Height;
	WIDTH = image->Width;

	nHeight = HEIGHT * (percentage / 100.00);
	nWidth = WIDTH * (percentage / 100.00);

	if(percentage > 100){
		image_output = CreateImage(nWidth, nHeight);

		for (y = 0; y < HEIGHT; y++) {
			for (x = 0; x < WIDTH; x++) {
				SetPixelR(image_output,  x * (percentage/100.00),  y * (percentage/100.00), GetPixelR(image, x, y) );
				SetPixelG(image_output,  x * (percentage/100.00),  y * (percentage/100.00), GetPixelG(image, x, y) );
				SetPixelB(image_output,  x * (percentage/100.00),  y * (percentage/100.00), GetPixelB(image, x, y) );
			}
		}

		DeleteImage(image);
		image = NULL;
		return image_output;
	}

	if(percentage < 100){
		image_output = CreateImage(nWidth, nHeight);

		for (y = 0; y < nHeight; y++) {
			for (x = 0; x < nWidth; x++) {
				SetPixelR(image_output,  x,  y, GetPixelR(image, x * (percentage/100.00), y * (percentage/100.00)) );
				SetPixelG(image_output,  x,  y, GetPixelG(image, x * (percentage/100.00), y * (percentage/100.00)) );
				SetPixelB(image_output,  x,  y, GetPixelB(image, x * (percentage/100.00), y * (percentage/100.00)) );
			}
		}

		DeleteImage(image);
		image = NULL;
		return image_output;
	}

	else if(percentage == 100){
		return image;
	}

	return image;
}
Avatar
Andrew (Andres) Tec
Engineer

always the perfect time to learn.

Previous