Create histogram of pixel values in PGM images

Version 1: Generate histogram from a single PGM file

The C program below loads a grey scale image from a plain text PGM file and generates a histogram of pixel values between 0 and 255. The histogram data are saved to a file “histogram.csv”. To plot the histogram in MATLAB / Octave, use the following commands:

h = csvread('histogram.csv');
plot (h)

This is the C code for the single file version:

//
// histogram.c - Written by Ted Burke on 21-Nov-2022
// Counts the number of pixels with each colour
// value (0-255) in a PGM image.
//
// The filename of the input PGM should be specified
// as a command line argument.
//
//     histogram.exe image001.pgm
//

#include <stdio.h>

// 2D pixel array, like a spreadsheet with 1000 rows & 1000 columns
int p[1000][1000];

// Other image properties - width, height, white value
int w, h, maxval;

int main(int argc, char *argv[])
{
    FILE *f;
    char line[200];
    int x, y;

    // Check whether an input filename was provided
    // as a command line argument
    if (argc < 2)
    {
        printf("Error: No input file specified\n");
        return 1;
    }

    // Open input image file
    if ((f = fopen(argv[1], "r")) == NULL)
    {
        printf("Error: Invalid input file specified\n");
        return 2;
    }

    // Read 4 header lines.
    // The first two lines are simply ignored.
    // The next two lines contain the width,
    // height and maximum pixel value (white).
    fscanf(f, "%[^\n]\n", line);
    fscanf(f, "%[^\n]\n", line);
    fscanf(f, "%d %d\n", &w, &h);
    fscanf(f, "%d\n", &maxval);

    // Read pixel data into 2-D array, p[][]
    for (y=0 ; y<h ; ++y) for (x=0 ; x<w ; ++x) fscanf(f, "%d", &p[y][x]);
    fclose(f); // Close input file

    // Count number of pixels for each colour value
    int hist[256] = {0};
    for (y=0 ; y<h ; ++y) for (x=0 ; x<w ; ++x) hist[p[y][x]]++;

    // Open output file
    if ((f = fopen("histogram.csv", "w")) == NULL)
    {
        printf("Error: Failed to open output file\n");
        return 3;
    }

    // Print pixel data into file
    for (int c=0 ; c<256 ; ++c) fprintf(f, "%d\n", hist[c]);
    fclose(f); // Close output file

    return 0; // Exit normally
}

Version 2: Generate histogram from multiple PGM files

This version generates a single pixel value histogram from multiple PGM files. The input files are specified as command line arguments. The histogram data can be plotted in MATLAB/Octave the same way as for the previous version (see above).

//
// histogram2.c - Written by Ted Burke on 21-Nov-2022
// Counts the number of pixels with each colour
// value (0-255) in multiple PGM images.
//
// The filenames of the input PGM images should be
// specified as command line arguments.
//
//     histogram2.exe image001.pgm
//

#include <stdio.h>

// 2D pixel array, like a spreadsheet with 1000 rows & 1000 columns
int p[1000][1000];

// Other image properties - width, height, white value
int w, h, maxval;

int main(int argc, char *argv[])
{
    FILE *f;
    char line[200];
    int hist[256] = {0};
    int i, x, y;

    for (i=1 ; i<argc ; ++i)
    {
        // Open next input image file
        if ((f = fopen(argv[i], "r")) == NULL)
        {
            printf("Error: Invalid input file specified\n");
            return 2;
        }

        // Read 4 header lines.
        // The first two lines are simply ignored.
        // The next two lines contain the width,
        // height and maximum pixel value (white).
        fscanf(f, "%[^\n]\n", line);
        fscanf(f, "%[^\n]\n", line);
        fscanf(f, "%d %d\n", &w, &h);
        fscanf(f, "%d\n", &maxval);

        // Read pixel data into 2-D array, p[][]
        for (y=0 ; y<h ; ++y) for (x=0 ; x<w ; ++x) fscanf(f, "%d", &p[y][x]);
        fclose(f); // Close input file

        // Count number of pixels for each colour value
        for (y=0 ; y<h ; ++y) for (x=0 ; x<w ; ++x) hist[p[y][x]]++;
    }

    // Open output file
    if ((f = fopen("histogram.csv", "w")) == NULL)
    {
        printf("Error: Failed to open output file\n");
        return 3;
    }

    // Print pixel data into file
    for (int c=0 ; c<256 ; ++c) fprintf(f, "%d\n", hist[c]);
    fclose(f); // Close output file

    return 0; // Exit normally
}

Posted in Uncategorized | Leave a comment

Assignment 2: Shape Sorter – Due 6pm Friday 9th December 2022

In this assignment, you will write a C program to identify two different types of shape in a PGM image file. To generate ten example images for testing, create a folder called shapesorter, download the program blobs.exe into it, then run it as shown below:

The program you will write is called shapesorter.exe. To get you started, example code to load an image from a PGM file, store its pixel data into a 2-dimensional array and the process its pixel data is provided at the end of this post. Save this example code in a file called shapesorter.c. (also in the shapesorter folder). Compile it using the following command:

gcc shapesorter.c -o shapesorter.exe

The example images contain two types of blob – rings and solids. An example of each is shown below.

Your objective is to modify shapesorter.c so that it correctly identifies each of the two types of blob. When you run your completed version of shapesorter.exe, it should:

  • Open a PGM image file.
  • Print either “ring” or “solid” in the console to indicate which type of blob is shown in the image.
  • Save a modified version of the image to a new file. In the modified image, a small filled square (20 pixels wide) should be superimposed on the centre of the blob. The colour of the square should be pure white on a solid blob and pure black on a ring blob.

