#include "image_loader.h"
#include "display_utils.h"
#include <asm-generic/errno-base.h>
#include <errno.h>
#include <jpeglib.h>
#include <magic.h>
#include <png.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BUFFER_LENGTH 4
image_error_t image_loader_load(image_t *image, image_load_callback callback,
void *state) {
image_error_t error = image_deduce_type(image);
if (error != IMERR_SUCCESS) {
return error;
}
switch (image->type) {
case IMG_PPM:
return image_loader_load_ppm(image, callback, state);
case IMG_JPG:
return image_loader_load_jpeg(image, callback, state);
case IMG_PNG:
return image_loader_load_png(image, callback, state);
case IMG_UNKNOWN:
return IMERR_UNKNOWN_FORMAT;
}
return IMERR_UNKNOWN;
}
image_error_t image_error_from_errno() {
switch (errno) {
case ENOENT:
return IMERR_FILE_NOT_FOUND;
case EACCES:
return IMERR_FILE_NO_PERMISSIONS;
default:
return IMERR_FILE_CANT_OPEN;
}
}
image_error_t image_loader_load_ppm(image_t *image,
image_load_callback callback, void *state) {
FILE *file = fopen(image->path, "r");
if (file == NULL) {
return image_error_from_errno();
}
char descriptor[10];
short maxBrightness;
if (fscanf(file, "%2s %hd %hd %hd", descriptor, &image->width, &image->height,
&maxBrightness) != 4) {
return IMERR_WRONG_FORMAT;
}
if (strcmp(descriptor, "P6") != 0) {
return IMERR_WRONG_FORMAT;
}
fseek(file, 1, SEEK_CUR);
raw_pixel_onebit_t max = {
.red = maxBrightness, .green = maxBrightness, .blue = maxBrightness};
image->pixels =
malloc(image->width * image->height * sizeof(display_pixel_t));
if (image->pixels == NULL) {
return IMERR_UNKNOWN;
}
for (int i = 0; i < image->height * image->width; i++) {
raw_pixel_onebit_t pixel;
if (fread(&pixel, sizeof(raw_pixel_onebit_t), 1, file) < 1) {
free(image->pixels);
fclose(file);
return IMERR_UNKNOWN;
}
image->pixels[i] = raw_pixel_onebit_convert_to_display(pixel, max);
callback(state, (double)i / (image->height * image->width));
}
fclose(file);
return IMERR_SUCCESS;
}
image_error_t image_loader_load_jpeg(image_t *image,
image_load_callback callback,
void *state) {
struct jpeg_decompress_struct cinfo;
struct jpeg_error_mgr jerr;
JSAMPROW row_pointer[1];
FILE *infile = fopen(image->path, "r");
if (infile == NULL) {
return image_error_from_errno();
}
cinfo.err = jpeg_std_error(&jerr);
jpeg_create_decompress(&cinfo);
jpeg_stdio_src(&cinfo, infile);
jpeg_read_header(&cinfo, TRUE);
// handle errors
image->width = cinfo.image_width;
image->height = cinfo.image_height;
int image_size = cinfo.image_width * cinfo.image_height * 2;
unsigned char *out = malloc(image_size);
if (out == NULL) {
return IMERR_UNKNOWN;
fclose(infile);
}
cinfo.out_color_space = JCS_RGB565;
jpeg_start_decompress(&cinfo);
while (cinfo.output_scanline < cinfo.output_height) {
row_pointer[0] = &out[cinfo.output_scanline * cinfo.image_width * 2];
jpeg_read_scanlines(&cinfo, row_pointer, 1);
callback(state, (double)cinfo.output_scanline / cinfo.output_height);
}
jpeg_finish_decompress(&cinfo);
jpeg_destroy_decompress(&cinfo);
fclose(infile);
image->pixels = (display_pixel_t *)out;
return IMERR_SUCCESS;
}
image_error_t image_loader_load_png(image_t *image,
image_load_callback callback, void *state) {
FILE *infile = fopen(image->path, "r");
if (infile == NULL) {
return image_error_from_errno();
}
png_structp png =
png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
if (png == NULL) {
return IMERR_UNKNOWN;
}
png_infop info = png_create_info_struct(png);
if (info == NULL) {
return IMERR_WRONG_FORMAT;
}
png_init_io(png, infile);
png_read_info(png, info);
image->width = png_get_image_width(png, info);
image->height = png_get_image_height(png, info);
png_byte color_type = png_get_color_type(png, info);
png_byte bit_depth = png_get_bit_depth(png, info);
if (bit_depth == 16) {
png_set_strip_16(png);
}
if (color_type == PNG_COLOR_TYPE_PALETTE) {
png_set_palette_to_rgb(png);
}
if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) {
png_set_expand_gray_1_2_4_to_8(png);
}
if (png_get_valid(png, info, PNG_INFO_tRNS))
png_set_tRNS_to_alpha(png);
if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_GRAY ||
color_type == PNG_COLOR_TYPE_PALETTE) {
png_set_filler(png, 0xFF, PNG_FILLER_AFTER);
}
if (color_type == PNG_COLOR_TYPE_GRAY ||
color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
png_set_gray_to_rgb(png);
}
png_read_update_info(png, info);
png_bytep *row_pointers = malloc(sizeof(png_bytep) * image->height);
if (row_pointers == NULL) {
png_destroy_read_struct(&png, &info, NULL);
fclose(infile);
return IMERR_UNKNOWN;
}
for (int i = 0; i < image->height; i++) {
row_pointers[i] = malloc(png_get_rowbytes(png, info));
callback(state, 0.5 * ((double)i / image->height));
}
png_read_image(png, row_pointers);
fclose(infile);
png_destroy_read_struct(&png, &info, NULL);
display_pixel_t *pixels =
malloc(sizeof(display_pixel_t) * image->width * image->height);
if (pixels == NULL) {
png_destroy_read_struct(&png, &info, NULL);
free(row_pointers);
fclose(infile);
return IMERR_UNKNOWN;
}
for (int y = 0; y < image->height; y++) {
png_bytep row = row_pointers[y];
for (int x = 0; x < image->width; x++) {
png_bytep px = &(row[x * 4]);
int alpha = px[3];
int coef = 255 * 255;
pixels[y * image->width + x].fields.r =
((double)px[0] * alpha / coef) * DISPLAY_MAX_RED;
pixels[y * image->width + x].fields.g =
((double)px[1] * alpha / coef) * DISPLAY_MAX_GREEN;
pixels[y * image->width + x].fields.b =
((double)px[2] * alpha / coef) * DISPLAY_MAX_BLUE;
}
callback(state, 0.5 + 0.5 * ((double)y / image->height));
}
image->pixels = pixels;
return IMERR_SUCCESS;
}
image_error_t image_deduce_type(image_t *image) {
FILE *file = fopen(image->path, "r");
if (file == NULL) {
return image_error_from_errno();
}
magic_t magic = magic_open(MAGIC_MIME_TYPE);
magic_load(magic, NULL);
const char *mime = magic_file(magic, image->path);
if (strstr(mime, "jpeg") != NULL || strstr(mime, "jpg") != NULL) {
image->type = IMG_JPG;
} else if (strstr(mime, "image/png") != NULL) {
image->type = IMG_PNG;
} else if (strstr(mime, "image/x-portable-pixmap") != NULL ||
strstr(mime, "image/x-portable-anymap") != NULL) {
image->type = IMG_PPM;
} else {
magic_close(magic);
image->type = IMG_UNKNOWN;
return IMERR_UNKNOWN_FORMAT;
}
magic_close(magic);
return IMERR_SUCCESS;
}