Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 99 additions & 32 deletions image.c
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
#define _POSIX_C_SOURCE 199309L
#include <stdio.h>
#include <stdint.h>
#include <time.h>
#include <string.h>
#include "image.h"
#include <pthread.h>

#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"

#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image_write.h"

int g_threads = 4;

//An array of kernel matrices to be used for image convolution.
//The indexes of these match the enumeration from the header file. ie. algorithms[BLUR] returns the kernel corresponding to a box blur.
Matrix algorithms[]={
Expand All @@ -21,6 +25,28 @@ Matrix algorithms[]={
{{0,0,0},{0,1,0},{0,0,0}}
};

typedef struct {
int start_row; // inclusive
int end_row; // exclusive
Image *src;
Image *dst;
Matrix alg;
} WorkerArgs;

static void* worker(void* ap) {
WorkerArgs *a = (WorkerArgs*)ap;
for (int row = a->start_row; row < a->end_row; row++) {
for (int pix = 0; pix < a->src->width; pix++) {
for (int bit = 0; bit < a->src->bpp; bit++) {
a->dst->data[Index(pix,row,a->src->width,bit,a->src->bpp)] =
getPixelValue(a->src, pix, row, bit, a->alg);
}
}
}
return NULL;
}



//getPixelValue - Computes the value of a specific pixel on a specific channel using the selected convolution kernel
//Paramters: srcImage: An Image struct populated with the image being convoluted
Expand Down Expand Up @@ -56,22 +82,41 @@ uint8_t getPixelValue(Image* srcImage,int x,int y,int bit,Matrix algorithm){
// destImage: A pointer to a pre-allocated (including space for the pixel array) structure to receive the convoluted image. It should be the same size as srcImage
// algorithm: The kernel matrix to use for the convolution
//Returns: Nothing
void convolute(Image* srcImage,Image* destImage,Matrix algorithm){
int row,pix,bit,span;
span=srcImage->bpp*srcImage->bpp;
for (row=0;row<srcImage->height;row++){
for (pix=0;pix<srcImage->width;pix++){
for (bit=0;bit<srcImage->bpp;bit++){
destImage->data[Index(pix,row,srcImage->width,bit,srcImage->bpp)]=getPixelValue(srcImage,pix,row,bit,algorithm);
}
}
void convolute(Image* srcImage, Image* destImage, Matrix algorithm){
int threads = g_threads;
if (threads < 1) threads = 1;
if (threads > srcImage->height) threads = srcImage->height; // avoid empty workers

pthread_t *ts = (pthread_t*)malloc(sizeof(pthread_t) * threads);
WorkerArgs *arg = (WorkerArgs*)malloc(sizeof(WorkerArgs) * threads);

int rows = srcImage->height;
int base = rows / threads;
int rem = rows % threads;
int row = 0;

for (int i = 0; i < threads; i++) {
int take = base + (i < rem ? 1 : 0);
arg[i].start_row = row;
arg[i].end_row = row + take;
arg[i].src = srcImage;
arg[i].dst = destImage;
memcpy(arg[i].alg, algorithm, sizeof(Matrix));
row += take;
pthread_create(&ts[i], NULL, worker, &arg[i]);
}
for (int i = 0; i < threads; i++) pthread_join(ts[i], NULL);

free(ts);
free(arg);
}



//Usage: Prints usage information for the program
//Returns: -1
int Usage(){
printf("Usage: image <filename> <type>\n\twhere type is one of (edge,sharpen,blur,gauss,emboss,identity)\n");
printf("Usage: image <filename> <type> [threads]\n\twhere type is one of (edge,sharpen,blur,gauss,emboss,identity)\n");
return -1;
}

Expand All @@ -89,34 +134,56 @@ enum KernelTypes GetKernelType(char* type){

//main:
//argv is expected to take 2 arguments. First is the source file name (can be jpg, png, bmp, tga). Second is the lower case name of the algorithm.
int main(int argc,char** argv){
long t1,t2;
t1=time(NULL);
int main(int argc, char** argv) {
struct timespec t1, t2; // precise timer

stbi_set_flip_vertically_on_load(0);
if (argc!=3) return Usage();
char* fileName=argv[1];
if (!strcmp(argv[1],"pic4.jpg")&&!strcmp(argv[2],"gauss")){

// Expected usage: image <filename> <type> [threads]
if (argc < 3 || argc > 4) return Usage();
if (argc == 4) {
g_threads = atoi(argv[3]);
if (g_threads < 1) g_threads = 1;
}

char* fileName = argv[1];
if (!strcmp(argv[1], "pic4.jpg") && !strcmp(argv[2], "gauss")) {
printf("You have applied a gaussian filter to Gauss which has caused a tear in the time-space continum.\n");
}
enum KernelTypes type=GetKernelType(argv[2]);

Image srcImage,destImage,bwImage;
srcImage.data=stbi_load(fileName,&srcImage.width,&srcImage.height,&srcImage.bpp,0);
if (!srcImage.data){
printf("Error loading file %s.\n",fileName);
enum KernelTypes type = GetKernelType(argv[2]);

Image srcImage, destImage;
srcImage.data = stbi_load(fileName, &srcImage.width, &srcImage.height, &srcImage.bpp, 0);
if (!srcImage.data) {
printf("Error loading file %s.\n", fileName);
return -1;
}
destImage.bpp=srcImage.bpp;
destImage.height=srcImage.height;
destImage.width=srcImage.width;
destImage.data=malloc(sizeof(uint8_t)*destImage.width*destImage.bpp*destImage.height);
convolute(&srcImage,&destImage,algorithms[type]);
stbi_write_png("output.png",destImage.width,destImage.height,destImage.bpp,destImage.data,destImage.bpp*destImage.width);

destImage.bpp = srcImage.bpp;
destImage.height = srcImage.height;
destImage.width = srcImage.width;
destImage.data = malloc(sizeof(uint8_t) * destImage.width * destImage.bpp * destImage.height);

// Start timer
clock_gettime(CLOCK_MONOTONIC, &t1);

// Perform convolution (multi-threaded)
convolute(&srcImage, &destImage, algorithms[type]);

// Stop timer
clock_gettime(CLOCK_MONOTONIC, &t2);

// Compute elapsed time in seconds (with fractional precision)
double seconds = (t2.tv_sec - t1.tv_sec) + (t2.tv_nsec - t1.tv_nsec) / 1e9;

// Save output image
stbi_write_png("output.png", destImage.width, destImage.height,
destImage.bpp, destImage.data, destImage.bpp * destImage.width);

stbi_image_free(srcImage.data);

free(destImage.data);
t2=time(NULL);
printf("Took %ld seconds\n",t2-t1);
return 0;
}

printf("Took %.3f seconds using %d thread(s)\n", seconds, g_threads);
return 0;
}
178 changes: 178 additions & 0 deletions image_openmp.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
#define _POSIX_C_SOURCE 199309L
#include <stdio.h>
#include <stdint.h>
#include <time.h>
#include <string.h>
#include "image.h"
#include <pthread.h>
#include <omp.h>

#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"

#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image_write.h"

int g_threads = 4;

//An array of kernel matrices to be used for image convolution.
//The indexes of these match the enumeration from the header file. ie. algorithms[BLUR] returns the kernel corresponding to a box blur.
Matrix algorithms[]={
{{0,-1,0},{-1,4,-1},{0,-1,0}},
{{0,-1,0},{-1,5,-1},{0,-1,0}},
{{1/9.0,1/9.0,1/9.0},{1/9.0,1/9.0,1/9.0},{1/9.0,1/9.0,1/9.0}},
{{1.0/16,1.0/8,1.0/16},{1.0/8,1.0/4,1.0/8},{1.0/16,1.0/8,1.0/16}},
{{-2,-1,0},{-1,1,1},{0,1,2}},
{{0,0,0},{0,1,0},{0,0,0}}
};

typedef struct {
int start_row; // inclusive
int end_row; // exclusive
Image *src;
Image *dst;
Matrix alg;
} WorkerArgs;

static void* worker(void* ap) {
WorkerArgs *a = (WorkerArgs*)ap;
for (int row = a->start_row; row < a->end_row; row++) {
for (int pix = 0; pix < a->src->width; pix++) {
for (int bit = 0; bit < a->src->bpp; bit++) {
a->dst->data[Index(pix,row,a->src->width,bit,a->src->bpp)] =
getPixelValue(a->src, pix, row, bit, a->alg);
}
}
}
return NULL;
}



//getPixelValue - Computes the value of a specific pixel on a specific channel using the selected convolution kernel
//Paramters: srcImage: An Image struct populated with the image being convoluted
// x: The x coordinate of the pixel
// y: The y coordinate of the pixel
// bit: The color channel being manipulated
// algorithm: The 3x3 kernel matrix to use for the convolution
//Returns: The new value for this x,y pixel and bit channel
uint8_t getPixelValue(Image* srcImage,int x,int y,int bit,Matrix algorithm){
int px,mx,py,my,i,span;
span=srcImage->width*srcImage->bpp;
// for the edge pixes, just reuse the edge pixel
px=x+1; py=y+1; mx=x-1; my=y-1;
if (mx<0) mx=0;
if (my<0) my=0;
if (px>=srcImage->width) px=srcImage->width-1;
if (py>=srcImage->height) py=srcImage->height-1;
uint8_t result=
algorithm[0][0]*srcImage->data[Index(mx,my,srcImage->width,bit,srcImage->bpp)]+
algorithm[0][1]*srcImage->data[Index(x,my,srcImage->width,bit,srcImage->bpp)]+
algorithm[0][2]*srcImage->data[Index(px,my,srcImage->width,bit,srcImage->bpp)]+
algorithm[1][0]*srcImage->data[Index(mx,y,srcImage->width,bit,srcImage->bpp)]+
algorithm[1][1]*srcImage->data[Index(x,y,srcImage->width,bit,srcImage->bpp)]+
algorithm[1][2]*srcImage->data[Index(px,y,srcImage->width,bit,srcImage->bpp)]+
algorithm[2][0]*srcImage->data[Index(mx,py,srcImage->width,bit,srcImage->bpp)]+
algorithm[2][1]*srcImage->data[Index(x,py,srcImage->width,bit,srcImage->bpp)]+
algorithm[2][2]*srcImage->data[Index(px,py,srcImage->width,bit,srcImage->bpp)];
return result;
}

//convolute: Applies a kernel matrix to an image
//Parameters: srcImage: The image being convoluted
// destImage: A pointer to a pre-allocated (including space for the pixel array) structure to receive the convoluted image. It should be the same size as srcImage
// algorithm: The kernel matrix to use for the convolution
//Returns: Nothing
void convolute(Image* srcImage, Image* destImage, Matrix algorithm){
// Parallelize the outer (row) loop. Each thread writes distinct rows => no races.
#pragma omp parallel for schedule(static)
for (int row = 0; row < srcImage->height; row++) {
for (int pix = 0; pix < srcImage->width; pix++) {
for (int bit = 0; bit < srcImage->bpp; bit++) {
destImage->data[Index(pix,row,srcImage->width,bit,srcImage->bpp)] =
getPixelValue(srcImage, pix, row, bit, algorithm);
}
}
}
}




//Usage: Prints usage information for the program
//Returns: -1
int Usage(){
printf("Usage: image <filename> <type> [threads]\n\twhere type is one of (edge,sharpen,blur,gauss,emboss,identity)\n");
return -1;
}

//GetKernelType: Converts the string name of a convolution into a value from the KernelTypes enumeration
//Parameters: type: A string representation of the type
//Returns: an appropriate entry from the KernelTypes enumeration, defaults to IDENTITY, which does nothing but copy the image.
enum KernelTypes GetKernelType(char* type){
if (!strcmp(type,"edge")) return EDGE;
else if (!strcmp(type,"sharpen")) return SHARPEN;
else if (!strcmp(type,"blur")) return BLUR;
else if (!strcmp(type,"gauss")) return GAUSE_BLUR;
else if (!strcmp(type,"emboss")) return EMBOSS;
else return IDENTITY;
}

//main:
//argv is expected to take 2 arguments. First is the source file name (can be jpg, png, bmp, tga). Second is the lower case name of the algorithm.
int main(int argc, char** argv) {
struct timespec t1, t2; // precise timer

stbi_set_flip_vertically_on_load(0);

// Expected usage: image <filename> <type> [threads]
if (argc < 3 || argc > 4) return Usage();
if (argc == 4) {
g_threads = atoi(argv[3]);
if (g_threads < 1) g_threads = 1;
}

omp_set_num_threads(g_threads);


char* fileName = argv[1];
if (!strcmp(argv[1], "pic4.jpg") && !strcmp(argv[2], "gauss")) {
printf("You have applied a gaussian filter to Gauss which has caused a tear in the time-space continum.\n");
}

enum KernelTypes type = GetKernelType(argv[2]);

Image srcImage, destImage;
srcImage.data = stbi_load(fileName, &srcImage.width, &srcImage.height, &srcImage.bpp, 0);
if (!srcImage.data) {
printf("Error loading file %s.\n", fileName);
return -1;
}

destImage.bpp = srcImage.bpp;
destImage.height = srcImage.height;
destImage.width = srcImage.width;
destImage.data = malloc(sizeof(uint8_t) * destImage.width * destImage.bpp * destImage.height);

// Start timer
clock_gettime(CLOCK_MONOTONIC, &t1);

// Perform convolution (multi-threaded)
convolute(&srcImage, &destImage, algorithms[type]);

// Stop timer
clock_gettime(CLOCK_MONOTONIC, &t2);

// Compute elapsed time in seconds (with fractional precision)
double seconds = (t2.tv_sec - t1.tv_sec) + (t2.tv_nsec - t1.tv_nsec) / 1e9;

// Save output image
stbi_write_png("output.png", destImage.width, destImage.height,
destImage.bpp, destImage.data, destImage.bpp * destImage.width);

stbi_image_free(srcImage.data);
free(destImage.data);

printf("Took %.3f seconds using %d thread(s)\n", seconds, g_threads);
return 0;
}
24 changes: 21 additions & 3 deletions makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,23 @@
CC = gcc
CFLAGS = -g
LDFLAGS = -lm

image:image.c image.h
gcc -g image.c -o image -lm
# Default target
all: image image_pthread

# Serial version
image: image.c image.h
$(CC) $(CFLAGS) image.c -o image $(LDFLAGS)

# Pthread version
image_pthread: image.c image.h
$(CC) -O2 -g image.c -o image_pthread -lm -lpthread

image_openmp: image_openmp.c image.h
$(CC) -O2 -g image_openmp.c -o image_openmp -fopenmp -lm



# Clean up binaries and outputs
clean:
rm -f image output.png
rm -f image image_pthread output.png image_openmp