Assessment and Deadline

The primary method of assessment for this assignment is a short interview which will take place following submission of your code. Submit your final code (the file shapesorter.c only) via Brightspace before 6pm on Friday 9th December 2022. Interviews may optionally be carried out prior to the deadline by agreement between student and instructor.

Example code – shapesorter.c

//
// shapesorter.c - Loads, modifies and saves a PGM file
// written by Ted Burke - last modified 14-11-2022
//
// To compile shapesorter:
//
//     gcc -o shapesorter.exe shapesorter.c
//
// The default input file is input.pgm
// The default output file is output.pgm
// Other input and output filenames can be specified
// as command line arguments. For example,
//
//     shapesorter.exe image001.pgm modified.pgm
//

#include <stdio.h>

// 2D pixel array, like a spreadsheet with 1000 rows & 1000 columns
int p[1000][1000];

// Other image properties - width, height, white value
int w, h, maxval;

int main(int argc, char *argv[])
{
    // Variables
    FILE *f;
    char line[200];
    int x, y;
    
    ///////////////////////////////////////////////
    /// LOAD PIXEL DATA FROM FILE INTO 2D ARRAY ///
    ///////////////////////////////////////////////
    
    // Open input image file
    printf("Opening input file\n");
    if (argc > 1)
    {
        f = fopen(argv[1], "r");
    }
    else
    {
        f = fopen("input.pgm", "r");
    }
    
    // Read 4 header lines.
    // The first two are simply ignored. The next two
    // contain the width, height and white value.
    printf("Reading file header info\n");
    fscanf(f, "%[^\n]\n", line);
    fscanf(f, "%[^\n]\n", line);
    fscanf(f, "%d %d\n", &w, &h);
    fscanf(f, "%d\n", &maxval);
    
    // Read pixel data into 2-D array
    printf("Reading pixel data from file\n");
    y = 0;
    while(y < h)
    {
        x = 0;
        while(x < w)
        {
            fscanf(f, "%d", &p[y][x]);
            x = x + 1;
        }
        y = y + 1;
    }
    
    // Close input file
    printf("Closing input file\n");
    fclose(f);
    
    //////////////////////////////////////
    /// MODIFY ALL PIXELS IN THE IMAGE ///
    //////////////////////////////////////
    
    // Modify all pixel values one at a time
    printf("Modifying pixel data\n");
    y = 0;
    while(y < h)
    {
        x = 0;
        while(x < w)
        {
            // Just as an example, invert the colour
            // of each pixel in the image
            p[y][x] = 255 - p[y][x];
            
            x = x + 1; // next pixel on this row
        }
        y = y + 1; // next row of pixels
    }
    
    //////////////////////////////
    /// OUTPUT IMAGE TO A FILE ///
    //////////////////////////////
    
    // open output file
    printf("Opening output file\n");
    if (argc > 2)
    {
        f = fopen(argv[2], "w");
    }
    else
    {
        f = fopen("output.pgm", "w");
    }
    
    // Print header info into file
    printf("Printing file header to file\n");
    fprintf(f, "P2\n");
    fprintf(f, "# My PGM file\n");
    fprintf(f, "%d %d\n", w, h);
    fprintf(f, "%d\n", maxval);
    
    // Print pixel data into file
    printf("Printing pixel data to file\n");
    y = 0;
    while(y < h)
    {
        x = 0;
        while(x < w)
        {
            fprintf(f, "%3d ", p[y][x]);
            x = x + 1;
        }
        fprintf(f, "\n");
        y = y + 1;
    }
    
    // Close file
    printf("Closing output file\n");
    fclose(f);
    
    // exit normally
    return 0;
}

Alternative example code: shapesorter.c

//
// shapesorter.c - alternative version
// Loads, analyses / modifies and saves a PGM file
// Written by Ted Burke - last modified 21-11-2022
//
// To compile shapesorter:
//
//     gcc -o shapesorter.exe shapesorter.c
//
// Input and output filenames should be specified
// as command line arguments. For example,
//
//     shapesorter.exe image001.pgm modified.pgm
//
// If no output file is specified, the default "output.pgm".
//

#include <stdio.h>
 
// 2D pixel array, like a spreadsheet with 1000 rows & 1000 columns
int p[1000][1000];
 
// Other image properties - width, height, white value
int w, h, maxval;
 
