diff --git a/Makefile b/Makefile index 4729737..a8c1fd0 100644 --- a/Makefile +++ b/Makefile @@ -1,19 +1,24 @@ # Compiler and compiler flags CC = gcc -CFLAGS = -g -std=c11 -Wall -Wextra -Werror -Wshadow -LDFLAGS = -lm +CFLAGS = -g -std=c11 -Wall -Wextra -Wshadow +LDFLAGS = -lm -lpng -ljpeg + +# Include paths (adjust for your system) +INCLUDES = -I/ucrt64/include +# For Linux/macOS with standard paths, comment out INCLUDES or set to empty: +# INCLUDES = # Source files and executable name -SRCS = filter.c helpers.c -TARGET = filter +SRCS = filter.c helpers.c image_io.c bmp_io.c png_io.c jpeg_io.c +TARGET = filter.exe # Default target all: $(TARGET) # Rule to link the object files into the final executable $(TARGET): $(SRCS) - $(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS) + $(CC) $(CFLAGS) $(INCLUDES) $^ -o $@ $(LDFLAGS) # Rule to clean up generated files clean: - rm -f $(TARGET) + rm -f $(TARGET) *.o diff --git a/README.md b/README.md index 4df5604..c341b8b 100644 --- a/README.md +++ b/README.md @@ -261,9 +261,81 @@ To apply a filter via command-line: - `B `: brightness - `o`: oilpaint - -Example for glow: -filter -G input.bmp output.bmp +#### Examples: + +```bash +# Grayscale a PNG image +./filter.exe -g input.png output.png +# Sepia on JPEG image +./filter.exe -s input.jpg output.jpg +# Blur a BMP image +./filter.exe -b input.bmp output.bmp +# Chain multiple filters +./filter.exe -g -b input.png output.png +# Convert format (PNG to JPEG) +./filter.exe -g input.png output.jpg +# Brightness adjustment +./filter.exe -B 40 input.jpg output.jpg +``` + +You can also chain multiple filters by supplying multiple tags (e.g., `./filter.exe -v -g input.bmp output.bmp` for vignette then grayscale). + +## Supported Image Formats + +The Image-Filtering tool supports multiple image formats: + +- **BMP**: 24-bit uncompressed BMP files +- **PNG**: PNG images with RGB color space (supports various PNG formats including RGBA, grayscale, palette - automatically converted to RGB) +- **JPEG**: JPEG images with RGB color space (supports grayscale and RGB JPEGs) + +### Format Auto-Detection + +The tool automatically detects the input image format based on file signatures (magic bytes). The output format is determined by the file extension of the output filename. You can convert between formats by simply changing the output file extension. + +### Examples with Different Formats + +```bash +# Process PNG image +./filter.exe -g photo.png photo_gray.png +# Process JPEG image +./filter.exe -s photo.jpg photo_sepia.jpg +# Convert PNG to JPEG +./filter.exe -g input.png output.jpg +# Convert JPEG to BMP +./filter.exe -b input.jpg output.bmp +# Chain filters on PNG +./filter.exe -g -b input.png output.png +``` + +## Building and Installation + +#### Windows (MSYS2) +```bash +# Install dependencies in MSYS2 UCRT64 terminal +pacman -Syu +pacman -S mingw-w64-ucrt-x86_64-gcc +pacman -S mingw-w64-ucrt-x86_64-libpng +pacman -S mingw-w64-ucrt-x86_64-libjpeg-turbo +pacman -S make +``` + +#### Linux (Ubuntu/Debian) +```bash +sudo apt-get update +sudo apt-get install libpng-dev libjpeg-dev build-essential +``` + +#### macOS +```bash +brew install libpng libjpeg +``` + +### Building + +```bash +make clean +make +``` You can also chain multiple filters by supplying multiple tags (e.g., `./filter vg input.bmp output.bmp` for vignette then grayscale). diff --git a/bmp.h b/bmp.h index ae1fa68..1580856 100644 --- a/bmp.h +++ b/bmp.h @@ -14,7 +14,7 @@ #endif // --- Bitmap File Header (14 bytes) --- -typedef struct +typedef struct bmp_file_header_struct { uint16_t bfType; // File type ("BM") uint32_t bfSize; // Size of the file in bytes @@ -24,7 +24,7 @@ typedef struct } BITMAPFILEHEADER; // --- Bitmap Info Header (40 bytes for BITMAPINFOHEADER) --- -typedef struct +typedef struct bmp_info_header_struct { uint32_t biSize; // Header size (40 bytes) int32_t biWidth; // Image width in pixels @@ -40,7 +40,7 @@ typedef struct } BITMAPINFOHEADER; // --- RGB Triple (3 bytes per pixel) --- -typedef struct +typedef struct bmp_rgb_triple_struct { uint8_t rgbtBlue; uint8_t rgbtGreen; diff --git a/bmp_io.c b/bmp_io.c new file mode 100644 index 0000000..b09201c --- /dev/null +++ b/bmp_io.c @@ -0,0 +1,107 @@ +#include "image_io.h" +#include "bmp.h" +#include +#include + +// Read BMP file +int read_bmp(const char *filename, ImageData *img) { + FILE *inptr = fopen(filename, "rb"); + if (inptr == NULL) { + return 1; + } + BITMAPFILEHEADER bf; + if (fread(&bf, sizeof(BITMAPFILEHEADER), 1, inptr) != 1) { + fclose(inptr); + return 1; + } + BITMAPINFOHEADER bi; + if (fread(&bi, sizeof(BITMAPINFOHEADER), 1, inptr) != 1) { + fclose(inptr); + return 1; + } + if (bf.bfType != 0x4d42 || bf.bfOffBits != 54 || bi.biSize != 40 || bi.biBitCount != 24 || bi.biCompression != 0) { + fclose(inptr); + return 1; + } + img->height = abs(bi.biHeight); + img->width = bi.biWidth; + img->format = IMAGE_FORMAT_BMP; + + img->pixels = (RGBTRIPLE **)malloc(img->height * sizeof(RGBTRIPLE *)); + if (img->pixels == NULL) { + fclose(inptr); + return 1; + } + RGBTRIPLE *pixel_data = (RGBTRIPLE *)calloc(img->height * img->width, sizeof(RGBTRIPLE)); + if (pixel_data == NULL) { + free(img->pixels); + fclose(inptr); + return 1; + } + for (int i = 0; i < img->height; i++) { + img->pixels[i] = pixel_data + i * img->width; + } + int padding = (4 - (img->width * sizeof(RGBTRIPLE)) % 4) % 4; + + // Read pixels + for (int i = 0; i < img->height; i++) { + if (fread(img->pixels[i], sizeof(RGBTRIPLE), img->width, inptr) != (size_t)img->width) { + free_image(img); + fclose(inptr); + return 1; + } + fseek(inptr, padding, SEEK_CUR); + } + + fclose(inptr); + return 0; +} + +// Write BMP file +int write_bmp(const char *filename, ImageData *img) { + FILE *outptr = fopen(filename, "wb"); + if (outptr == NULL) { + return 1; + } + int padding = (4 - (img->width * sizeof(RGBTRIPLE)) % 4) % 4; + int row_size = img->width * sizeof(RGBTRIPLE) + padding; + int image_size = row_size * img->height; + + BITMAPFILEHEADER bf; + bf.bfType = 0x4d42; + bf.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + image_size; + bf.bfReserved1 = 0; + bf.bfReserved2 = 0; + bf.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER); + + BITMAPINFOHEADER bi; + bi.biSize = sizeof(BITMAPINFOHEADER); + bi.biWidth = img->width; + bi.biHeight = img->height; + bi.biPlanes = 1; + bi.biBitCount = 24; + bi.biCompression = 0; + bi.biSizeImage = image_size; + bi.biXPelsPerMeter = 0; + bi.biYPelsPerMeter = 0; + bi.biClrUsed = 0; + bi.biClrImportant = 0; + + if (fwrite(&bf, sizeof(BITMAPFILEHEADER), 1, outptr) != 1 || fwrite(&bi, sizeof(BITMAPINFOHEADER), 1, outptr) != 1) { + fclose(outptr); + return 1; + } + + for (int i = 0; i < img->height; i++) { + if (fwrite(img->pixels[i], sizeof(RGBTRIPLE), img->width, outptr) != (size_t)img->width) { + fclose(outptr); + return 1; + } + for (int k = 0; k < padding; k++) { + fputc(0x00, outptr); + } + } + fclose(outptr); + return 0; +} + diff --git a/ex2.png b/ex2.png new file mode 100644 index 0000000..854ac43 Binary files /dev/null and b/ex2.png differ diff --git a/filter.c b/filter.c index 1680813..0eb2dfc 100644 --- a/filter.c +++ b/filter.c @@ -3,50 +3,65 @@ #include #include "helpers.h" +#include "image_io.h" int main(int argc, char *argv[]) { // Define allowable filters char *filters = "bgrsivtmdGoB:"; - - char filterArr[argc-3]; + // Allocate filter array + char *filterArr = (char *)malloc((argc - 2) * sizeof(char)); + if (!filterArr) { + printf("Memory allocation error.\n"); + return 1; + } int filterCount = 0; + int brightness_value = 0; // gets all filter flags and checks validity int opt; - while ((opt = getopt(argc, argv, filters)) != -1) -{ - if (opt == '?') - { - printf("Invalid filter option\n"); - return 1; - } - - if (opt == 'm') - { - printf("\nAvailable filters:\n"); - printf(" -b Blur\n"); - printf(" -g Grayscale\n"); - printf(" -r Reflect\n"); - printf(" -s Sepia\n"); - printf(" -i Invert\n"); - printf(" -v Vignette\n"); - printf(" -t Threshold\n"); - printf(" -d Detect edges\n"); - printf(" -B Adjust brightness\n"); - printf(" -G Glow\n"); - printf(" -o Oil paint\n"); - printf(" -m Show this menu\n\n"); - return 0; + while ((opt = getopt(argc, argv, filters)) != -1) { + if (opt == '?') { + printf("Invalid filter option\n"); + free(filterArr); + return 1; + } + if (opt == 'B') { + if (optarg) { + brightness_value = atoi(optarg); + } else { + printf("Brightness filter requires a value. Usage: -B \n"); + free(filterArr); + return 1; + } + filterArr[filterCount++] = opt; + } else if (opt == 'm') { + printf("\nAvailable filters:\n"); + printf(" -b Blur\n"); + printf(" -g Grayscale\n"); + printf(" -r Reflect\n"); + printf(" -s Sepia\n"); + printf(" -i Invert\n"); + printf(" -v Vignette\n"); + printf(" -t Threshold\n"); + printf(" -d Detect edges\n"); + printf(" -B Adjust brightness\n"); + printf(" -G Glow\n"); + printf(" -o Oil paint\n"); + printf(" -m Show this menu\n\n"); + free(filterArr); + return 0; + } else { + filterArr[filterCount++] = opt; + } } - filterArr[filterCount++] = opt; -} - if (argc < optind + 2) { printf("Usage: ./filter [flag] infile outfile\n"); + printf("Filters: -g (grayscale), -s (sepia), -r (reflect), -b (blur), -i (invert), -v (vignette), -G (glow), -t (threshold), -d (edge detection), -o (oil paint), -B (brightness)\n"); + free(filterArr); return 3; } @@ -54,67 +69,32 @@ int main(int argc, char *argv[]) char *infile = argv[optind]; char *outfile = argv[optind + 1]; - // Open input file - FILE *inptr = fopen(infile, "rb"); - if (inptr == NULL) - { - printf("Could not open %s.\n", infile); + // Read image using new I/O system + ImageData img; + img.pixels = NULL; + img.width = 0; + img.height = 0; + img.format = IMAGE_FORMAT_UNKNOWN; + + if (read_image(infile, &img) != 0) { + printf("Could not read %s. Unsupported file format or file not found.\n", infile); + printf("Supported formats: BMP, PNG, JPEG\n"); return 4; } - // Open output file - FILE *outptr = fopen(outfile, "wb"); - if (outptr == NULL) - { - fclose(inptr); - printf("Could not create %s.\n", outfile); - return 5; - } - - // Read infile's BITMAPFILEHEADER - BITMAPFILEHEADER bf; - fread(&bf, sizeof(BITMAPFILEHEADER), 1, inptr); - - // Read infile's BITMAPINFOHEADER - BITMAPINFOHEADER bi; - fread(&bi, sizeof(BITMAPINFOHEADER), 1, inptr); - - // Ensure infile is (likely) a 24-bit uncompressed BMP 4.0 - if (bf.bfType != 0x4d42 || bf.bfOffBits != 54 || bi.biSize != 40 || - bi.biBitCount != 24 || bi.biCompression != 0) - { - fclose(outptr); - fclose(inptr); - printf("Unsupported file format.\n"); - return 6; - } - - // Get image's dimensions - int height = abs(bi.biHeight); - int width = bi.biWidth; - - // Allocate memory for image - RGBTRIPLE(*image)[width] = calloc(height, width * sizeof(RGBTRIPLE)); - if (image == NULL) - { - printf("Not enough memory to store image.\n"); - fclose(outptr); - fclose(inptr); - return 7; + // Validate image was read correctly + if (img.pixels == NULL || img.width <= 0 || img.height <= 0 || img.pixels[0] == NULL) { + printf("Error: Invalid image data after reading.\n"); + free_image(&img); + return 4; } - // Determine padding for scanlines - int padding = (4 - (width * sizeof(RGBTRIPLE)) % 4) % 4; - - // Iterate over infile's scanlines - for (int i = 0; i < height; i++) - { - // Read row into pixel array - fread(image[i], sizeof(RGBTRIPLE), width, inptr); + int height = img.height; + int width = img.width; + + // Cast pixels to match filter function - // Skip over padding - fseek(inptr, padding, SEEK_CUR); - } + RGBTRIPLE(*image)[width] = (RGBTRIPLE(*)[width])img.pixels[0]; // Filter image for(int i=0; i +#include +#include +#include + +// Detect image format by reading file signature +ImageFormat detect_image_format(const char *filename) { + FILE *file = fopen(filename, "rb"); + if (!file) { + return IMAGE_FORMAT_UNKNOWN; + } + uint8_t header[8]; + size_t bytes_read = fread(header, 1, 8, file); + fclose(file); + + if (bytes_read < 2) return IMAGE_FORMAT_UNKNOWN; + + //BMP: "BM"(0x42 0x4D) + if (header[0] == 0x42 && header[1] == 0x4D) { + return IMAGE_FORMAT_BMP; + } + //PNG: \x89PNG\r\n\x1a\n + if (bytes_read >= 8 && header[0] == 0x89 && header[1] == 0x50 && header[2] == 0x4E && header[3] == 0x47 && header[4] == 0x0D && header[5] == 0x0A && header[6] == 0x1A && header[7] == 0x0A) { + return IMAGE_FORMAT_PNG; + } + //JPEG: \xFF\xD8\xFF + if (bytes_read >= 3 && header[0] == 0xFF && header[1] == 0xD8 && header[2] == 0xFF) { + return IMAGE_FORMAT_JPEG; + } + return IMAGE_FORMAT_UNKNOWN; +} +// Get format from file extension +ImageFormat get_format_from_extension(const char *filename) { + const char *ext = strrchr(filename, '.'); + if (!ext) { + return IMAGE_FORMAT_UNKNOWN; + } + ext++; + char lower_ext[10]; + int i = 0; + while (ext[i] && i < 9) { + lower_ext[i] = tolower(ext[i]); + i++; + } + lower_ext[i] = '\0'; + if (strcmp(lower_ext, "bmp") == 0) { + return IMAGE_FORMAT_BMP; + } else if (strcmp(lower_ext, "png") == 0) { + return IMAGE_FORMAT_PNG; + } else if (strcmp(lower_ext, "jpg") == 0 || strcmp(lower_ext, "jpeg") == 0) { + return IMAGE_FORMAT_JPEG; + } + return IMAGE_FORMAT_UNKNOWN; +} +extern int read_bmp(const char *filename, ImageData *img); +extern int read_png(const char *filename, ImageData *img); +extern int read_jpeg(const char *filename, ImageData *img); +extern int write_bmp(const char *filename, ImageData *img); +extern int write_png(const char *filename, ImageData *img); +extern int write_jpeg(const char *filename, ImageData *img); +// Read image from file +int read_image(const char *filename, ImageData *img) { + if (!filename || !img) { + return 1; + } + ImageFormat format = detect_image_format(filename); + if (format == IMAGE_FORMAT_UNKNOWN) { + format = get_format_from_extension(filename); + } + img->format = format; + switch (format) { + case IMAGE_FORMAT_BMP: + return read_bmp(filename, img); + case IMAGE_FORMAT_PNG: + return read_png(filename, img); + case IMAGE_FORMAT_JPEG: + return read_jpeg(filename, img); + default: + return 1; + } +} +// Write image to file +int write_image(const char *filename, ImageData *img, ImageFormat output_format) { + if (!filename || !img || !img->pixels) { + return 1; + } + if (output_format == IMAGE_FORMAT_UNKNOWN) { + output_format = get_format_from_extension(filename); + } + if (output_format == IMAGE_FORMAT_UNKNOWN) { + output_format = img->format; + } + switch (output_format) { + case IMAGE_FORMAT_BMP: + return write_bmp(filename, img); + case IMAGE_FORMAT_PNG: + return write_png(filename, img); + case IMAGE_FORMAT_JPEG: + return write_jpeg(filename, img); + default: + return 1; + } +} +// Free image data +void free_image(ImageData *img) { + if (img && img->pixels) { + if (img->height > 0 && img->pixels[0]) { + free(img->pixels[0]); + } + free(img->pixels); + img->pixels = NULL; + img->width = 0; + img->height = 0; + } +} + diff --git a/image_io.h b/image_io.h new file mode 100644 index 0000000..b03035e --- /dev/null +++ b/image_io.h @@ -0,0 +1,18 @@ +#ifndef IMAGE_IO_H +#define IMAGE_IO_H +#include "bmp.h" +#include + +// Image format types +typedef enum {IMAGE_FORMAT_BMP, IMAGE_FORMAT_PNG, IMAGE_FORMAT_JPEG, IMAGE_FORMAT_UNKNOWN} ImageFormat; +// Image data structure +typedef struct { int width; int height; RGBTRIPLE **pixels; ImageFormat format;} ImageData; +// detect image format from file +ImageFormat detect_image_format(const char *filename); + +int read_image(const char *filename, ImageData *img); +int write_image(const char *filename, ImageData *img, ImageFormat output_format); +void free_image(ImageData *img); +ImageFormat get_format_from_extension(const char *filename); +#endif + diff --git a/jpeg_io.c b/jpeg_io.c new file mode 100644 index 0000000..b8af765 --- /dev/null +++ b/jpeg_io.c @@ -0,0 +1,135 @@ +// Define NOGDI BEFORE any Windows headers to prevent GDI types +#define NOGDI +#define WIN32_LEAN_AND_MEAN +#define NOMINMAX +#include "bmp.h" +#include +#include +#include +#include "image_io.h" + +struct jpeg_error_mgr_wrapper { + struct jpeg_error_mgr pub; + jmp_buf setjmp_buffer; +}; + +static void jpeg_error_exit(j_common_ptr cinfo) { + struct jpeg_error_mgr_wrapper *myerr = (struct jpeg_error_mgr_wrapper *)cinfo->err; + (*cinfo->err->output_message)(cinfo); + longjmp(myerr->setjmp_buffer, 1); +} +// Read JPEG file +int read_jpeg(const char *filename, ImageData *img) { + FILE *infile = fopen(filename, "rb"); + if (!infile) { + return 1; + } + struct jpeg_decompress_struct cinfo; + struct jpeg_error_mgr_wrapper jerr; + + cinfo.err = jpeg_std_error(&jerr.pub); + jerr.pub.error_exit = jpeg_error_exit; + if (setjmp(jerr.setjmp_buffer)) { + jpeg_destroy_decompress(&cinfo); + fclose(infile); + return 1; + } + jpeg_create_decompress(&cinfo); + jpeg_stdio_src(&cinfo, infile); + jpeg_read_header(&cinfo, TRUE); + jpeg_start_decompress(&cinfo); + + img->width = cinfo.output_width; + img->height = cinfo.output_height; + img->format = IMAGE_FORMAT_JPEG; + + // Allocate contiguous memory for pixels + img->pixels = (RGBTRIPLE **)malloc(img->height * sizeof(RGBTRIPLE *)); + if (!img->pixels) { + jpeg_destroy_decompress(&cinfo); + fclose(infile); + return 1; + } + RGBTRIPLE *pixel_data = (RGBTRIPLE *)calloc(img->height * img->width, sizeof(RGBTRIPLE)); + if (!pixel_data) { + free(img->pixels); + jpeg_destroy_decompress(&cinfo); + fclose(infile); + return 1; + } + for (int i = 0; i < img->height; i++) { + img->pixels[i] = pixel_data + i * img->width; + } + + // Read scanlines + JSAMPARRAY buffer = (*cinfo.mem->alloc_sarray)((j_common_ptr)&cinfo, JPOOL_IMAGE, + cinfo.output_width * cinfo.output_components, 1); + int row = 0; + while (cinfo.output_scanline < cinfo.output_height) { + jpeg_read_scanlines(&cinfo, buffer, 1); + JSAMPROW ptr = buffer[0]; + for (int col = 0; col < img->width; col++) { + img->pixels[row][col].rgbtRed = ptr[col * 3]; + img->pixels[row][col].rgbtGreen = ptr[col * 3 + 1]; + img->pixels[row][col].rgbtBlue = ptr[col * 3 + 2]; + } + row++; + } + jpeg_finish_decompress(&cinfo); + jpeg_destroy_decompress(&cinfo); + fclose(infile); + return 0; +} +// Write JPEG file +int write_jpeg(const char *filename, ImageData *img) { + FILE *outfile = fopen(filename, "wb"); + if (!outfile) { + return 1; + } + struct jpeg_compress_struct cinfo; + struct jpeg_error_mgr_wrapper jerr; + + cinfo.err = jpeg_std_error(&jerr.pub); + jerr.pub.error_exit = jpeg_error_exit; + if (setjmp(jerr.setjmp_buffer)) { + jpeg_destroy_compress(&cinfo); + fclose(outfile); + return 1; + } + jpeg_create_compress(&cinfo); + jpeg_stdio_dest(&cinfo, outfile); + + cinfo.image_width = img->width; + cinfo.image_height = img->height; + cinfo.input_components = 3; + cinfo.in_color_space = JCS_RGB; + + jpeg_set_defaults(&cinfo); + jpeg_set_quality(&cinfo, 90, TRUE); + jpeg_start_compress(&cinfo, TRUE); + + // Write scanlines + JSAMPROW row_pointer[1]; + unsigned char *row_buffer = (unsigned char *)malloc(img->width * 3); + if (!row_buffer) { + jpeg_destroy_compress(&cinfo); + fclose(outfile); + return 1; + } + while (cinfo.next_scanline < cinfo.image_height) { + int row = cinfo.next_scanline; + for (int col = 0; col < img->width; col++) { + row_buffer[col * 3] = img->pixels[row][col].rgbtRed; + row_buffer[col * 3 + 1] = img->pixels[row][col].rgbtGreen; + row_buffer[col * 3 + 2] = img->pixels[row][col].rgbtBlue; + } + row_pointer[0] = row_buffer; + jpeg_write_scanlines(&cinfo, row_pointer, 1); + } + free(row_buffer); + jpeg_finish_compress(&cinfo); + jpeg_destroy_compress(&cinfo); + fclose(outfile); + return 0; +} + diff --git a/output.png b/output.png new file mode 100644 index 0000000..ff25c56 Binary files /dev/null and b/output.png differ diff --git a/png_io.c b/png_io.c new file mode 100644 index 0000000..e8336af --- /dev/null +++ b/png_io.c @@ -0,0 +1,173 @@ +#define NOGDI +#define WIN32_LEAN_AND_MEAN +#define NOMINMAX +#include "image_io.h" +#include +#include +// Read PNG file +int read_png(const char *filename, ImageData *img) { + FILE *fp = fopen(filename, "rb"); + if (!fp) { + return 1; + } + png_byte header[8]; + if (fread(header, 1, 8, fp) != 8 || png_sig_cmp(header, 0, 8)) { + fclose(fp); + return 1; + } + png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (!png_ptr) { + fclose(fp); + return 1; + } + png_infop info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) { + png_destroy_read_struct(&png_ptr, NULL, NULL); + fclose(fp); + return 1; + } + if (setjmp(png_jmpbuf(png_ptr))) { + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + fclose(fp); + return 1; + } + png_init_io(png_ptr, fp); + png_set_sig_bytes(png_ptr, 8); + png_read_info(png_ptr, info_ptr); + + img->width = png_get_image_width(png_ptr, info_ptr); + img->height = png_get_image_height(png_ptr, info_ptr); + img->format = IMAGE_FORMAT_PNG; + + png_byte color_type = png_get_color_type(png_ptr, info_ptr); + png_byte bit_depth = png_get_bit_depth(png_ptr, info_ptr); + + if (color_type == PNG_COLOR_TYPE_PALETTE) { + png_set_palette_to_rgb(png_ptr); + } + if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) { + png_set_expand_gray_1_2_4_to_8(png_ptr); + } + if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) { + png_set_tRNS_to_alpha(png_ptr); + } + if (bit_depth == 16) { + png_set_strip_16(png_ptr); + } + if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) { + png_set_gray_to_rgb(png_ptr); + } + if (color_type == PNG_COLOR_TYPE_RGBA) { + png_set_strip_alpha(png_ptr); + } + + png_read_update_info(png_ptr, info_ptr); + + img->pixels = (RGBTRIPLE **)malloc(img->height * sizeof(RGBTRIPLE *)); + if (!img->pixels) { + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + fclose(fp); + return 1; + } + RGBTRIPLE *pixel_data = (RGBTRIPLE *)calloc(img->height * img->width, sizeof(RGBTRIPLE)); + if (!pixel_data) { + free(img->pixels); + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + fclose(fp); + return 1; + } + for (int y = 0; y < img->height; y++) { + img->pixels[y] = pixel_data + y * img->width; + } + png_bytep *row_pointers = (png_bytep *)malloc(img->height * sizeof(png_bytep)); + if (!row_pointers) { + free(pixel_data); + free(img->pixels); + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + fclose(fp); + return 1; + } + int rowbytes = png_get_rowbytes(png_ptr, info_ptr); + for (int y = 0; y < img->height; y++) { + row_pointers[y] = (png_byte *)malloc(rowbytes); + if (!row_pointers[y]) { + for (int j = 0; j < y; j++) { + free(row_pointers[j]); + } + free(row_pointers); + free(pixel_data); + free(img->pixels); + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + fclose(fp); + return 1; + } + } + + // Read image + png_read_image(png_ptr, row_pointers); + for (int y = 0; y < img->height; y++) { + png_bytep row = row_pointers[y]; + for (int x = 0; x < img->width; x++) { + // PNG stores RGB, copy directly + img->pixels[y][x].rgbtRed = row[x * 3]; + img->pixels[y][x].rgbtGreen = row[x * 3 + 1]; + img->pixels[y][x].rgbtBlue = row[x * 3 + 2]; + } + free(row_pointers[y]); + } + + free(row_pointers); + png_read_end(png_ptr, NULL); + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + fclose(fp); + return 0; +} +// Write PNG file +int write_png(const char *filename, ImageData *img) { + FILE *fp = fopen(filename, "wb"); + if (!fp) { + return 1; + } + png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (!png_ptr) { + fclose(fp); + return 1; + } + png_infop info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) { + png_destroy_write_struct(&png_ptr, NULL); + fclose(fp); + return 1; + } + if (setjmp(png_jmpbuf(png_ptr))) { + png_destroy_write_struct(&png_ptr, &info_ptr); + fclose(fp); + return 1; + } + png_init_io(png_ptr, fp); + png_set_IHDR(png_ptr, info_ptr, img->width, img->height, + 8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); + + png_write_info(png_ptr, info_ptr); + png_bytep row = (png_bytep)malloc(3 * img->width * sizeof(png_byte)); + if (!row) { + png_destroy_write_struct(&png_ptr, &info_ptr); + fclose(fp); + return 1; + } + for (int y = 0; y < img->height; y++) { + for (int x = 0; x < img->width; x++) { + row[x * 3] = img->pixels[y][x].rgbtRed; + row[x * 3 + 1] = img->pixels[y][x].rgbtGreen; + row[x * 3 + 2] = img->pixels[y][x].rgbtBlue; + } + png_write_row(png_ptr, row); + } + free(row); + png_write_end(png_ptr, NULL); + png_destroy_write_struct(&png_ptr, &info_ptr); + fclose(fp); + return 0; +} +