From efe600537740bb572f4a062ab6b9df12623e0c24 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 22 Jun 2009 21:56:19 +0000 Subject: Finish asynchronous PNG interface, and test it in png test. --- libdimension/dimension/progress.h | 11 +++- libdimension/png.c | 124 +++++++++++++++++++++++++++++--------- libdimension/progress.c | 84 +++++++++++++++++++++++--- tests/png.c | 89 ++++++++++++++++++++++----- 4 files changed, 254 insertions(+), 54 deletions(-) diff --git a/libdimension/dimension/progress.h b/libdimension/dimension/progress.h index d436b5d..194ae83 100644 --- a/libdimension/dimension/progress.h +++ b/libdimension/dimension/progress.h @@ -36,6 +36,10 @@ typedef struct { /* The worker thread */ pthread_t thread; + + /* Condition variable for waiting for a particular amount of progress */ + pthread_cond_t cond; + pthread_mutex_t mutex; } dmnsn_progress; dmnsn_progress *dmnsn_new_progress(); @@ -47,7 +51,10 @@ int dmnsn_finish_progress(dmnsn_progress *progress); double dmnsn_get_progress(const dmnsn_progress* progress); -void dmnsn_new_progress_element(dmnsn_progress* progress, unsigned int total); -void dmnsn_increment_progress(dmnsn_progress* progress); +void dmnsn_new_progress_element(dmnsn_progress *progress, unsigned int total); +void dmnsn_increment_progress(dmnsn_progress *progress); +void dmnsn_progress_done(dmnsn_progress *progress); + +void dmnsn_wait_progress(dmnsn_progress *progress, double prog); #endif /* DIMENSION_PROGRESS_H */ diff --git a/libdimension/png.c b/libdimension/png.c index 551bac8..b8cfabc 100644 --- a/libdimension/png.c +++ b/libdimension/png.c @@ -38,10 +38,7 @@ typedef struct { } dmnsn_png_read_payload; static void *dmnsn_png_write_canvas_thread(void *ptr); -static int dmnsn_png_write_canvas_impl(const dmnsn_canvas *canvas, FILE *file); - static void *dmnsn_png_read_canvas_thread(void *ptr); -static dmnsn_canvas *dmnsn_png_read_canvas_impl(FILE *file); /* Write a canvas to a png file, using libpng. Return 0 on success, nonzero on failure. */ @@ -75,6 +72,8 @@ dmnsn_png_write_canvas_async(const dmnsn_canvas *canvas, FILE *file) != 0) { dmnsn_error(DMNSN_SEVERITY_MEDIUM, "Creating png writing worker thread failed."); + dmnsn_delete_progress(progress); + return NULL; } } @@ -114,25 +113,48 @@ dmnsn_png_read_canvas_async(dmnsn_canvas **canvas, FILE *file) != 0) { dmnsn_error(DMNSN_SEVERITY_MEDIUM, "Creating png writing worker thread failed."); + dmnsn_delete_progress(progress); + return NULL; } } return progress; } +static int dmnsn_png_write_canvas_impl(dmnsn_progress *progress, + const dmnsn_canvas *canvas, FILE *file); +static dmnsn_canvas *dmnsn_png_read_canvas_impl(dmnsn_progress *progress, + FILE *file); static void * dmnsn_png_write_canvas_thread(void *ptr) { dmnsn_png_write_payload *payload = ptr; int *retval = malloc(sizeof(int)); if (retval) { - *retval = dmnsn_png_write_canvas_impl(payload->canvas, payload->file); + *retval = dmnsn_png_write_canvas_impl(payload->progress, + payload->canvas, payload->file); + } + dmnsn_progress_done(payload->progress); + return retval; +} + +static void * +dmnsn_png_read_canvas_thread(void *ptr) +{ + dmnsn_png_read_payload *payload = ptr; + int *retval = malloc(sizeof(int)); + if (retval) { + *payload->canvas = dmnsn_png_read_canvas_impl(payload->progress, + payload->file); + *retval = payload->canvas ? 0 : 1; } + dmnsn_progress_done(payload->progress); return retval; } static int -dmnsn_png_write_canvas_impl(const dmnsn_canvas *canvas, FILE *file) +dmnsn_png_write_canvas_impl(dmnsn_progress *progress, + const dmnsn_canvas *canvas, FILE *file) { png_structp png_ptr; png_infop info_ptr; @@ -147,6 +169,8 @@ dmnsn_png_write_canvas_impl(const dmnsn_canvas *canvas, FILE *file) width = canvas->x; height = canvas->y; + dmnsn_new_progress_element(progress, height); + png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (!png_ptr) return 1; /* Couldn't create libpng write struct */ @@ -231,6 +255,7 @@ dmnsn_png_write_canvas_impl(const dmnsn_canvas *canvas, FILE *file) /* Write the row */ png_write_row(png_ptr, (png_bytep)row); + dmnsn_increment_progress(progress); } /* Finish the PNG file */ @@ -241,27 +266,25 @@ dmnsn_png_write_canvas_impl(const dmnsn_canvas *canvas, FILE *file) return 0; } -static void * -dmnsn_png_read_canvas_thread(void *ptr) -{ - dmnsn_png_read_payload *payload = ptr; - int *retval = malloc(sizeof(int)); - if (retval) { - *payload->canvas = dmnsn_png_read_canvas_impl(payload->file); - *retval = payload->canvas ? 1 : 0; - } - return retval; -} +/* Thread-specific pointer to the appropriate dmnsn_progress* for + dmnsn_png_read_row_callback */ +static pthread_key_t progress_key; +static pthread_mutex_t progress_mutex = PTHREAD_MUTEX_INITIALIZER; +static int progress_key_init = 0; + +static void dmnsn_png_read_row_callback(png_structp png_ptr, png_uint_32 row, + int pass); static dmnsn_canvas * -dmnsn_png_read_canvas_impl(FILE *file) +dmnsn_png_read_canvas_impl(dmnsn_progress *progress, FILE *file) { dmnsn_canvas *canvas; png_byte header[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; png_structp png_ptr; png_infop info_ptr; png_uint_32 width, height, rowbytes; - int bit_depth, color_type, interlace_type, compression_type, filter_method; + int bit_depth, color_type, interlace_type, compression_type, filter_method, + number_of_passes; png_bytep image = NULL; png_bytep *row_pointers = NULL; unsigned int x, y; @@ -269,10 +292,38 @@ dmnsn_png_read_canvas_impl(FILE *file) dmnsn_sRGB sRGB; png_bytep png_pixel; + /* Initialize/set progress_key */ + + if (pthread_mutex_lock(&progress_mutex) != 0) { + dmnsn_error(DMNSN_SEVERITY_MEDIUM, + "Couldn't lock thread-specific pointer mutex."); + } + + if (progress_key_init == 0) { + if (pthread_key_create(&progress_key, NULL) != 0) { + /* High severity because dmnsn_png_read_row_callback will surely segfault + if it can't get the dmnsn_progress* from the key */ + dmnsn_error(DMNSN_SEVERITY_HIGH, + "Couldn't create thread-specific pointer."); + } + + progress_key_init = 1; + } + + if (pthread_setspecific(progress_key, progress) != 0) { + dmnsn_error(DMNSN_SEVERITY_HIGH, "Couldn't set thread-specific pointer."); + } + + if (pthread_mutex_unlock(&progress_mutex) != 0) { + dmnsn_error(DMNSN_SEVERITY_MEDIUM, + "Couldn't unlock thread-specific pointer mutex."); + } + if (!file) return NULL; /* file was NULL */ fread(header, 1, 8, file); - if (png_sig_cmp(header, 0, 8)) return NULL; /* file is not a PNG file */ + if (png_sig_cmp(header, 0, 8)) return NULL; /* file is not a PNG file, or the + read failed */ /* Create the libpng read struct */ png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); @@ -305,6 +356,10 @@ dmnsn_png_read_canvas_impl(FILE *file) /* Get useful information from the info struct */ png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, &interlace_type, &compression_type, &filter_method); + number_of_passes = png_set_interlace_handling(png_ptr); + + dmnsn_new_progress_element(progress, (number_of_passes + 1)*height); + png_set_read_status_fn(png_ptr, &dmnsn_png_read_row_callback); /* * - Convert paletted images to RGB. @@ -350,8 +405,8 @@ dmnsn_png_read_canvas_impl(FILE *file) row_pointers[y] = image + y*rowbytes; } - /* Read the image to image all at once. At the expense of greater memory use, - this handles interlacing for us. */ + /* Read the image to memory all at once. At the expense of greater memory + use, this handles interlacing for us. */ png_read_image(png_ptr, row_pointers); /* Allocate the canvas */ @@ -370,8 +425,8 @@ dmnsn_png_read_canvas_impl(FILE *file) loops, although that doesn't really matter for a decent compiler. */ if (bit_depth == 16) { if (color_type & PNG_COLOR_MASK_ALPHA) { - for (x = 0; x < width; ++x) { - for (y = 0; y < height; ++y) { + for (y = 0; y < height; ++y) { + for (x = 0; x < width; ++x) { png_pixel = image + 8*(y*width + x); sRGB.R = ((double)((png_pixel[0] << UINT16_C(8)) + png_pixel[1])) @@ -386,10 +441,11 @@ dmnsn_png_read_canvas_impl(FILE *file) + png_pixel[7]))/UINT16_MAX; dmnsn_set_pixel(canvas, x, height - y - 1, color); } + dmnsn_increment_progress(progress); } } else { - for (x = 0; x < width; ++x) { - for (y = 0; y < height; ++y) { + for (y = 0; y < height; ++y) { + for (x = 0; x < width; ++x) { png_pixel = image + 6*(y*width + x); sRGB.R = ((double)((png_pixel[0] << UINT16_C(8)) + png_pixel[1])) @@ -402,13 +458,14 @@ dmnsn_png_read_canvas_impl(FILE *file) color = dmnsn_color_from_sRGB(sRGB); dmnsn_set_pixel(canvas, x, height - y - 1, color); } + dmnsn_increment_progress(progress); } } } else { /* Bit depth is 8 */ if (color_type & PNG_COLOR_MASK_ALPHA) { - for (x = 0; x < width; ++x) { - for (y = 0; y < height; ++y) { + for (y = 0; y < height; ++y) { + for (x = 0; x < width; ++x) { png_pixel = image + 4*(y*width + x); sRGB.R = ((double)png_pixel[0])/UINT8_MAX; @@ -419,10 +476,11 @@ dmnsn_png_read_canvas_impl(FILE *file) color.trans = ((double)png_pixel[3])/UINT8_MAX; dmnsn_set_pixel(canvas, x, height - y - 1, color); } + dmnsn_increment_progress(progress); } } else { - for (x = 0; x < width; ++x) { - for (y = 0; y < height; ++y) { + for (y = 0; y < height; ++y) { + for (x = 0; x < width; ++x) { png_pixel = image + 3*(y*width + x); sRGB.R = ((double)png_pixel[0])/UINT8_MAX; @@ -432,6 +490,7 @@ dmnsn_png_read_canvas_impl(FILE *file) color = dmnsn_color_from_sRGB(sRGB); dmnsn_set_pixel(canvas, x, height - y - 1, color); } + dmnsn_increment_progress(progress); } } } @@ -442,3 +501,10 @@ dmnsn_png_read_canvas_impl(FILE *file) png_destroy_read_struct(&png_ptr, &info_ptr, NULL); return canvas; } + +static void +dmnsn_png_read_row_callback(png_structp png_ptr, png_uint_32 row, int pass) +{ + dmnsn_progress *progress = pthread_getspecific(progress_key); + dmnsn_increment_progress(progress); +} \ No newline at end of file diff --git a/libdimension/progress.c b/libdimension/progress.c index e072f5d..aa6891c 100644 --- a/libdimension/progress.c +++ b/libdimension/progress.c @@ -25,9 +25,19 @@ dmnsn_progress * dmnsn_new_progress() { + dmnsn_progress_element element = { .progress = 0, .total = 1 }; dmnsn_progress *progress = malloc(sizeof(dmnsn_progress)); if (progress) { progress->elements = dmnsn_new_array(sizeof(dmnsn_progress_element)); + dmnsn_array_push(progress->elements, &element); + + if (pthread_cond_init(&progress->cond, NULL) != 0) { + dmnsn_error(DMNSN_SEVERITY_MEDIUM, + "Couldn't initialize condition variable."); + } + if (pthread_mutex_init(&progress->mutex, NULL) != 0) { + dmnsn_error(DMNSN_SEVERITY_MEDIUM, "Couldn't initialize mutex."); + } } return progress; } @@ -36,6 +46,13 @@ void dmnsn_delete_progress(dmnsn_progress *progress) { if (progress) { + if (pthread_mutex_destroy(&progress->mutex) != 0) { + dmnsn_error(DMNSN_SEVERITY_LOW, "Leaking mutex."); + } + if (pthread_cond_destroy(&progress->cond) != 0) { + dmnsn_error(DMNSN_SEVERITY_LOW, "Leaking condition variable."); + } + dmnsn_delete_array(progress->elements); free(progress); } @@ -47,6 +64,10 @@ int dmnsn_finish_progress(dmnsn_progress *progress) int retval = 1; if (progress) { + if (pthread_cond_broadcast(&progress->cond) != 0) { + dmnsn_error(DMNSN_SEVERITY_MEDIUM, "Couldn't signal condition variable."); + } + if (pthread_join(progress->thread, &ptr) != 0) { /* Medium severity because an unjoined thread likely means that the thread is incomplete or invalid */ @@ -62,7 +83,7 @@ int dmnsn_finish_progress(dmnsn_progress *progress) } double -dmnsn_get_progress(const dmnsn_progress* progress) +dmnsn_get_progress(const dmnsn_progress *progress) { dmnsn_progress_element *element; double prog = 0.0; @@ -81,23 +102,68 @@ dmnsn_get_progress(const dmnsn_progress* progress) } void -dmnsn_new_progress_element(dmnsn_progress* progress, unsigned int total) +dmnsn_new_progress_element(dmnsn_progress *progress, unsigned int total) { dmnsn_progress_element element = { .progress = 0, .total = total }; dmnsn_array_push(progress->elements, &element); } void -dmnsn_increment_progress(dmnsn_progress* progress) +dmnsn_increment_progress(dmnsn_progress *progress) { dmnsn_progress_element *element; dmnsn_array_wrlock(progress->elements); - element = dmnsn_array_at(progress->elements, progress->elements->length - 1); - ++element->progress; - if (element->progress >= element->total) { - dmnsn_array_resize_unlocked(progress->elements, - progress->elements->length - 1); - } + element = dmnsn_array_at(progress->elements, progress->elements->length - 1); + ++element->progress; + + while (element->progress >= element->total + && progress->elements->length > 1) { + dmnsn_array_resize_unlocked(progress->elements, + progress->elements->length - 1); + element = dmnsn_array_at(progress->elements, progress->elements->length - 1); + ++element->progress; + } + + if (pthread_cond_broadcast(&progress->cond) != 0) { + dmnsn_error(DMNSN_SEVERITY_MEDIUM, "Couldn't signal condition variable."); + } dmnsn_array_unlock(progress->elements); } + +/* Immediately set to 100% completion */ +void +dmnsn_progress_done(dmnsn_progress *progress) +{ + dmnsn_progress_element *element; + + dmnsn_array_wrlock(progress->elements); + dmnsn_array_resize_unlocked(progress->elements, 1); + element = dmnsn_array_at(progress->elements, progress->elements->length - 1); + element->progress = element->total; + + if (pthread_cond_broadcast(&progress->cond) != 0) { + dmnsn_error(DMNSN_SEVERITY_MEDIUM, "Couldn't signal condition variable."); + } + dmnsn_array_unlock(progress->elements); +} + +/* Wait until dmnsn_get_progress(progress) >= prog */ +void +dmnsn_wait_progress(dmnsn_progress *progress, double prog) +{ + if (pthread_mutex_lock(&progress->mutex) != 0) { + dmnsn_error(DMNSN_SEVERITY_MEDIUM, "Couldn't lock condition mutex."); + } + + while (dmnsn_get_progress(progress) < prog) { + if (pthread_cond_wait(&progress->cond, &progress->mutex) != 0) { + dmnsn_error(DMNSN_SEVERITY_MEDIUM, + "Couldn't wait on condition variable."); + } + } + + if (pthread_mutex_unlock(&progress->mutex) != 0) { + dmnsn_error(DMNSN_SEVERITY_MEDIUM, "Couldn't unlock condition mutex."); + } +} diff --git a/tests/png.c b/tests/png.c index f8c81b8..213e176 100644 --- a/tests/png.c +++ b/tests/png.c @@ -23,8 +23,11 @@ #include #include -int main() { +int +main() { dmnsn_canvas *canvas; + dmnsn_progress *progress; + double prog; dmnsn_color color; dmnsn_CIE_xyY xyY; dmnsn_CIE_Lab Lab; @@ -42,12 +45,6 @@ int main() { return EXIT_FAILURE; } - ofile = fopen("dimension1.png", "wb"); - if (!ofile) { - fprintf(stderr, "--- Opening 'dimension1.png' for writing failed! ---\n"); - return EXIT_FAILURE; - } - for (i = 0; i < x; ++i) { for (j = 0; j < y; ++j) { /* @@ -106,37 +103,101 @@ int main() { } } - if (dmnsn_png_write_canvas(canvas, ofile)) { - fprintf(stderr, "--- Writing canvas failed! ---\n"); + ofile = fopen("dimension1.png", "wb"); + if (!ofile) { + fprintf(stderr, "--- Opening 'dimension1.png' for writing failed! ---\n"); + dmnsn_delete_canvas(canvas); + return EXIT_FAILURE; + } + + progress = dmnsn_png_write_canvas_async(canvas, ofile); + if (!progress) { + fprintf(stderr, "--- Creating PNG writing worker thread failed! ---\n"); + fclose(ofile); + dmnsn_delete_canvas(canvas); return EXIT_FAILURE; } - dmnsn_delete_canvas(canvas); + /* Give an ellipsis progress indication */ + prog = 0.0; + while ((prog += 1.0/10.0) < 1.0) { + dmnsn_wait_progress(progress, prog); + printf("."); + fflush(stdout); + } + printf("\n"); + + if (dmnsn_finish_progress(progress) != 0) { + fprintf(stderr, "--- Writing canvas to PNG failed! ---\n"); + fclose(ofile); + dmnsn_delete_canvas(canvas); + return EXIT_FAILURE; + } fclose(ofile); + dmnsn_delete_canvas(canvas); + ifile = fopen("dimension1.png", "rb"); if (!ifile) { fprintf(stderr, "--- Opening 'dimension1.png' for reading failed! ---\n"); return EXIT_FAILURE; } - if (!(canvas = dmnsn_png_read_canvas(ifile))) { - fprintf(stderr, "--- Reading canvas failed! ---\n"); + progress = dmnsn_png_read_canvas_async(&canvas, ifile); + if (!progress) { + fprintf(stderr, "--- Creating PNG reading worker thread failed! ---\n"); + fclose(ifile); + return EXIT_FAILURE; + } + + /* Give an ellipsis progress indication */ + prog = 0.0; + while ((prog += 1.0/10.0) < 1.0) { + dmnsn_wait_progress(progress, prog); + printf("."); + fflush(stdout); + } + printf("\n"); + + if (dmnsn_finish_progress(progress) != 0) { + fprintf(stderr, "--- Reading canvas from PNG failed! ---\n"); + fclose(ifile); return EXIT_FAILURE; } fclose(ifile); + ofile = fopen("dimension2.png", "wb"); if (!ofile) { fprintf(stderr, "--- Opening 'dimension2.png' for writing failed! ---\n"); return EXIT_FAILURE; } - if (dmnsn_png_write_canvas(canvas, ofile)) { - fprintf(stderr, "--- Writing canvas failed! ---\n"); + progress = dmnsn_png_write_canvas_async(canvas, ofile); + if (!progress) { + fprintf(stderr, "--- Creating PNG writing worker thread failed! ---\n"); + fclose(ofile); + dmnsn_delete_canvas(canvas); return EXIT_FAILURE; } + /* Give an ellipsis progress indication */ + prog = 0.0; + while ((prog += 1.0/10.0) < 1.0) { + dmnsn_wait_progress(progress, prog); + printf("."); + fflush(stdout); + } + printf("\n"); + + if (dmnsn_finish_progress(progress) != 0) { + fprintf(stderr, "--- Writing canvas to PNG failed! ---\n"); + fclose(ofile); + dmnsn_delete_canvas(canvas); + return EXIT_FAILURE; + } + + fclose(ofile); dmnsn_delete_canvas(canvas); return EXIT_SUCCESS; } -- cgit v1.2.3