diff --git a/image.c b/image.c index 2573a5b..0364f2d 100644 --- a/image.c +++ b/image.c @@ -1,8 +1,10 @@ +#define _POSIX_C_SOURCE 199309L #include #include #include #include #include "image.h" +#include #define STB_IMAGE_IMPLEMENTATION #include "stb_image.h" @@ -10,6 +12,8 @@ #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[]={ @@ -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 @@ -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;rowheight;row++){ - for (pix=0;pixwidth;pix++){ - for (bit=0;bitbpp;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 \n\twhere type is one of (edge,sharpen,blur,gauss,emboss,identity)\n"); + printf("Usage: image [threads]\n\twhere type is one of (edge,sharpen,blur,gauss,emboss,identity)\n"); return -1; } @@ -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 [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; -} \ No newline at end of file + + printf("Took %.3f seconds using %d thread(s)\n", seconds, g_threads); + return 0; +} diff --git a/image_openmp.c b/image_openmp.c new file mode 100644 index 0000000..2505323 --- /dev/null +++ b/image_openmp.c @@ -0,0 +1,178 @@ +#define _POSIX_C_SOURCE 199309L +#include +#include +#include +#include +#include "image.h" +#include +#include + +#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 [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 [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; +} diff --git a/makefile b/makefile index e60b05d..8c5c276 100644 --- a/makefile +++ b/makefile @@ -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 \ No newline at end of file + rm -f image image_pthread output.png image_openmp