int main(int argc, char *argv[])
{
    // Variables
    FILE *f;
    char line[200];
    int x, y;
     
    ///////////////////////////////////////////////
    /// LOAD PIXEL DATA FROM FILE INTO 2D ARRAY ///
    ///////////////////////////////////////////////
    
    // Check whether an input filename was provided
    // as a command line argument
    if (argc < 2)
    {
        printf("Error: No input file specified\n");
        return 1;
    }
    
    // Open input image file
    printf("Opening input file\n"); 
    f = fopen(argv[1], "r");
    if (f == NULL)
    {
        printf("Error: Invalid input file specified\n");
        return 2;
    }
     
    // Read 4 header lines.
    // The first two lines are simply ignored.
    // The next two lines contain the width,
    // height and maximum pixel value (white).
    printf("Reading file header info\n");
    fscanf(f, "%[^\n]\n", line);
    fscanf(f, "%[^\n]\n", line);
    fscanf(f, "%d %d\n", &w, &h);
    fscanf(f, "%d\n", &maxval);
     
    // Read pixel data into 2-D array
    printf("Reading pixel data from file\n");
    for (y = 0 ; y < h ; ++y)
    {
        for (x = 0 ; x < w ; ++x)
        {
            fscanf(f, "%d", &p[y][x]);
        }
    }
     
    // Close input file
    printf("Closing input file\n");
    fclose(f);
     
    //////////////////////////////////////
    /// MODIFY ALL PIXELS IN THE IMAGE ///
    //////////////////////////////////////
     
    // Modify all pixel values one at a time
    int dark_pixel_count = 0;
    printf("Analysing pixel data\n");
    for (y = 0 ; y < h ; ++y)
    {
        for (x = 0 ; x < w ; ++x)
        {
            // Count the dark pixels
            if (p[y][x] < 128) dark_pixel_count++;          
        }
    }
    printf("%d dark pixels found\n", dark_pixel_count);
     
    //////////////////////////////
    /// OUTPUT IMAGE TO A FILE ///
    //////////////////////////////
     
    // open output file
    printf("Opening output file\n");
    if (argc > 2)
    {
        f = fopen(argv[2], "w");
    }
    else
    {
        f = fopen("output.pgm", "w");
    }
    
    if (f == NULL)
    {
        printf("Error: Failed to open output file\n");
        return 3;
    }
     
    // Print header info into file
    printf("Printing file header to file\n");
    fprintf(f, "P2\n# My PGM file\n%d %d\n%d\n", w, h, maxval);

    // Print pixel data into file
    printf("Printing pixel data to file\n");
    for (y = 0 ; y < h ; ++y)
    {
        for (x = 0 ; x < w ; ++x)
        {
            fprintf(f, "%3d ", p[y][x]);
        }
        fprintf(f, "\n");
    }
     
    // Close file
    printf("Closing output file\n");
    fclose(f);
     
    // exit normally
    return 0;
}
Posted in Uncategorized | Leave a comment

Program to generate PGM images of random blobs – rings and solids

The program blobs.exe (full code provided below) generates a set of PGM image files, each showing a random dark coloured blob on a light background. There are two types of blob – solids and rings. An example of each is shown below.

To generate ten images, run blobs.exe as follows:

This is the full source code:

//
// blobs.c - This program generates images of blobs
// Written by Ted Burke - last updated 21-11-2021
//
// To compile:
//
//      gcc -o blobs.exe blobs.c
//
// To create 10 blob images:
//
//      blobs.exe 10
//

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>
 
// Image data is stored in 2D pixel array
int p[1000][1000];
int w=640, h=400;
 
#define R1 20
#define R2 20
#define R3 20
#define R4 20
#define R5 40
#define R6 40
#define R7 20
#define R8 20
 
#define N_min 5
#define N_max 50
 
// Function prototypes
void add_blob_to_image();
void clear_image();
void save_image_as(char *);
 
// Main function
int main(int argc, char *argv[])
{
    // Variables
    int file_count, n, x, y;
    char filename[20];
 
    // If used specified number of files to create,
    // store that information into num_files variable.
    if (argc > 1) file_count = atoi(argv[1]);
    else file_count = 10;
 
    // Seed random number generation
    srand(time(NULL));
 
    // Create blob image files
    n = 1;
    while(n <= file_count)
    {
        clear_image();
        add_blob_to_image();
        sprintf(filename, "image%03d.pgm", n);
        save_image_as(filename);
        n = n + 1;
    }
 
    // Exit normally
    return 0;
}
 
// Set the image to plain noisy background
void clear_image()
{
    int x, y;
 
    // Fill pixel array with values
    y = 0;
    while(y < h)
    {
        x = 0;
        while(x < w)
        {
            // Set pixel to a bright random value
            p[y][x] = 180 + (rand() % 60);
 
            x = x + 1;
        }
        y = y + 1;
    }
}
 
