diff --git a/configure.ac b/configure.ac index e0f860a7..ad67fc91 100644 --- a/configure.ac +++ b/configure.ac @@ -71,9 +71,9 @@ dnl 6. If any interfaces have been removed or changed since the last dnl public release, then set age to 0. dnl dnl libwraster -WRASTER_CURRENT=7 +WRASTER_CURRENT=8 WRASTER_REVISION=0 -WRASTER_AGE=1 +WRASTER_AGE=2 WRASTER_VERSION=$WRASTER_CURRENT:$WRASTER_REVISION:$WRASTER_AGE AC_SUBST(WRASTER_VERSION) dnl diff --git a/wrlib/ChangeLog b/wrlib/ChangeLog index d6d99681..21e13784 100644 --- a/wrlib/ChangeLog +++ b/wrlib/ChangeLog @@ -1,3 +1,5 @@ +- added RSaveRawImage() + - added RSaveTitledImage() - removed obsoleted RDestroyImage() diff --git a/wrlib/imgformat.h b/wrlib/imgformat.h index 209b375f..a903bc3f 100644 --- a/wrlib/imgformat.h +++ b/wrlib/imgformat.h @@ -90,10 +90,12 @@ Bool RSaveXPM(RImage *image, const char *filename); #ifdef USE_PNG Bool RSavePNG(RImage *image, const char *filename, char *title); +Bool RSaveRawPNG(RImage *image, char *title, unsigned char **out_buf, size_t *out_size); #endif #ifdef USE_JPEG Bool RSaveJPEG(RImage *image, const char *filename, char *title); +Bool RSaveRawJPEG(RImage *image, char *title, unsigned char **out_buf, size_t *out_size); #endif /* diff --git a/wrlib/load.c b/wrlib/load.c index ef4e3b94..0f824201 100644 --- a/wrlib/load.c +++ b/wrlib/load.c @@ -162,6 +162,11 @@ RImage *RLoadImage(RContext *context, const char *file, int index) assert(file != NULL); + /* just to suppress the compilation warning as index is only used with TIFF and GIF */ +#if !defined(USE_TIFF) && !defined(USE_GIF) + (void)index; +#endif + if (RImageCacheSize < 0) init_cache(); diff --git a/wrlib/save.c b/wrlib/save.c index eee8ce54..840f002d 100644 --- a/wrlib/save.c +++ b/wrlib/save.c @@ -3,7 +3,7 @@ * Raster graphics library * * Copyright (c) 1998-2003 Alfredo K. Kojima - * Copyright (c) 2013-2023 Window Maker Team + * Copyright (c) 2013-2025 Window Maker Team * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public @@ -40,6 +40,25 @@ Bool RSaveImage(RImage *image, const char *filename, const char *format) return RSaveTitledImage(image, filename, format, NULL); } +Bool RSaveRawImage(RImage *image, const char *format, unsigned char **out_buf, size_t *out_size) +{ +#ifdef USE_PNG + if (strcasecmp(format, "PNG") == 0) + return RSaveRawPNG(image, NULL, out_buf, out_size); +#endif + +#ifdef USE_JPEG + if (strcasecmp(format, "JPG") == 0) + return RSaveRawJPEG(image, NULL, out_buf, out_size); + + if (strcasecmp(format, "JPEG") == 0) + return RSaveRawJPEG(image, NULL, out_buf, out_size); +#endif + + RErrorCode = RERR_BADFORMAT; + return False; +} + Bool RSaveTitledImage(RImage *image, const char *filename, const char *format, char *title) { #ifdef USE_PNG diff --git a/wrlib/save_jpeg.c b/wrlib/save_jpeg.c index 5320ba47..33a6d178 100644 --- a/wrlib/save_jpeg.c +++ b/wrlib/save_jpeg.c @@ -2,7 +2,7 @@ * * Raster graphics library * - * Copyright (c) 2023 Window Maker Team + * Copyright (c) 2023-2025 Window Maker Team * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public @@ -33,39 +33,92 @@ #include "imgformat.h" #include "wr_i18n.h" +/* Structure for JPEG memory destination */ +struct jpeg_mem_data { + unsigned char **out_buf; + size_t *out_size; + unsigned char *buffer; + size_t buffer_size; +}; + +/* JPEG memory destination methods */ +static void jpeg_init_mem_destination(j_compress_ptr cinfo) +{ + struct jpeg_mem_data *dest = (struct jpeg_mem_data *)cinfo->client_data; + dest->buffer_size = 32768; /* Initial buffer size */ + dest->buffer = malloc(dest->buffer_size); + if (!dest->buffer) { + /* Memory allocation failed - will be caught by caller */ + dest->buffer_size = 0; + return; + } + cinfo->dest->next_output_byte = dest->buffer; + cinfo->dest->free_in_buffer = dest->buffer_size; +} + +static boolean jpeg_empty_mem_output_buffer(j_compress_ptr cinfo) +{ + struct jpeg_mem_data *dest = (struct jpeg_mem_data *)cinfo->client_data; + size_t old_size = dest->buffer_size; + dest->buffer_size *= 2; + dest->buffer = realloc(dest->buffer, dest->buffer_size); + if (!dest->buffer) { + /* Memory allocation failed - signal error */ + dest->buffer_size = 0; + return FALSE; + } + cinfo->dest->next_output_byte = dest->buffer + old_size; + cinfo->dest->free_in_buffer = dest->buffer_size - old_size; + return TRUE; +} + +static void jpeg_term_mem_destination(j_compress_ptr cinfo) +{ + struct jpeg_mem_data *dest = (struct jpeg_mem_data *)cinfo->client_data; + *dest->out_size = dest->buffer_size - cinfo->dest->free_in_buffer; + *dest->out_buf = dest->buffer; + /* Don't free dest->buffer here - caller will free it */ +} + /* - * Save RImage to JPEG image + * Save RImage to JPEG data in memory */ -Bool RSaveJPEG(RImage *img, const char *filename, char *title) +Bool RSaveRawJPEG(RImage *img, char *title, unsigned char **out_buf, size_t *out_size) { - FILE *file; - int x, y, img_depth; + int x, y; char *buffer; RColor pixel; struct jpeg_compress_struct cinfo; struct jpeg_error_mgr jerr; + struct jpeg_destination_mgr jpeg_dest; + struct jpeg_mem_data mem_data; JSAMPROW row_pointer; - file = fopen(filename, "wb"); - if (!file) { - RErrorCode = RERR_OPEN; - return False; - } - - if (img->format == RRGBAFormat) - img_depth = 4; - else - img_depth = 3; + *out_buf = NULL; + *out_size = 0; /* collect separate RGB values to a buffer */ buffer = malloc(sizeof(char) * 3 * img->width * img->height); + if (!buffer) { + RErrorCode = RERR_NOMEMORY; + return False; + } + for (y = 0; y < img->height; y++) { for (x = 0; x < img->width; x++) { RGetPixel(img, x, y, &pixel); - buffer[y*img->width*3+x*3+0] = (char)(pixel.red); - buffer[y*img->width*3+x*3+1] = (char)(pixel.green); - buffer[y*img->width*3+x*3+2] = (char)(pixel.blue); + /* Handle transparent pixels by converting them to white + * since JPEG doesn't support transparency */ + if (pixel.alpha == 0) { + buffer[y*img->width*3+x*3+0] = (char)255; /* white red */ + buffer[y*img->width*3+x*3+1] = (char)255; /* white green */ + buffer[y*img->width*3+x*3+2] = (char)255; /* white blue */ + } else { + buffer[y*img->width*3+x*3+0] = (char)(pixel.red); + buffer[y*img->width*3+x*3+1] = (char)(pixel.green); + buffer[y*img->width*3+x*3+2] = (char)(pixel.blue); + } } } @@ -74,7 +127,17 @@ Bool RSaveJPEG(RImage *img, const char *filename, char *title) /* Initialize cinfo structure */ jpeg_create_compress(&cinfo); - jpeg_stdio_dest(&cinfo, file); + + /* Set up custom memory destination */ + mem_data.out_buf = out_buf; + mem_data.out_size = out_size; + jpeg_dest.init_destination = jpeg_init_mem_destination; + jpeg_dest.empty_output_buffer = jpeg_empty_mem_output_buffer; + jpeg_dest.term_destination = jpeg_term_mem_destination; + cinfo.dest = &jpeg_dest; + cinfo.dest->next_output_byte = NULL; + cinfo.dest->free_in_buffer = 0; + cinfo.client_data = &mem_data; cinfo.image_width = img->width; cinfo.image_height = img->height; @@ -90,15 +153,51 @@ Bool RSaveJPEG(RImage *img, const char *filename, char *title) jpeg_write_marker(&cinfo, JPEG_COM, (const JOCTET*)title, strlen(title)); while (cinfo.next_scanline < cinfo.image_height) { - row_pointer = (JSAMPROW) &buffer[cinfo.next_scanline * img_depth * img->width]; + row_pointer = (JSAMPROW) &buffer[cinfo.next_scanline * 3 * img->width]; jpeg_write_scanlines(&cinfo, &row_pointer, 1); } jpeg_finish_compress(&cinfo); + jpeg_destroy_compress(&cinfo); /* Clean */ free(buffer); - fclose(file); + + return True; +} + +/* + * Save RImage to JPEG file + */ + +Bool RSaveJPEG(RImage *img, const char *filename, char *title) +{ + FILE *file; + unsigned char *jpeg_data; + size_t jpeg_size; + size_t written; + + /* Generate JPEG data in memory */ + if (!RSaveRawJPEG(img, title, &jpeg_data, &jpeg_size)) { + return False; + } + + /* Write to file */ + file = fopen(filename, "wb"); + if (!file) { + free(jpeg_data); + RErrorCode = RERR_OPEN; + return False; + } + + written = fwrite(jpeg_data, 1, jpeg_size, file); + fclose(file); + free(jpeg_data); + + if (written != jpeg_size) { + RErrorCode = RERR_WRITE; + return False; + } return True; } diff --git a/wrlib/save_png.c b/wrlib/save_png.c index 40d3b996..c8504118 100644 --- a/wrlib/save_png.c +++ b/wrlib/save_png.c @@ -2,7 +2,7 @@ * * Raster graphics library * - * Copyright (c) 2023 Window Maker Team + * Copyright (c) 2023-2025 Window Maker Team * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public @@ -33,12 +33,51 @@ #include "imgformat.h" #include "wr_i18n.h" -/* - * Save RImage to PNG image - */ -Bool RSavePNG(RImage *img, const char *filename, char *title) +/* Structure to hold PNG data in memory */ +struct png_mem_data { + unsigned char *buffer; + size_t size; + size_t capacity; +}; + +/* Callback function to write PNG data to memory buffer */ +static void png_write_to_memory(png_structp png_ptr, png_bytep data, png_size_t length) +{ + struct png_mem_data *p = (struct png_mem_data *)png_get_io_ptr(png_ptr); + size_t new_size = p->size + length; + + /* Expand buffer if necessary */ + if (new_size > p->capacity) { + size_t new_capacity = p->capacity ? p->capacity * 2 : 8192; + while (new_capacity < new_size) + new_capacity *= 2; + + unsigned char *new_buffer = realloc(p->buffer, new_capacity); + if (!new_buffer) { + png_error(png_ptr, "Out of memory"); + return; + } + p->buffer = new_buffer; + p->capacity = new_capacity; + } + + /* Copy data to buffer */ + memcpy(p->buffer + p->size, data, length); + p->size += length; +} + +/* Dummy flush function for memory I/O */ +static void png_flush_memory(png_structp png_ptr) +{ + /* No-op for memory I/O */ + (void)png_ptr; +} + +/* + * Save RImage to PNG data in memory + */ +Bool RSaveRawPNG(RImage *img, char *title, unsigned char **out_buf, size_t *out_size) { - FILE *file; png_structp png_ptr; png_infop png_info_ptr; png_bytep png_row; @@ -46,17 +85,14 @@ Bool RSavePNG(RImage *img, const char *filename, char *title) int x, y; int width = img->width; int height = img->height; + struct png_mem_data png_data = {NULL, 0, 0}; - file = fopen(filename, "wb"); - if (file == NULL) { - RErrorCode = RERR_OPEN; - return False; - } + *out_buf = NULL; + *out_size = 0; /* Initialize write structure */ png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (png_ptr == NULL) { - fclose(file); RErrorCode = RERR_NOMEMORY; return False; } @@ -64,19 +100,22 @@ Bool RSavePNG(RImage *img, const char *filename, char *title) /* Initialize info structure */ png_info_ptr = png_create_info_struct(png_ptr); if (png_info_ptr == NULL) { - fclose(file); + png_destroy_write_struct(&png_ptr, NULL); RErrorCode = RERR_NOMEMORY; return False; } /* Setup Exception handling */ - if (setjmp(png_jmpbuf (png_ptr))) { - fclose(file); + if (setjmp(png_jmpbuf(png_ptr))) { + if (png_data.buffer) + free(png_data.buffer); + png_destroy_write_struct(&png_ptr, &png_info_ptr); RErrorCode = RERR_INTERNAL; return False; } - png_init_io(png_ptr, file); + /* Set up memory I/O */ + png_set_write_fn(png_ptr, &png_data, png_write_to_memory, png_flush_memory); /* Write header (8 bit colour depth) */ png_set_IHDR(png_ptr, png_info_ptr, width, height, 8, PNG_COLOR_TYPE_RGB, @@ -96,6 +135,13 @@ Bool RSavePNG(RImage *img, const char *filename, char *title) /* Allocate memory for one row (3 bytes per pixel - RGB) */ png_row = (png_bytep) malloc(3 * width * sizeof(png_byte)); + if (!png_row) { + if (png_data.buffer) + free(png_data.buffer); + png_destroy_write_struct(&png_ptr, &png_info_ptr); + RErrorCode = RERR_NOMEMORY; + return False; + } /* Write image data */ for (y = 0; y < height; y++) { @@ -114,8 +160,7 @@ Bool RSavePNG(RImage *img, const char *filename, char *title) /* End write */ png_write_end(png_ptr, NULL); - /* Clean */ - fclose(file); + /* Clean up structures */ if (png_info_ptr != NULL) png_free_data(png_ptr, png_info_ptr, PNG_FREE_ALL, -1); if (png_ptr != NULL) @@ -123,5 +168,54 @@ Bool RSavePNG(RImage *img, const char *filename, char *title) if (png_row != NULL) free(png_row); + /* Return the buffer */ + *out_buf = png_data.buffer; + *out_size = png_data.size; + + return True; +} + +/* + * Save RImage to PNG image + */ +Bool RSavePNG(RImage *img, const char *filename, char *title) +{ + FILE *file; + unsigned char *png_data = NULL; + size_t png_size = 0; + size_t written; + + if (!img || !filename) { + RErrorCode = RERR_BADIMAGEFILE; + return False; + } + + /* Use RSaveRawPNG to generate PNG data in memory */ + if (!RSaveRawPNG(img, title, &png_data, &png_size)) { + /* Error code already set by RSaveRawPNG */ + return False; + } + + /* Open file for writing */ + file = fopen(filename, "wb"); + if (file == NULL) { + free(png_data); + RErrorCode = RERR_OPEN; + return False; + } + + /* Write PNG data to file */ + written = fwrite(png_data, 1, png_size, file); + fclose(file); + + /* Check if all data was written */ + if (written != png_size) { + free(png_data); + RErrorCode = RERR_WRITE; + return False; + } + + /* Clean up */ + free(png_data); return True; } diff --git a/wrlib/wraster.h.in b/wrlib/wraster.h.in index 6f6e5df4..6414455e 100644 --- a/wrlib/wraster.h.in +++ b/wrlib/wraster.h.in @@ -391,6 +391,9 @@ RImage *RGetImageFromXPMData(RContext *context, char **xpmData) Bool RSaveImage(RImage *image, const char *filename, const char *format) __wrlib_nonnull(1, 2, 3); +Bool RSaveRawImage(RImage *image, const char *format, unsigned char **out_buf, size_t *out_size) + __wrlib_nonnull(1, 2, 3, 4); + Bool RSaveTitledImage(RImage *image, const char *filename, const char *format, char *title) __wrlib_nonnull(1, 2, 3);