From cb0300faab6e0ed78cfd1ae35a69c1e557021564 Mon Sep 17 00:00:00 2001 From: Tamas Nepusz Date: Thu, 14 Sep 2023 13:36:17 +0200 Subject: [PATCH] feat: added support for JPEG output --- CMakeLists.txt | 9 +++++ src/formats/geotiff.c | 8 +++-- src/formats/geotiff.h | 5 +-- src/formats/image.c | 48 ++++++++++++++++++++++++++ src/formats/image.h | 16 ++++++++- src/formats/jpeg.c | 57 ++++++++++++++++++++++++++++-- src/formats/jpeg.h | 2 +- src/formats/png.c | 19 ++++++++-- src/formats/png.h | 2 +- src/stitch.c | 80 ++++++++++++++++++++----------------------- 10 files changed, 188 insertions(+), 58 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5aa48c3..aaaf98e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,6 +3,7 @@ cmake_minimum_required(VERSION 3.21) project(stitch) include(CheckIncludeFile) +include(CheckIPOSupported) include(CheckSymbolExists) # Set the path where we look for additional CMake modules @@ -42,6 +43,9 @@ check_include_file("libintl.h" HAVE_LIBINTL_H) check_include_file("unistd.h" HAVE_UNISTD_H) check_symbol_exists(isatty unistd.h HAVE_ISATTY) +# Check for LTO support +check_ipo_supported(RESULT LTO_SUPPORTED) + # Turn on all compiler warnings set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall") @@ -85,3 +89,8 @@ if(GEOTIFF_FOUND AND PROJ_FOUND) target_link_libraries(stitch ${GEOTIFF_LIBRARY} ${TIFF_LIBRARIES}) target_link_libraries(stitch PROJ::proj) endif() + +# Use link-time optimization if available +if(LTO_SUPPORTED) + set_property(TARGET stitch PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE) +endif() diff --git a/src/formats/geotiff.c b/src/formats/geotiff.c index 69f162a..e177dad 100644 --- a/src/formats/geotiff.c +++ b/src/formats/geotiff.c @@ -8,8 +8,10 @@ # include # include -int write_geotiff(const char* outfile, int width, int height, unsigned char** rows, int px, int py, int minx, int maxy) { +int write_geotiff(const char* outfile, const image_t* img, int px, int py, int minx, int maxy) { int i; + int width = img->width; + int height = img->height; //TODO : Handle writing to stdout if required @@ -57,7 +59,7 @@ int write_geotiff(const char* outfile, int width, int height, unsigned char** ro //write raster image for (i = 0; i < height; i++) { - if (!TIFFWriteScanline(tif, rows[i], i, 0)) { + if (!TIFFWriteScanline(tif, (void*) image_get_row_ptr(img, i), i, 0)) { TIFFError("WriteImage", "failure in WriteScanline\n"); return EXIT_FAILURE; } @@ -76,7 +78,7 @@ int write_geotiff(const char* outfile, int width, int height, unsigned char** ro #else /* GEOTIFF_FOUND */ -int write_geotiff(const char* outfile, int width, int height, void* rows) { +int write_geotiff(const char* outfile, const image_t* img) { fprintf(stderr, "stitch was compiled without GeoTIFF support, sorry\n"); return EXIT_FAILURE; } diff --git a/src/formats/geotiff.h b/src/formats/geotiff.h index dbb8e8a..4e2701c 100644 --- a/src/formats/geotiff.h +++ b/src/formats/geotiff.h @@ -2,7 +2,4 @@ #include "image.h" -int write_geotiff( - const char* outfile, int width, int height, unsigned char** rows, - int px, int py, int minx, int maxy -); +int write_geotiff(const char* outfile, const image_t* img, int px, int py, int minx, int maxy); diff --git a/src/formats/image.c b/src/formats/image.c index 678fd4e..5e29ef8 100644 --- a/src/formats/image.c +++ b/src/formats/image.c @@ -3,6 +3,10 @@ #include int image_create(image_t* image, int width, int height, int depth) { + if (depth != 1 && depth != 3 && depth != 4) { + return EXIT_FAILURE; + } + image->width = width; image->height = height; image->depth = depth; @@ -19,3 +23,47 @@ void image_destroy(image_t* image) { image->width = image->height = image->depth = 0; } + +rgba_color_t image_get_pixel(const image_t* image, int x, int y) { + unsigned long long offset = (y * image->width + x) * image->depth; + rgba_color_t result; + + if (image->depth == 4) { + result.r = image->buf[offset]; + result.g = image->buf[offset + 1]; + result.b = image->buf[offset + 2]; + result.a = image->buf[offset + 3]; + } else if (image->depth == 3) { + result.r = image->buf[offset]; + result.g = image->buf[offset + 1]; + result.b = image->buf[offset + 2]; + result.a = 255; + } else if (image->depth == 1) { + result.r = image->buf[offset]; + result.g = image->buf[offset]; + result.b = image->buf[offset]; + result.a = 255; + } else { + result.r = result.g = result.b = result.a = 0; + } + + return result; +} + +void image_set_pixel(image_t* image, int x, int y, rgba_color_t color) { + if (image->depth != 4) { + return; + } + + unsigned long long offset = (y * image->width + x) * image->depth; + + image->buf[offset] = color.r; + image->buf[offset + 1] = color.g; + image->buf[offset + 2] = color.b; + image->buf[offset + 3] = color.a; +} + + +const void* image_get_row_ptr(const image_t* image, int y) { + return (const void*)(image->buf + y * image->width * image->depth); +} \ No newline at end of file diff --git a/src/formats/image.h b/src/formats/image.h index ae32306..85e47b8 100644 --- a/src/formats/image.h +++ b/src/formats/image.h @@ -1,11 +1,25 @@ #pragma once +#include + typedef struct image { - unsigned char *buf; + uint8_t *buf; int depth; int width; int height; } image_t; +typedef struct { + uint8_t r; + uint8_t g; + uint8_t b; + uint8_t a; +} rgba_color_t; + int image_create(image_t* image, int width, int height, int depth); void image_destroy(image_t* image); + +rgba_color_t image_get_pixel(const image_t* image, int x, int y); +void image_set_pixel(image_t* image, int x, int y, rgba_color_t color); + +const void* image_get_row_ptr(const image_t* image, int y); diff --git a/src/formats/jpeg.c b/src/formats/jpeg.c index 51caf79..d7ef33b 100644 --- a/src/formats/jpeg.c +++ b/src/formats/jpeg.c @@ -7,6 +7,7 @@ #if JPEG_FOUND # include +# include int read_jpeg(char *s, int len, image_t *img) { struct jpeg_decompress_struct cinfo; @@ -14,6 +15,7 @@ int read_jpeg(char *s, int len, image_t *img) { cinfo.err = jpeg_std_error(&jerr); jpeg_create_decompress(&cinfo); + jpeg_mem_src(&cinfo, (unsigned char *) s, len); jpeg_read_header(&cinfo, TRUE); jpeg_start_decompress(&cinfo); @@ -39,8 +41,57 @@ int read_jpeg(char *s, int len, image_t *img) { return EXIT_SUCCESS; } -int write_jpeg(const char* outfile, int width, int height, void* rows) { - fprintf(stderr, "Not implemented yet, sorry\n"); +int write_jpeg(const char* outfile, const image_t* img) { + FILE *outfp = stdout; + if (outfile != NULL) { + fprintf(stderr, "Output JPG: %s\n", outfile); + outfp = fopen(outfile, "wb"); + if (outfp == NULL) { + perror(outfile); + exit(EXIT_FAILURE); + } + } else { + fprintf(stderr, "Output JPG: stdout\n"); + } + + struct jpeg_compress_struct cinfo; + struct jpeg_error_mgr jerr; + unsigned char* ptr; + + outfp = fopen(outfile, "wb"); + if (outfp == NULL) { + return EXIT_FAILURE; + } + + cinfo.err = jpeg_std_error(&jerr); + jpeg_create_compress(&cinfo); + + jpeg_stdio_dest(&cinfo, outfp); + + cinfo.image_width = img->width; + cinfo.image_height = img->height; + cinfo.input_components = img->depth; + cinfo.in_color_space = JCS_EXT_RGBA; + + jpeg_set_defaults(&cinfo); + jpeg_set_quality(&cinfo, 95, TRUE); + + jpeg_start_compress(&cinfo, TRUE); + + /* Write every scanline ... */ + while(cinfo.next_scanline < cinfo.image_height) { + ptr = (void*) image_get_row_ptr(img, cinfo.next_scanline); + jpeg_write_scanlines(&cinfo, &ptr, 1); + } + + jpeg_finish_compress(&cinfo); + if (outfile != NULL) { + fclose(outfp); + } + + jpeg_destroy_compress(&cinfo); + + return EXIT_FAILURE; } @@ -51,7 +102,7 @@ int read_jpeg(char *s, int len, image_t *img) { return EXIT_FAILURE; } -int write_jpeg(const char* outfile, int width, int height, void* rows) { +int write_jpeg(const char* outfile, const image_t* img) { fprintf(stderr, "stitch was compiled without JPEG support, sorry\n"); return EXIT_FAILURE; } diff --git a/src/formats/jpeg.h b/src/formats/jpeg.h index 79d2877..7860a41 100644 --- a/src/formats/jpeg.h +++ b/src/formats/jpeg.h @@ -3,4 +3,4 @@ #include "image.h" int read_jpeg(char *s, int len, image_t* img); -int write_jpeg(const char* outfile, int width, int height, void* rows); +int write_jpeg(const char* outfile, const image_t* img); diff --git a/src/formats/png.c b/src/formats/png.c index dca526d..fc8db2b 100644 --- a/src/formats/png.c +++ b/src/formats/png.c @@ -80,7 +80,7 @@ int read_png(char *s, int len, image_t *img) { return EXIT_SUCCESS; } -int write_png(const char* outfile, int width, int height, void* rows) { +int write_png(const char* outfile, const image_t* img) { FILE *outfp = stdout; if (outfile != NULL) { fprintf(stderr, "Output PNG: %s\n", outfile); @@ -94,6 +94,7 @@ int write_png(const char* outfile, int width, int height, void* rows) { } png_structp png_ptr; png_infop info_ptr; + void** rows; png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, fail, fail, fail); if (png_ptr == NULL) { @@ -107,12 +108,24 @@ int write_png(const char* outfile, int width, int height, void* rows) { return EXIT_FAILURE; } - png_set_IHDR(png_ptr, info_ptr, width, height, 8, PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); + rows = calloc(img->height, sizeof(void*)); + if (rows == NULL) { + fprintf(stderr, "Not enough memory\n"); + return EXIT_FAILURE; + } + + for (int i = 0; i < img->height; i++) { + rows[i] = image_get_row_ptr(img, i); + } + + png_set_IHDR(png_ptr, info_ptr, img->width, img->height, 8, PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); png_set_rows(png_ptr, info_ptr, rows); png_init_io(png_ptr, outfp); png_write_png(png_ptr, info_ptr, 0, NULL); png_destroy_write_struct(&png_ptr, &info_ptr); + free(rows); + if (outfile != NULL) { fclose(outfp); } @@ -128,7 +141,7 @@ int read_png(char *s, int len, image_t *img) { } -int write_png(const char* outfile, int width, int height, void* rows) { +int write_png(const char* outfile, const image_t* img) { fprintf(stderr, "stitch was compiled without PNG support, sorry\n"); return EXIT_FAILURE; } diff --git a/src/formats/png.h b/src/formats/png.h index 6d08fcb..1983a1d 100644 --- a/src/formats/png.h +++ b/src/formats/png.h @@ -3,4 +3,4 @@ #include "image.h" int read_png(char *s, int len, image_t* img); -int write_png(const char* outfile, int width, int height, void* rows); +int write_png(const char* outfile, const image_t* img); diff --git a/src/stitch.c b/src/stitch.c index 185b254..6072e6b 100644 --- a/src/stitch.c +++ b/src/stitch.c @@ -249,7 +249,10 @@ int main(int argc, char **argv) { int outfmt = OUTFMT_PNG; int x, y; bool writeworldfile = false; - unsigned long long int offset, ioffset; + unsigned long long int ioffset; + + image_t img, outimg; + rgba_color_t color; while ((i = getopt(argc, argv, "eho:t:cf:w")) != -1) { switch (i) { @@ -382,10 +385,12 @@ int main(int argc, char **argv) { exit(EXIT_FAILURE); } - unsigned char *buf = malloc(dim * 4); - memset(buf, '\0', dim * 4); - if (buf == NULL) { - fprintf(stderr, "Can't allocate memory for %lld\n", dim * 4); + if (image_create(&outimg, width, height, 4)) { + fprintf( + stderr, "Can't allocate memory for %lld bytes (%d x %d pixels)\n", + dim * 4, width, height + ); + exit(EXIT_FAILURE); } unsigned int tx, ty; @@ -459,8 +464,6 @@ int main(int argc, char **argv) { free(url2); - struct image img; - if (data.len >= 4 && memcmp(data.buf, "\x89PNG", 4) == 0) { exit_code = read_png(data.buf, data.len, &img); } else if (data.len >= 2 && memcmp(data.buf, "\xFF\xD8", 2) == 0) { @@ -495,15 +498,16 @@ int main(int argc, char **argv) { continue; } - offset = ((y + yoff) * width + x + xoff) * 4; ioffset = (y * img.width + x) * img.depth; if (img.depth == 4) { /* RGBA image */ - double as = buf[offset + 3] / 255.0; - double rs = buf[offset + 0] / 255.0 * as; - double gs = buf[offset + 1] / 255.0 * as; - double bs = buf[offset + 2] / 255.0 * as; + color = image_get_pixel(&outimg, xd, yd); + + double as = color.a / 255.0; + double rs = color.r / 255.0 * as; + double gs = color.g / 255.0 * as; + double bs = color.b / 255.0 * as; double ad = img.buf[ioffset + 3] / 255.0; double rd = img.buf[ioffset + 0] / 255.0 * ad; @@ -516,21 +520,15 @@ int main(int argc, char **argv) { double gr = gs * (1 - ad) + gd; double br = bs * (1 - ad) + bd; - buf[offset + 3] = ar * 255.0; - buf[offset + 0] = rr / ar * 255.0; - buf[offset + 1] = gr / ar * 255.0; - buf[offset + 2] = br / ar * 255.0; - } else if (img.depth == 3) { - buf[offset + 0] = img.buf[ioffset + 0]; - buf[offset + 1] = img.buf[ioffset + 1]; - buf[offset + 2] = img.buf[ioffset + 2]; - buf[offset + 3] = 255; + color.r = rr / ar * 255.0; + color.g = gr / ar * 255.0; + color.b = br / ar * 255.0; + color.a = ar * 255.0; } else { - buf[offset + 0] = img.buf[ioffset + 0]; - buf[offset + 1] = img.buf[ioffset + 0]; - buf[offset + 2] = img.buf[ioffset + 0]; - buf[offset + 3] = 255; + color = image_get_pixel(&img, x, y); } + + image_set_pixel(&outimg, xd, yd, color); } } @@ -539,11 +537,6 @@ int main(int argc, char **argv) { } } - unsigned char **rows = calloc(height, sizeof(unsigned char*)); - for (i = 0; i < height; i++) { - rows[i] = buf + i * (4 * width); - } - if (elevation) { double ratio; double avg_elevation; @@ -555,9 +548,10 @@ int main(int argc, char **argv) { avg_elevation = 0; counter = 0; - for (y = 0, offset = 0, counter = 0; y < height; y++) { - for (x = 0; x < width; x++, offset += 4) { - pixel_elevation = (buf[offset] << 16) + (buf[offset+1] << 8) + buf[offset+2]; + for (y = 0, counter = 0; y < height; y++) { + for (x = 0; x < width; x++) { + color = image_get_pixel(&outimg, x, y); + pixel_elevation = (color.r << 16) + (color.g << 8) + color.b; if (min_elevation > pixel_elevation) { min_elevation = pixel_elevation; } @@ -585,37 +579,38 @@ int main(int argc, char **argv) { fprintf(stderr, "==Midpoint in [0; 1] range: %.4f\n", (avg_elevation - min_elevation) * ratio / 255); - for (y = 0, offset = 0; y < height; y++) { - for (x = 0; x < width; x++, offset += 4) { - pixel_elevation = (buf[offset] << 16) + (buf[offset+1] << 8) + buf[offset+2]; - buf[offset] = buf[offset + 1] = buf[offset + 2] = - round((pixel_elevation - min_elevation) * ratio); + for (y = 0; y < height; y++) { + for (x = 0; x < width; x++) { + color = image_get_pixel(&outimg, x, y); + pixel_elevation = (color.r << 16) + (color.g << 8) + color.b; + color.r = color.g = color.b = round((pixel_elevation - min_elevation) * ratio); + image_set_pixel(&outimg, x, y, color); } } } if (outfmt == OUTFMT_PNG) { - exit_code = write_png(outfile, width, height, rows); + exit_code = write_png(outfile, &outimg); if (exit_code) { exit(exit_code); } } else if (outfmt == OUTFMT_JPEG) { - exit_code = write_jpeg(outfile, width, height, rows); + exit_code = write_jpeg(outfile, &outimg); if (exit_code) { exit(exit_code); } } else if (outfmt == OUTFMT_GEOTIFF) { - exit_code = write_geotiff(outfile, width, height, rows, px, py, minx, maxy); + exit_code = write_geotiff(outfile, &outimg, px, py, minx, maxy); if (exit_code) { exit(exit_code); } } - free(rows); + image_destroy(&outimg); //write world file if (writeworldfile) { @@ -668,5 +663,6 @@ int main(int argc, char **argv) { fprintf(stderr, "Can't write a worldfile when writing to stdout\n"); } } + return 0; }