// Add a blob (solid or ring) to the image
void add_blob_to_image()
{
    // Variables
    int x, y, N;
    int ring;
    int xc, yc, ir_min, ir_max, or_min, or_max;
 
    // Choose inner radius max and min values
    ir_min = R1 + (rand()%R2);
    ir_max = ir_min + R3 + (rand()%R4);
 
    // Choose outer radius max and min values
    or_min = ir_max + R5 + (rand()%R6);
    or_max = or_min + R7 + (rand()%R8);
 
    // Randomly choose either solid or ring
    ring = rand()%2;
 
    // Print object type
    if (ring)
    {
        fprintf(stderr, "Ring.... ");
    }
    else
    {
        fprintf(stderr, "Solid... ");
 
        // Blob is solid, so remove centre hole
        ir_min = 0;
        ir_max = 0;
    }
 
    // Choose blob centre point
    xc = 10 + or_max + (rand()%(w-20-2*or_max));
    yc = 10 + or_max + (rand()%(h-20-2*or_max));
 
    double distance, r, angle;
 
    // Choose number of fixed points on boundary contour
    N = N_min + (rand()%(N_max-N_min));
    double ir_vals[N+1], or_vals[N+1];
 
    // Print parameters for current blob
    fprintf(stderr,
	    "N=%2d, xc=%3d, yc=%3d, "
		"ir_min=%2d, ir_max=%2d, "
		"or_min=%3d, or_max=%3d\n",
        N, xc, yc, ir_min, ir_max, or_min, or_max);
	
    int n;
    int i;
    double di;
    double ir, or; // inner and outer radius at a particular angle
	
    // Calculate varying inner and outer boundaries
    for (n=0; n<N ; n++)
    {
        // Inner boundary contour
        if (ring) ir_vals[n] = ir_min + (rand()%(ir_max-ir_min));
        else ir_vals[n] = 0;
 
        // Outer boundary contour
        or_vals[n] = or_min + (rand()%(or_max-or_min));
    }
	
    // Add redundant value to end of array for convenience
    // in calculating interpolated values. This extra value
    // is equal to the first one.
    ir_vals[N] = ir_vals[0];
    or_vals[N] = or_vals[0];
	
    // Rasterize the blob
    y = 0;
    while(y < h)
    {
        x = 0;
        while(x < w)
        {
            // distance of current point from centre
            distance = sqrt((x-xc)*(x-xc)+(y-yc)*(y-yc));
			
            // angle of current point relative to centre
            angle = atan2(y-yc, x-xc);
			
            // di is a double value between 0 and N
            di = (N/2.0)*(1 + angle/M_PI);
            if (di < 0) di = 0;
            if (di >= N) di = N - 0.001;
			
            // i is the index into the ir_vals and or_vals arrays
            i = (int)di;
			
            // Reduce di to only its fractional part, i.e. a value
            // between 0 and 1
            di = di - i;
			
            // Calculate inner and outer boundary distances at this angle.
            // This formula smoothly interpolates between points in the
            // ir_vals and or_vals arrays.
            ir = (0.5 + 0.5*cos(di*M_PI))*ir_vals[i] + (0.5 - 0.5*cos(di*M_PI))*ir_vals[i+1];
            or = (0.5 + 0.5*cos(di*M_PI))*or_vals[i] + (0.5 - 0.5*cos(di*M_PI))*or_vals[i+1];
			
            // If this point lies within the blob boundary, make it dark.
            if (distance >= ir && distance < or) p[y][x] = rand()%(p[y][x]/2);
			
            // Next point
            x = x + 1;
        }
        // Next row
        y = y + 1;
    }
}
 
// Save the current image to the specified filename
void save_image_as(char *filename)
{
    // Variables
    int x, y;
	
    // Open output file
    FILE *f;
    f = fopen(filename, "w");
	
    // Print header info to file
    fprintf(f, "P2\n");
    fprintf(f, "# Blob image\n");
    fprintf(f, "%d %d\n", w, h);
    fprintf(f, "255\n");
	
    // Print all pixel data to file
    y = 0;
    while(y < h)
    {
        // Print a row of pixel values to file
        x = 0;
        while(x < w)
        {
            // Print one pixel value to file
            fprintf(f, "%d ", p[y][x]);
			
            x = x + 1;
        }
        fprintf(f, "\n");
        y = y + 1;
    }
	
    // Close output file
    fclose(f);
}

Posted in Uncategorized | Leave a comment

Array example: Counting values over 300 in an array

//
// signal.c - Count how many elements of the
// signal array are greater than 300
//

#include <stdio.h>
#include <math.h>

int main()
{
	int i, counter;
	double time[1000];
	double signal[1000];
	
	// waveform characteristics
	double A = 325.27;        // amplitude
	double w = 2 * M_PI * 50; // angular frequency
	double theta = 0;         // phase
	
	// Fill arrays with values
	i = 0;
	while(i < 1000)
	{
		time[i] = i * 0.001;
		signal[i] = A * cos(w*time[i] + theta);
		
		i = i + 1;
	}
	
	// Open a file "sig.csv" in write mode
	FILE *myfile;
	myfile = fopen("sig.csv", "w");
	
	// Write time-voltage samples to the CSV file
	i = 0;
	counter = 0;
	while(i < 1000)
	{
		fprintf(myfile, "%lf,%lf\n", time[i], signal[i]);
		
		if (signal[i] > 300)
		{
			counter++; // increment counter
		}
		
		i = i + 1;
	}
	
	// Close the file
	fclose(myfile);
	
	// Print out the number of values over 300
	printf("There were %d numbers greater than 300.\n", counter);
	
	return 0;
}

Posted in Uncategorized | Leave a comment

ECG problem starter code

Link to PhysioBank ATM

  • Go to PhysioBank ATM and select the settings shown below.
  • Click on “samples.txt” link to download the ECG signals.
  • Open “samples.txt” in Notepad++ and delete the first two lines of the file, which just contain the column headings.
  • Now your ECG data is ready to use!
//
// ecg.c
//

#include <stdio.h>

int main()
{
	int i;
	double time[7680];
	double ecg1[7680];
	double ecg2[7680];
	
	// Load ECG data from file
	FILE *ecg_file;
	ecg_file = fopen("samples.txt", "r");
	i = 0;
	while(i < 7680)
	{
		fscanf(ecg_file, "%lf %lf %lf", &time[i], &ecg1[i], &ecg2[i]);
		i++;
	}
	fclose(ecg_file);
	
	// Print some data to check it loaded correctly
	i = 0;
	while (i < 10)
	{
		printf("%lf %lf %lf\n", time[i], ecg1[i], ecg2[i]);
		i++;
	}
	
	// NOW COUNT THE HEARTBEATS!!
	
	return 0;
}

Posted in Uncategorized | Leave a comment

How to set the PATH environment variable for MinGW / gcc / g++

An easy way to install MinGW (and hence gcc / g++) on Windows is to install the Code::Blocks integrated development environement (IDE). Some versions of the Code::Blocks installer include MinGW and others don’t, so be careful to choose the right one. The installer I recommend for the current version of Code::Blocks is:

    codeblocks-20.03mingw-setup.exe

