lexlib 2.0.1

library with miscellaneous stuff
Documentation
// Copyright 2023 alexevier <alexevier@proton.me>
// licensed under the zlib license <https://www.zlib.net/zlib_license.html>

#include "lexlib/defines.h"
#ifdef LIBPNG
#	include<stdio.h>
#	include<png.h>
#else
#	define LEXLIB_EXPERIMENTAL
#	include<internal/stbi.h>
#endif

#include<lexlib/image.h>
#include<stdbool.h>
#include<string.h>

uint8_t lexlibImageLoadPng(struct LexlibImage *image, const char *filename){
#ifdef LIBPNG
	FILE *file = fopen(filename, "rb");
	if(!file)
		return LEXLIB_CANT_OPEN;

	{// check if its a png
		uint8_t head[8];
		fread(head, 1, 8, file);
		bool ispng = !png_sig_cmp(head, 0, 8);
		if(!ispng){
			fclose(file);
			return LEXLIB_INVALID_TYPE;
		}
	}

	png_structp pngPtr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
	if(!pngPtr){
		fclose(file);
		return LEXLIB_OUT_OF_MEMORY;
	}
	png_infop infoPtr = png_create_info_struct(pngPtr);
	if(!infoPtr){
		fclose(file);
		png_destroy_read_struct(&pngPtr, NULL, NULL);
		return LEXLIB_OUT_OF_MEMORY;
	}
	png_infop endInfo = png_create_info_struct(pngPtr);
	if(!endInfo){
		fclose(file);
		png_destroy_read_struct(&pngPtr, &infoPtr, NULL);
		return LEXLIB_OUT_OF_MEMORY;
	}

	int readTransforms = 0;
#if LEXLIB_LITTLE_ENDIAN
	readTransforms |= PNG_TRANSFORM_SWAP_ENDIAN;
#endif

	// initial read
	png_init_io(pngPtr, file);
	png_set_sig_bytes(pngPtr, 8);
	png_read_png(pngPtr, infoPtr, readTransforms, NULL);

	// read info
	image->width    = png_get_image_width(pngPtr, infoPtr);
	image->height   = png_get_image_height(pngPtr, infoPtr);
	image->bpc      = png_get_bit_depth(pngPtr, infoPtr);
	image->channels = png_get_channels(pngPtr, infoPtr);

	switch(png_get_color_type(pngPtr, infoPtr)){
		case PNG_COLOR_TYPE_GRAY:
			image->profile = LEXLIB_GRAY;
			break;
		case PNG_COLOR_TYPE_GRAY_ALPHA:
			image->profile = LEXLIB_GRAYA;
			break;
		case PNG_COLOR_TYPE_RGB:
			image->profile = LEXLIB_RGB;
			break;
		case PNG_COLOR_TYPE_RGB_ALPHA:
			image->profile = LEXLIB_RGBA;
			break;
		default:
			png_destroy_read_struct(&pngPtr, &infoPtr, &endInfo);
			return LEXLIB_UNSUPORTED;
	}

	uint8_t err;
	if((err = lexlibImageNew(image, image->width, image->height, image->profile, image->bpc))){
		fclose(file);
		png_destroy_read_struct(&pngPtr, &infoPtr, &endInfo);
		return err;
	}

	png_byte** rowPointers = png_get_rows(pngPtr, infoPtr);
	size_t rowSize = image->width * image->bpp / 8;
	size_t offset = 0;
	uint8_t *data = image->data;
	for(size_t y = 0; y < image->height; y++){
		memcpy(data+offset, rowPointers[y], rowSize * sizeof(uint8_t));
		offset += rowSize;
	}

	png_destroy_read_struct(&pngPtr, &infoPtr, &endInfo);
	fclose(file);
#else // ..libpng
	switch(lexlibStbImageIs16(filename)){
		case 0:
			image->bpc = 8;
			break;
		case 1:
			image->bpc = 16;
			break;
		case LEXLIB_CANT_OPEN:
			return LEXLIB_CANT_OPEN;
		default:
			return LEXLIB_CANT_READ;
	}

	int channels;
	switch(image->bpc){
		case 8:
			image->data = lexlibStbImageLoad(filename, (int*)&image->width, (int*)&image->height, &channels);
			break;
		case 16:
			image->data = lexlibStbImage16Load(filename, (int*)&image->width, (int*)&image->height, &channels);
			break;
	}

	image->channels = channels;

	if(!image->data)
		return LEXLIB_CANT_READ;

	if(lexlibImageRepairStruct(image)){
		lexlibImageDelete(image);
		image->data = NULL;
		return LEXLIB_ERROR;
	}
#endif

	return LEXLIB_OK;
}

uint8_t lexlibImageSavePng(const struct LexlibImage *image, const char *filename){
#ifdef LIBPNG
	png_structp pngPtr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
	if(!pngPtr)
		return LEXLIB_OUT_OF_MEMORY;
	png_infop infoPtr = png_create_info_struct(pngPtr);
	if(!infoPtr){
		png_destroy_write_struct(&pngPtr, NULL);
		return LEXLIB_OUT_OF_MEMORY;
	}

	int colorType = 0;
	switch(image->profile){
		case LEXLIB_GRAY:
			colorType = PNG_COLOR_TYPE_GRAY;
			break;
		case LEXLIB_GRAYA:
			colorType = PNG_COLOR_TYPE_GRAY_ALPHA;
			break;
		case LEXLIB_RGB:
			colorType = PNG_COLOR_TYPE_RGB;
			break;
		case LEXLIB_RGBA:
			colorType = PNG_COLOR_TYPE_RGB_ALPHA;
			break;
		default:
			png_destroy_write_struct(&pngPtr, &infoPtr);
			return LEXLIB_UNSUPORTED;
	}

	png_set_IHDR(pngPtr, infoPtr, image->width, image->height, image->bpc, colorType, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
	png_set_compression_level(pngPtr, 9);

	FILE *file = fopen(filename, "wb");
	if(!file){
		png_destroy_write_struct(&pngPtr, &infoPtr);
		return LEXLIB_CANT_WRITE;
	}

	// write png
	png_init_io(pngPtr, file);
	png_write_info(pngPtr, infoPtr);
	png_write_flush(pngPtr);

#if LEXLIB_LITTLE_ENDIAN
	if(image->bpc > 8)
       png_set_swap(pngPtr);
#endif

	// write data
	size_t rowSize = image->width * (image->bpp / 8);
	uint32_t offset = 0;
	uint8_t *data = image->data;
	for(uint32_t y = 0; y < image->height; y++){
		png_write_row(pngPtr, data+offset);
		offset += rowSize;
	}

	png_write_end(pngPtr, infoPtr);

	png_destroy_write_struct(&pngPtr, &infoPtr);
	fclose(file);
	return LEXLIB_OK;
#else // ..libpng
	if(image->bpc > 8)
		return LEXLIB_UNSUPORTED;
	return lexlibStbImageWritePng(filename, image->data, image->width, image->height, image->channels, 0); // one line :D
#endif
}