It can be downloaded from the following page

The installation process includes an option to update the PATH environment variable, so that gcc (or g++) can be run in a command window without needing to include the full path to the MinGW installation folder. However, if you accidentally omit that option when installing Code::Blocks, you can update the PATH manually as follows:

Type “path” in the Windows search bar as shown below and then click on “Edit the system environment variables”.

Click on the “Environment Variables…” button.

In the “System variables” panel (the lower half of the Environment Variables dialog box), click on “Path” to select it and then click the “Edit…” button.

Click “New” to add a new item to the list and then click “Browse…” to set the new item to the “MinGW/bin” folder within your Code::Blocks installation. On my machine, the full location is

    C:\Program Files\CodeBlocks\MinGW\bin

but it may be different on your machine if you installed Code::Blocks in a different location.

Close all dialog boxes by clicking “OK” and then open a new command window, by typing “cmd” in the Windows search bar and then clicking “Command Prompt” as shown below.

In the command window, type “gcc” and press enter. If the path is set correctly, then you should see the error shown below (you get an error because you didn’t supply the name of a C file to be compiled). If you see that error, then all is well and gcc is ready to use.

Posted in Uncategorized | Leave a comment

Cherrypicker example from class

//
// cableheight.c
// Written by Ted Burke
// Last updated 11-Oct-2021
//

#include <stdio.h>
#include <math.h>

int main()
{
	// Declare variables
	double ground_distance, min_cable_height;
	double angle_degrees, angle_radians;
	
	// Display welcome message
	printf("Cable Height Calculator\n");
	printf("Written by Ted Burke (ted.burke@tudublin.ie)\n");
	
	// Ask user for ground distance
	printf("Please type in ground distance in metres: ");
	scanf("%lf", &ground_distance);
	
	// As the user for the angle to the lowest point on the line
	printf("Please type in angle in degrees: ");
	scanf("%lf", &angle_degrees);
	
	// Convert angle from degrees to radians
	angle_radians = (M_PI * angle_degrees) / 180.0;
	
	// Calculate minimum cable height
	min_cable_height = ground_distance * tan(angle_radians);
	
	// Print out the minimum cable height
	printf("Minimum cable height is %.15lf metres\n", min_cable_height);

	// Print a message saying whether it's safe or not to proceed
	if (min_cable_height <= 12)
	{
		printf("Do not proceed - cable is too low!!!\n");
	}
	else
	{
		printf("Proceed with caution - cable is high enough.\n");
	}
	
	return 0;
}
Posted in Uncategorized | Leave a comment

Assignment 2: Shape Sorter – Due 5pm Friday 11th December 2020

In this assignment, you will write a C program called shapesorter.c to analyse and classify PGM image files. Each image contains one of two types of shape: type 0: round or type 1: square. Your compiled program, shapesorter.exe, must do the following:

  1. Load the pixel data from a user-specified PGM image file.
  2. Identify whether the image contains a round or square shape. The image type number should be printed to the console – “0” or “1”. Your program should not print any other text.
  3. Modify the image by adding a tight-fitting rectangular box around the shape. The tighter it fits, the better. The line width of the box should be 1 pixel. The colour of the rectangular box should be white for a round shape and black for a square shape.
  4. Save the modified image with the filename output.pgm.

Example images

Two programs have been provided to generate the images your program will classify. Download them from the following links, then compile and run them to generate the images:

You may need to right click on the links to download the files. The full gcc command to compile each program is included in the opening comments of each file.

Both programs create a set of example PGM files, each containing a single shape. Both programs generate a mixture of round and square shapes. Begin by working on the easy images and only move on to the more difficult images if you have mastered the easy ones.

To get you started, example C code that demonstrates how to load, modify and save a PGM image is provided below.

Deadline, submission and assessment

You will submit your final code (the file shapesorter.c only) via Brightspace no later than 5pm on Friday 11th December 2020. The primary method of assessment for this assignment is a short interview which will take place following submission of your code. In some cases, interviews may be carried out prior to the deadline by agreement between student and instructor.

Example code

//
// shapesorter.c - Loads, modifies and saves a PGM file
// written by Ted Burke - last modified 17-11-2020
//
// To compile shapesorter:
//
//      gcc -o shapesorter.exe shapesorter.c
//
// The input filename is specified as a command line argument:
//
//      shapesorter.exe easy001.pgm
//
 
#include <stdio.h>
 
int main(int argc, char *argv[])
{
    // Variables declarations
    //
    // Note: Declaring a variable as "const" means its
    // value can only be set once. It prevents accidental
    // changes to variables that should remain constant.
    //
    char filetype[100];   // used to store "P2" string from PGM header
    int x, y;             // pixel coordinates
    int w, h;       // image width and height
    int whiteval;   // pixel value for pure white
    int p[480][640];      // 2-D array of pixel values
 
    ////////////////////////////////////////////////
    /// LOAD PIXEL DATA FROM FILE INTO 2-D ARRAY ///
    ////////////////////////////////////////////////
 
    // Open the PGM file that was specified as a
    // command line argument
    FILE *finput;
    printf("Opening input file\n");
    finput = fopen(argv[1], "r");
 
    // The first 3 lines of the file are the PGM header.
    // The first line is "P2", the second contains the
    // width and height, and the third is the white value.
    printf("Reading file header info\n");
    fscanf(finput, "%s", filetype);
    fscanf(finput, "%d %d", &w, &h);
    fscanf(finput, "%d", &whiteval);
 
    // Read the pixel data into the 2-D array
    printf("Reading pixel data from file\n");
    y = 0;
    while(y < h)
    {
        x = 0;
        while(x < w)
        {
            fscanf(finput, "%d", &p[y][x]);
            x = x + 1;
        }
        y = y + 1;
    }
 
    // Close the input file
    printf("Closing input file\n");
    fclose(finput);
 
    /////////////////////////////////////////////////////
    /// MODIFY THE IMAGE - THIS IS JUST AN EXAMPLE!!! ///
    /////////////////////////////////////////////////////
 
    // Paint a certain region white in the image
    printf("Modifying pixel data\n");
    y = 100;
    while(y < 200)
    {
        x = 120;
        while(x < 240)
        {
            p[y][x] = 255;
 
            x = x + 1;
        }
        y = y + 1;
    }
 
    //////////////////////////////////
    /// OUTPUT THE IMAGE TO A FILE ///
    //////////////////////////////////
 
    // Open the output file
    printf("Opening output file\n");
    FILE *foutput;
    foutput = fopen("output.pgm", "w");
 
    // Print header info into the file
    printf("Printing file header to file\n");
    fprintf(foutput, "P2\n%d %d\n%d\n", w, h, whiteval);
 
    // Print pixel data into the file
    printf("Printing pixel data to file\n");
    y = 0;
    while(y < h)
    {
        x = 0;
        while(x < w)
        {
            fprintf(foutput, "%d ", p[y][x]);
            x = x + 1;
        }
        fprintf(foutput, "\n");
        y = y + 1;
    }
 
    // Close the file
    printf("Closing output file\n");
    fclose(foutput);
 
    // Exit normally
    return 0;
}

Posted in Uncategorized | Leave a comment

Incomplete draft of rings and crosses image generator

Example output image (converted from PGM to PNG for web):

This is the complete code:

//
// xo.c - Generate X and O images for ShapeSorter assignment
// Written by Ted Burke, 12-11-2019
//
// To compile:
//
//    gcc -o xo xo.c -lm
//

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <complex.h>
#include <time.h>

// Image width and height
#define W 640
#define H 480

// Primary and secondary pixel buffers
unsigned char p1[H][W] = {0};
unsigned char p2[H][W] = {0};

// Prototypes for drawing functions
void disc(int cx, int cy, int r, int c);
void line(int x1, int y1, int x2, int y2, int r, int c);
void grow(int r, int c);
void ring(int cx, int cy, int r, int d, int c);
void cross(int cx, int cy, int r, int d, int c);

int main()
{
    int x, y, n;
    
    // Seed random number generation
    srand((unsigned) time(0));
    
    // Draw a random selection of objects (crosses and rings)
    // into the primary pixel buffer    
    for (n=0 ; n<16 ; ++n)
    {
        int cx = (n%4)*W/4 + W/8;
        int cy = (n/4)*H/4 + H/8;
        int r = H/16;
        
        int c = 50+rand()*200.0/RAND_MAX;
        int d = r/5;
        
        if (rand()%2) ring(cx, cy, r, d, c);
        else cross(cx, cy, r, d, c);
    }
    
    // Write image to PGM file
    FILE *f = fopen("output.pgm", "w");
    fprintf(f, "P2\n%d %d\n255\n", W, H);
    for (y=0 ; y<H ; ++y)
    {
        for (x=0 ; x<W ; ++x) fprintf(f, "%3d ", p1[y][x]);
        fprintf(f, "\n");
    }
    fclose(f);
    
    return 0;
}

// Draws a rough ring centred on cx,cy with radius r,
// line thickness d and colour c
void ring(int cx, int cy, int r, int d, int c)
{
    int n, N=64;
    complex double z1, z2, a;
    double b;
    
    // Parameters to stretch and tilt ring
    a = cexp(I*rand()*M_PI/RAND_MAX); // angle
    b = 0.5+rand()*1.0/RAND_MAX;      // stretch
    
    // Construct ring from N line segments
    for (n=0 ; n<N ; ++n)
    {
        z1 = (r + d*(rand()*0.4/RAND_MAX - 0.2))*cexp(I*n*2.0*M_PI/N);
        z2 = (r + d*(rand()*0.4/RAND_MAX - 0.2))*cexp(I*(n+1)*2.0*M_PI/N);
        
        z1 = cx + I*cy + a * (b*creal(z1) + I*cimag(z1));
        z2 = cx + I*cy + a * (b*creal(z2) + I*cimag(z2));
        
        line(creal(z1), cimag(z1), creal(z2), cimag(z2), (0.95+rand()*0.1/RAND_MAX)*d, c);
    }
}

// Draws a cross centred at cx,cy with line thickess d,
// colour c and "radius" r (from centre to line ends)
void cross(int cx, int cy, int r, int d, int c)
{
    int m, n, N=64;
    complex double z1, z2;
    complex double a[2];
    
    // Parameters to stretch and tilt ring
    a[0] = cexp(I*rand()*M_PI/RAND_MAX);
    a[1] = a[0] * cexp(I*M_PI_2*(0.75+rand()*0.5/RAND_MAX));    
    
    // Draw the two rough strokes of the cross
    for (m=0 ; m<2 ; ++m)
    {
        // Initial point
        n = 0;
        z2 = n*(2.0*r)/N - r + I*(rand()*0.1/RAND_MAX-0.05)*d;
        
        // Trace out this stroke as a series of line segments
        for (n=1 ; n<N ; ++n)
        {
            z1 = z2;
            z2 = n*(2.0*r)/N - r + I*(rand()*0.1/RAND_MAX-0.05)*d;
            
            line(cx+creal(a[m]*z1), cy+cimag(a[m]*z1),
                 cx+creal(a[m]*z2), cy+cimag(a[m]*z2),
                 (0.95+rand()*0.1/RAND_MAX)*d, c);
        }
    }
}

// Draws a line of thickness 2r and colour c from
// point x1,y1 to point x2,y2
void line(int x1, int y1, int x2, int y2, int r, int c)
{
    int x, y;
    
    if (x1 > x2)
    {
        // swap points
        x = x1; x1 = x2; x2 = x;
        y = y1; y1 = y2; y2 = y;
    }
    
    if (x1 != x2) for (x=x1 ; x<=x2 ; ++x)
    {
        // Connect points with horizontal pieces
        y = y1 + (y2-y1)*(x*1.0-x1)/(x2-x1);
        if (r>0) disc(x, y, r, c);
        else p1[y][x] = c;
    }
    
    if (y1 > y2)
    {
        // swap points
        x = x1; x1 = x2; x2 = x;
        y = y1; y1 = y2; y2 = y;
    }
    
    if (y1 != y2) for (y=y1 ; y<=y2 ; ++y)
    {
        // Connect points with vertical pieces
        x = x1 + (x2-x1)*(y*1.0-y1)/(y2-y1);
        if (r>0) disc(x, y, r, c);
        else p1[y][x] = c;
    }
}

// Draw a disc of radius r with value c centred on x,y in primary pixel
// buffer
void disc(int cx, int cy, int r, int c)
{
    int x, y;
    int r2 = r*r;
    
    for (y=(cy-r>0 ? cy-r : 0) ; y<=(cy+r<H ? cy+r : H-1) ; ++y)
    {
        for (x=(cx-r>0 ? cx-r : 0) ; x<=(cx+r<W ? cx+r : W-1) ; ++x)
        {
            if ((x-cx)*(x-cx) + (y-cy)*(y-cy) <= r2) p1[y][x] = c;
        }
    }   
}

// This function replaces every pixel of value c with a disc
// of the same colour and of radius r centred on the same point
void grow(int r, int c)
{
    int x, y;
    
    // Copy image from primary pixel buffer into secondary pixel buffer
    memcpy(p2, p1, W*H);
    
    // Scan for pixels with value c in the secondary pixel buffer
    for (y=0 ; y<H ; ++y) for (x=0 ; x<W ; ++x)
    {
        if (p2[y][x] == c) disc(x, y, r, c);
    }
}
Posted in Uncategorized | Leave a comment

PGM Examples – solution code (don’t look until you’ve attempted them yourself)

Example 1:

//
// pgmexample1 - written by Ted Burke - 12-11-2019
//

#include <stdio.h>

#define W 640
#define H 480

unsigned char p[H][W] = {0};

int main()
{
    int x, y;
    
    // Fill in pixels
    y = 0;
    while(y < H)
    {
        x = 0;
        while (x < W)
        {
            if (y > H/2)
            {
                p[y][x] = 0;
            }
            else
            {
                p[y][x] = 255;
            }
            
            x = x + 1;
        }
        
        y = y + 1;
    }
    
    // Write PGM file
    FILE *f = fopen("example1.pgm", "w");
    fprintf(f, "P2\n640 480\n255\n");
    for (y=0 ; y<H ; ++y)
    {
        for (x=0 ; x<W ; ++x)
        {
            fprintf(f, "%3d ", p[y][x]);
        }
    }
    
    // Exit program
    return 0;
}

Example 2:

//
// pgmexample2 - written by Ted Burke - 12-11-2019
//

#include <stdio.h>

#define W 640
#define H 480

unsigned char p[H][W] = {0};

int main()
{
    // Fill in pixels
    int x, y;
    for (y=0 ; y<H ; ++y) for (x=0 ; x<W ; ++x)
    {
        if ((y > H/2) == (x > W/2)) p[y][x] = 0;
        else p[y][x] = 255;
    }
    
    // Write PGM file
    FILE *f = fopen("example2.pgm", "w");
    fprintf(f, "P2\n640 480\n255\n");
    for (y=0 ; y<H ; ++y) for (x=0 ; x<W ; ++x)
    {
        fprintf(f, "%3d ", p[y][x]);
    }
    
    // Exit program
    return 0;
}

Example 3:

//
// pgmexample3 - written by Ted Burke - 12-11-2019
//

#include <stdio.h>

#define W 640
#define H 480

unsigned char p[H][W] = {0};

int main()
{
    // Fill in pixels
    int x, y;
    for (y=0 ; y<H ; ++y) for (x=0 ; x<W ; ++x)
    {
        if (y > x) p[y][x] = 50;
        else p[y][x] = 200;
    }
    
    // Write PGM file
    FILE *f = fopen("example3.pgm", "w");
    fprintf(f, "P2\n640 480\n255\n");
    for (y=0 ; y<H ; ++y) for (x=0 ; x<W ; ++x)
    {
        fprintf(f, "%3d ", p[y][x]);
    }
    
    // Exit program
    return 0;
}

Example 4:

//
// pgmexample4 - written by Ted Burke - 12-11-2019
//

#include <stdio.h>

#define W 640
#define H 480

unsigned char p[H][W] = {0};

int main()
{
    // Fill in pixels
    int x, y;
    for (y=0 ; y<H ; ++y) for (x=0 ; x<W ; ++x)
    {
        if (y < 100 || y > H-100 || x < 100 || x > W-100) p[y][x] = 255;
        else p[y][x] = 100;
    }
    
    // Write PGM file
    FILE *f = fopen("example4.pgm", "w");
    fprintf(f, "P2\n640 480\n255\n");
    for (y=0 ; y<H ; ++y) for (x=0 ; x<W ; ++x)
    {
        fprintf(f, "%3d ", p[y][x]);
    }
    
    // Exit program
    return 0;
}

Example 5:

//
// pgmexample5 - written by Ted Burke - 12-11-2019
//

#include <stdio.h>

#define W 640
#define H 480

unsigned char p[H][W] = {0};

int main()
{
    // Fill in pixels
    int x, y;
    
    for (y=0 ; y<H ; ++y) for (x=0 ; x<W ; ++x)
    {
        if ((y>H/2-50 && y<H/2+50) || (x>W/2-50 && x<W/2+50)) p[y][x] = 0;
        else p[y][x] = 150;
    }
    
    // Write PGM file
    FILE *f = fopen("example5.pgm", "w");
    fprintf(f, "P2\n640 480\n255\n");
    for (y=0 ; y<H ; ++y) for (x=0 ; x<W ; ++x)
    {
        fprintf(f, "%3d ", p[y][x]);
    }
    
    // Exit program
    return 0;
}

Example 6:

//
// pgmexample6 - written by Ted Burke - 12-11-2019
//

#include <stdio.h>

#define W 640
#define H 480

unsigned char p[H][W] = {0};

void rectangle(int left, int top, int w, int h, int color)
{
    int x, y;
    
    for (y=top ; y<top+h ; ++y) for (x=left ; x<left+w ; ++x)
    {
        p[y][x] = color;
    }
}

void disc(int cx, int cy, int r, int color)
{
    int x, y;
    int r2 = r*r;
    
    for (y=0 ; y<H ; ++y) for (x=0 ; x<W ; ++x)
    {
        if ((y-cy)*(y-cy) + (x-cx)*(x-cx) < r2) p[y][x] = color;
    }
}

int main()
{
    // Fill in pixels
    int x, y;
    
    rectangle(0, 0, W, H, 255);
    disc(W/2, H/2, 200, 0);
    
    // Write PGM file
    FILE *f = fopen("example6.pgm", "w");
    fprintf(f, "P2\n640 480\n255\n");
    for (y=0 ; y<H ; ++y) for (x=0 ; x<W ; ++x)
    {
        fprintf(f, "%3d ", p[y][x]);
    }
    
    // Exit program
    return 0;
}

Example 7:

//
// pgmexample7 - written by Ted Burke - 12-11-2019
//

#include <stdio.h>

#define W 640
#define H 480

unsigned char p[H][W] = {0};

void rectangle(int left, int top, int w, int h, int color)
{
	int x, y;
	
	for (y=top ; y<top+h ; ++y) for (x=left ; x<left+w ; ++x)
	{
		p[y][x] = color;
	}
}

void disc(int cx, int cy, int r, int color)
{
	int x, y;
	int r2 = r*r;
	
	for (y=0 ; y<H ; ++y) for (x=0 ; x<W ; ++x)
	{
		if ((y-cy)*(y-cy) + (x-cx)*(x-cx) < r2) p[y][x] = color;
	}
}

int main()
{
	// Fill in pixels
	int x, y, n;
	
	rectangle(0, 0, W, H, 255);
	for (n=0 ; n<8 ; ++n)
	{
		disc(150+n*25, 100+n*25, 80, n*20);
	}
	
	// Write PGM file
	FILE *f = fopen("example7.pgm", "w");
	fprintf(f, "P2\n640 480\n255\n");
	for (y=0 ; y<H ; ++y) for (x=0 ; x<W ; ++x)
	{
		fprintf(f, "%3d ", p[y][x]);
	}
	
	// Exit program
	return 0;
}

Example 8:

//
// pgmexample8 - written by Ted Burke - 12-11-2019
//

#include <stdio.h>
#include <stdlib.h>

#define W 640
#define H 480

unsigned char p[H][W] = {0};

void rectangle(int left, int top, int w, int h, int color)
{
    int x, y;
    
    for (y=top ; y<top+h ; ++y) for (x=left ; x<left+w ; ++x)
    {
        p[y][x] = color;
    }
}

void disc(int cx, int cy, int r, int color)
{
    int x, y;
    int r2 = r*r;
    
    for (y=0 ; y<H ; ++y) for (x=0 ; x<W ; ++x)
    {
        if ((y-cy)*(y-cy) + (x-cx)*(x-cx) < r2) p[y][x] = color;
    }
}

int main()
{
    // Fill in pixels
    int x, y, n;
    
    rectangle(0, 0, W, H, 255);
    for (n=0 ; n<100 ; ++n)
    {
        if (rand()%2)
        {
            rectangle(30+rand()%480, 30+rand()%320, 10+rand()%20, 20+rand()%30, rand()%150);
        }
        else
        {
            disc(80+rand()%480, 80+rand()%320, 10+rand()%20, rand()%150);
        }
    }
    
    // Write PGM file
    FILE *f = fopen("example8.pgm", "w");
    fprintf(f, "P2\n640 480\n255\n");
    for (y=0 ; y<H ; ++y) for (x=0 ; x<W ; ++x)
    {
        fprintf(f, "%3d ", p[y][x]);
    }
    
    // Exit program
    return 0;
}
Posted in Uncategorized | Leave a comment