rsys

Basic data structures and low-level features
git clone git://git.meso-star.fr/rsys.git
Log | Files | Refs | README | LICENSE

commit 1ab15ecc0b7027970c9b1d4e6f64a8692a91e86f
parent 0f3db1ca56a848b4157250011586562213eb1514
Author: vaplv <vaplv@free.fr>
Date:   Sat,  1 Apr 2017 09:40:29 +0200

Merge branch 'feature_image_ppm'

Diffstat:
Mcmake/CMakeLists.txt | 1+
Msrc/image.c | 392++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Msrc/image.h | 70+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Msrc/rsys.h | 17++++++++++++++++-
Asrc/test_image.c | 300+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 755 insertions(+), 25 deletions(-)

diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt @@ -183,6 +183,7 @@ if(NOT NO_TEST) new_test(test_free_list rsys) new_test(test_func_name) new_test(test_hash_table rsys) + new_test(test_image rsys) new_test(test_library rsys) new_test(test_list rsys) new_test(test_logger rsys) diff --git a/src/image.c b/src/image.c @@ -14,7 +14,9 @@ * along with the RSys library. If not, see <http://www.gnu.org/licenses/>. */ #define _POSIX_C_SOURCE 200112L /* snprintf support */ +#include "cstr.h" #include "image.h" +#include "mem_allocator.h" #include <stdio.h> #include <string.h> @@ -22,6 +24,361 @@ #include "io_c99.h" #endif +enum ppm_id { P3, P6 }; + +struct parser { + char buf[512]; + char* tk; + FILE* stream; +}; + +/******************************************************************************* + * Helper functions + ******************************************************************************/ +static void +parser_init(struct parser* parser, FILE* stream) +{ + ASSERT(parser && stream); + parser->tk = NULL; + parser->stream = stream; +} + +static char* +parser_next_token(struct parser* parser) +{ + do { + if(parser->tk) parser->tk = strtok(NULL, " \t\n"); + + if(!parser->tk) { + char* line = fgets(parser->buf, (int)sizeof(parser->buf), parser->stream); + if(line) parser->tk = strtok(line, " \t\n"); + } + + if(parser->tk && parser->tk[0] == '#') + parser->tk = NULL; + + } while(!parser->tk && !feof(parser->stream)); + return parser->tk; +} + +static INLINE res_T +parse_ppm_id(const char* str, enum ppm_id* id) +{ + ASSERT(id); + if(!str) return RES_BAD_ARG; + if(!strcmp(str, "P3")) { + *id = P3; + } else if(!strcmp(str, "P6")) { + *id = P6; + } else { + return RES_BAD_ARG; + } + return RES_OK; +} + +static res_T +parse_raw_pixels + (struct parser* parser, + const size_t width, + const size_t height, + const enum image_format fmt, + char* buffer) +{ + size_t i, n; + res_T res = RES_OK; + ASSERT(parser && width > 0 && height > 0 && buffer); + + n = (size_t)(width * height * 3/*#channels*/); + FOR_EACH(i, 0, n) { + unsigned val; + + res = cstr_to_uint(parser_next_token(parser), &val); + if(res != RES_OK) return res; + + switch(fmt) { + case IMAGE_RGB8: + if(val > UINT8_MAX) return RES_BAD_ARG; + ((uint8_t*)buffer)[i] = (uint8_t)val; + break; + case IMAGE_RGB16: + if(val > UINT16_MAX) return RES_BAD_ARG; + ((uint16_t*)buffer)[i] = (uint16_t)val; + break; + default: FATAL("Unreachable code.\n"); break; + } + } + return RES_OK; +} + +static INLINE res_T +parse_bin_pixels + (struct parser* parser, + const size_t width, + const size_t height, + const enum image_format fmt, + char* buffer) +{ + size_t n, size; + ASSERT(parser && width > 0 && height > 0 && buffer); + switch(fmt) { + case IMAGE_RGB8: size = 1; break; + case IMAGE_RGB16: size = 2; break; + default: FATAL("Unreachable code.\n"); break; + } + n = (size_t)(width * height * 3/*#channels*/); + return (n == fread(buffer, size, n, parser->stream)) ? RES_OK : RES_BAD_ARG; +} + +static res_T +write_bin_ppm(const struct image* img, FILE* stream) +{ + size_t y; + ASSERT(img && stream); + + FOR_EACH(y, 0, img->height) { + const char* row = img->pixels + y * img->pitch; + size_t n; + n = fwrite(row, sizeof_image_format(img->format), img->width, stream); + if(n < img->width) return RES_IO_ERR; + } + return RES_OK; +} + +static res_T +write_raw_ppm(const struct image* img, FILE* stream) +{ + size_t x, y; + ASSERT(img && stream); + + FOR_EACH(y, 0, img->height) { + const char* row = img->pixels + y * img->pitch; + FOR_EACH(x, 0, img->width) { + const char* pixel = row + x * sizeof_image_format(img->format); + switch(img->format) { + case IMAGE_RGB8: + fprintf(stream, "%u %u %u\n", SPLIT3((uint8_t*)pixel)); + break; + case IMAGE_RGB16: + fprintf(stream, "%u %u %u\n", SPLIT3(((uint16_t*)pixel))); + break; + default: FATAL("Unreachable code.\n"); break; + } + } + } + return RES_OK; +} + +/******************************************************************************* + * Exported functions + ******************************************************************************/ +res_T +image_init(struct mem_allocator* mem_allocator, struct image* img) +{ + struct mem_allocator* allocator; + if(!img) return RES_BAD_ARG; + allocator = mem_allocator ? mem_allocator : &mem_default_allocator; + memset(img, 0, sizeof(struct image)); + img->allocator = allocator; + return RES_OK; +} + +res_T +image_release(struct image* img) +{ + if(!img) return RES_BAD_ARG; + if(img->pixels) MEM_RM(img->allocator, img->pixels); + return RES_OK; +} + +res_T +image_setup + (struct image* img, + const size_t width, + const size_t height, + const size_t pitch, + const enum image_format format, + const char* pixels) +{ + size_t size; + char* buffer = NULL; + res_T res = RES_OK; + + if(!img || !width || !height || !pitch || pitch < width) { + res = RES_BAD_ARG; + goto error; + } + + size = height * pitch; + if(size != img->height * img->pitch) { + buffer = MEM_ALLOC(img->allocator, size); + if(!buffer) { + res = RES_MEM_ERR; + goto error; + } + if(img->pixels) MEM_RM(img->allocator, img->pixels); + img->pixels = buffer; + } + + if(pixels) { + memcpy(img->pixels, pixels, size); + } + + img->width = width; + img->height = height; + img->pitch = pitch; + img->format = format; + +exit: + return res; +error: + if(buffer) MEM_RM(img->allocator, buffer); + goto exit; +} + +res_T +image_read_ppm(struct image* img, const char* filename) +{ + FILE* stream = NULL; + res_T res = RES_OK; + + if(!img || !filename) { + res = RES_BAD_ARG; + goto error; + } + + stream = fopen(filename, "r"); + if(!stream) { + res = RES_IO_ERR; + goto error; + } + + res = image_read_ppm_stream(img, stream); + if(res != RES_OK) goto error; + +exit: + if(stream) fclose(stream); + return res; +error: + goto exit; +} + +res_T +image_read_ppm_stream(struct image* img, FILE* stream) +{ + struct parser parser; + size_t pitch; + unsigned long width=0, height=0, max_val=0; + enum ppm_id id; + enum image_format fmt; + res_T res = RES_OK; + + if(!img || !stream) return RES_BAD_ARG; + + parser_init(&parser, stream); + + /* Read header */ + #define CALL(Func) { res = Func; if(res != RES_OK) goto error; } (void)0 + CALL(parse_ppm_id(parser_next_token(&parser), &id)); + CALL(cstr_to_ulong(parser_next_token(&parser), &width)); + CALL(cstr_to_ulong(parser_next_token(&parser), &height)); + CALL(cstr_to_ulong(parser_next_token(&parser), &max_val)); + #undef CALL + + /* Check header */ + if(!width || !height || !max_val || max_val > 65535) { + res = RES_BAD_ARG; + goto error; + } + + /* Allocate the image buffer */ + fmt = max_val <= 255 ? IMAGE_RGB8 : IMAGE_RGB16; + pitch = width * sizeof_image_format(fmt); + res = image_setup(img, width, height, pitch, fmt, NULL); + if(res != RES_OK) goto error; + + /* Read pixel data */ + switch(id) { + case P3: + res = parse_raw_pixels(&parser, width, height, fmt, img->pixels); + break; + case P6: + res = parse_bin_pixels(&parser, width, height, fmt, img->pixels); + break; + default: FATAL("Unreachable code.\n"); break; + } + +exit: + return res; +error: + goto exit; +} + +res_T +image_write_ppm + (const struct image* img, + const int binary, + const char* filename) +{ + FILE* stream = NULL; + res_T res = RES_OK; + + if(!img || !filename) { + res = RES_BAD_ARG; + goto error; + } + + stream = fopen(filename, "w"); + if(!stream) { + res = RES_IO_ERR; + goto error; + } + + res = image_write_ppm_stream(img, binary, stream); + if(res != RES_OK) goto error; + +exit: + if(stream) fclose(stream); + return res; +error: + goto exit; +} + +res_T +image_write_ppm_stream + (const struct image* img, + const int bin, + FILE* stream) +{ + res_T res = RES_OK; + + if(!img || !stream) { + res = RES_BAD_ARG; + goto error; + } + + fprintf(stream, "%s %lu %lu\n", bin ? "P6" : "P3", + (unsigned long)img->width, + (unsigned long)img->height); + switch(img->format) { /* Write Max val */ + case IMAGE_RGB8: fprintf(stream, "255\n"); break; + case IMAGE_RGB16: fprintf(stream, "65535\n"); break; + default: FATAL("Unreachable code.\n"); break; + } + + if(bin) { + res = write_bin_ppm(img, stream); + } else { + res = write_raw_ppm(img, stream); + } + if(res != RES_OK) goto error; + + +exit: + return res; +error: + goto exit; +} + res_T image_ppm_write (const char* path, @@ -57,32 +414,20 @@ image_ppm_write_stream const int Bpp, const unsigned char* buffer) { - char buf[BUFSIZ]; + int i; res_T res = RES_OK; - if(width && height && Bpp && !buffer) { + if(!width || !height || !Bpp || !buffer || !fp) { res = RES_BAD_ARG; goto error; } - if(!fp) { - res = RES_BAD_ARG; + + i = fprintf(fp, "P3\n\n%i %i\n%i\n", width, height, 255); + if(i < 0) { + res = RES_IO_ERR; goto error; } - #define FWRITE(Fp, String) \ - { \ - const size_t i = fwrite(String, sizeof(char), strlen(String), Fp); \ - if(i != strlen(String) * sizeof(char)) { res = RES_MEM_ERR; goto error; }\ - } (void)0 - #define SNPRINTF(Buf, Sz, Str, Arg0, Arg1, Arg2) \ - { \ - const int i = snprintf(Buf, Sz, Str, Arg0, Arg1, Arg2); \ - if( i >= BUFSIZ ) { res = RES_MEM_ERR; goto error; } \ - } (void)0 - - SNPRINTF(buf, BUFSIZ, "P3\n\n%i %i\n%i\n", width, height, 255); - FWRITE(fp, buf); - if(Bpp) { const long pitch = width * Bpp; int x, y; @@ -90,17 +435,18 @@ image_ppm_write_stream const unsigned char* row = buffer + y * pitch; for(x = 0; x < width; ++x) { const unsigned char* pixel = row + x * Bpp; - SNPRINTF(buf, BUFSIZ, "%u %u %u\n", + i = fprintf(fp, "%u %u %u\n", pixel[0], Bpp > 1 ? pixel[1] : pixel[0], Bpp > 2 ? pixel[2] : pixel[0]); - FWRITE(fp, buf); + if(i < 0) { + res = RES_IO_ERR; + goto error; + } } - FWRITE(fp, "\n"); + fprintf(fp, "\n"); } } - #undef SNPRINTF - #undef FWRITE exit: return res; diff --git a/src/image.h b/src/image.h @@ -18,9 +18,77 @@ #include "rsys.h" +struct mem_allocator; + +enum image_format { + IMAGE_RGB8, + IMAGE_RGB16 +}; + +struct image { + size_t width; + size_t height; + size_t pitch; + enum image_format format; + char* pixels; + + /* Internal data */ + struct mem_allocator* allocator; +}; + +static FINLINE size_t +sizeof_image_format(const enum image_format fmt) +{ + switch(fmt) { + case IMAGE_RGB8: return sizeof(uint8_t[3]); + case IMAGE_RGB16: return sizeof(uint16_t[3]); + default: FATAL("Unreachable code.\n"); break; + } +} + BEGIN_DECLS RSYS_API res_T +image_init + (struct mem_allocator* allocator, /* May be NULL */ + struct image* img); + +RSYS_API res_T +image_release + (struct image* img); + +RSYS_API res_T +image_setup + (struct image* image, + const size_t width, + const size_t height, + const size_t pitch, + const enum image_format format, + const char* pixels); /* May be NULL */ + +RSYS_API res_T +image_read_ppm + (struct image* image, + const char* filename); + +RSYS_API res_T +image_read_ppm_stream + (struct image* image, + FILE* stream); + +RSYS_API res_T +image_write_ppm + (const struct image* image, + const int binary, + const char* filename); + +RSYS_API res_T +image_write_ppm_stream + (const struct image* image, + const int binary, + FILE* stream); + +DEPRECATED RSYS_API res_T image_ppm_write (const char* path, const int width, @@ -28,7 +96,7 @@ image_ppm_write const int bytes_per_pixel, const unsigned char* buffer); -RSYS_API res_T +DEPRECATED RSYS_API res_T image_ppm_write_stream (FILE* stream, const int width, diff --git a/src/rsys.h b/src/rsys.h @@ -343,7 +343,14 @@ typedef int res_T; #define CONTAINER_OF(Ptr, Type, Member) \ ((Type*)((uintptr_t)Ptr - offsetof(Type, Member))) -#define RESTRICT __restrict__ +#ifdef COMPILER_CL + #define RESTRICT __restrict +#elif defined COMPILER_GCC + #define RESTRICT __restrict__ +#else + #define RESTRICT +#endif + #define COUNTER __COUNTER__ #define SWAP(Type, A, B) \ @@ -369,6 +376,14 @@ typedef int res_T; ? (uintptr_t)(B) < ((uintptr_t)(A) + (SzA)) \ : (uintptr_t)(A) < ((uintptr_t)(B) + (SzB))) +#ifdef COMPILER_CL + #define DEPRECATED __declspec(deprecated) +#elif defined COMPILER_GCC + #define DEPRECATED __attribute__((deprecated)) +#else + #define DEPRECATED +#endif + #ifdef __cplusplus #define BEGIN_DECLS extern "C" { #define END_DECLS } diff --git a/src/test_image.c b/src/test_image.c @@ -0,0 +1,300 @@ +/* Copyright (C) 2013-2017 Vincent Forest (vaplv@free.fr) + * + * The RSys library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The RSys library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the RSys library. If not, see <http://www.gnu.org/licenses/>. */ + +#include "image.h" +#include "mem_allocator.h" +#include "test_utils.h" + +#define WIDTH 64 +#define HEIGHT 32 + +static void +check_image_eq + (const struct image* img, + const char* ref_pixels, + const size_t ref_width, + const size_t ref_height, + const enum image_format ref_fmt) +{ + size_t x, y; + size_t i; + + CHECK(img->format, ref_fmt); + CHECK(img->height, ref_height); + CHECK(img->width, ref_width); + CHECK(img->pitch >= img->width, 1); + + i = 0; + FOR_EACH(y, 0, img->height) { + const char* row = img->pixels + img->pitch * (size_t)y; + FOR_EACH(x, 0, img->width) { + const char* pixel = row + (size_t)x*sizeof_image_format(img->format); + switch(img->format) { + case IMAGE_RGB8: + CHECK(((uint8_t*)pixel)[0], ((uint8_t*)ref_pixels)[i]), ++i; + CHECK(((uint8_t*)pixel)[1], ((uint8_t*)ref_pixels)[i]), ++i; + CHECK(((uint8_t*)pixel)[2], ((uint8_t*)ref_pixels)[i]), ++i; + break; + case IMAGE_RGB16: + CHECK(((uint16_t*)pixel)[0], ((uint16_t*)ref_pixels)[i]), ++i; + CHECK(((uint16_t*)pixel)[1], ((uint16_t*)ref_pixels)[i]), ++i; + CHECK(((uint16_t*)pixel)[2], ((uint16_t*)ref_pixels)[i]), ++i; + break; + default: FATAL("Unreachable code.\n"); break; + } + } + } +} + +static void +check_image_read + (FILE* fp, + const size_t width, + const size_t height, + const enum image_format fmt, + struct mem_allocator* allocator) +{ + struct image img; + size_t max_val; + int is_bin; + char id[2]; + + CHECK(image_init(allocator, &img), RES_OK); + + CHECK(fseek(fp, 0, SEEK_SET), 0); + CHECK(image_read_ppm_stream(&img, fp), RES_OK); + + CHECK(fseek(fp, 0, SEEK_SET), 0); + CHECK(fread(id, 1, 2, fp), 2); + CHECK(id[0], 'P'); + CHECK(id[1] == '3' || id[1] == '6', 1); + is_bin = id[1] == '6'; + + switch(fmt) { + case IMAGE_RGB8: max_val = 255; break; + case IMAGE_RGB16: max_val = 65535; break; + default: FATAL("Unreachable code.\n"); break; + } + + CHECK(fseek(fp, 3, SEEK_SET), 0); + fprintf(fp, "%lu %lu\n", (unsigned long)width+1, (unsigned long)height); + CHECK(fseek(fp, 0, SEEK_SET), 0); + CHECK(image_read_ppm_stream(&img, fp), RES_BAD_ARG); + CHECK(fseek(fp, 3, SEEK_SET), 0); + fprintf(fp, "%lu %lu\n", (unsigned long)width, (unsigned long)height+1); + CHECK(fseek(fp, 0, SEEK_SET), 0); + CHECK(image_read_ppm_stream(&img, fp), RES_BAD_ARG); + CHECK(fseek(fp, 3, SEEK_SET), 0); + fprintf(fp, "%lu %lu\n", (unsigned long)width, (unsigned long)height); + fprintf(fp, "%lu\n", (unsigned long)max_val+1); + CHECK(fseek(fp, 0, SEEK_SET), 0); + + if(is_bin) { + CHECK(image_read_ppm_stream(&img, fp), RES_BAD_ARG); + } else { + switch(fmt) { + case IMAGE_RGB8: + CHECK(image_read_ppm_stream(&img, fp), RES_OK); + break; + case IMAGE_RGB16: + CHECK(image_read_ppm_stream(&img, fp), RES_BAD_ARG); + break; + default: FATAL("Unreachable code.\n"); break; + } + } + + CHECK(fseek(fp, 3, SEEK_SET), 0); + fprintf(fp, "%lu %lu\n", (unsigned long)width, (unsigned long)height); + fprintf(fp, "%lu\n", (unsigned long)max_val); + CHECK(fseek(fp, 0, SEEK_SET), 0); + CHECK(image_read_ppm_stream(&img, fp), RES_OK); + + CHECK(image_release(&img), RES_OK); +} + +static void +check_image + (const char* pixels, + const size_t width, + const size_t height, + const enum image_format fmt, + struct mem_allocator* allocator) +{ + struct image img; + size_t pitch; + FILE* fp; + + CHECK(image_init(NULL, NULL), RES_BAD_ARG); + CHECK(image_init(allocator, NULL), RES_BAD_ARG); + CHECK(image_init(NULL, &img), RES_OK); + CHECK(image_release(NULL), RES_BAD_ARG); + CHECK(image_release(&img), RES_OK); + CHECK(image_init(allocator, &img), RES_OK); + + pitch = width * sizeof_image_format(fmt); + + CHECK(image_setup(NULL, 0, 0, 0, fmt, NULL), RES_BAD_ARG); + CHECK(image_setup(&img, 0, 0, 0, fmt, NULL), RES_BAD_ARG); + CHECK(image_setup(NULL, width, 0, 0, fmt, NULL), RES_BAD_ARG); + CHECK(image_setup(&img, width, 0, 0, fmt, NULL), RES_BAD_ARG); + CHECK(image_setup(NULL, 0, height, 0, fmt, NULL), RES_BAD_ARG); + CHECK(image_setup(&img, 0, height, 0, fmt, NULL), RES_BAD_ARG); + CHECK(image_setup(NULL, width, height, 0, fmt, NULL), RES_BAD_ARG); + CHECK(image_setup(&img, width, height, 0, fmt, NULL), RES_BAD_ARG); + CHECK(image_setup(NULL, 0, 0, pitch, fmt, NULL), RES_BAD_ARG); + CHECK(image_setup(&img, 0, 0, pitch, fmt, NULL), RES_BAD_ARG); + CHECK(image_setup(NULL, width, 0, pitch, fmt, NULL), RES_BAD_ARG); + CHECK(image_setup(&img, width, 0, pitch, fmt, NULL), RES_BAD_ARG); + CHECK(image_setup(NULL, 0, height, pitch, fmt, NULL), RES_BAD_ARG); + CHECK(image_setup(&img, 0, height, pitch, fmt, NULL), RES_BAD_ARG); + CHECK(image_setup(NULL, width, height, pitch, fmt, NULL), RES_BAD_ARG); + CHECK(image_setup(&img, width, height, pitch, fmt, NULL), RES_OK); + CHECK(image_setup(NULL, 0, 0, 0, fmt, pixels), RES_BAD_ARG); + CHECK(image_setup(&img, 0, 0, 0, fmt, pixels), RES_BAD_ARG); + CHECK(image_setup(NULL, width, 0, 0, fmt, pixels), RES_BAD_ARG); + CHECK(image_setup(&img, width, 0, 0, fmt, pixels), RES_BAD_ARG); + CHECK(image_setup(NULL, 0, height, 0, fmt, pixels), RES_BAD_ARG); + CHECK(image_setup(&img, 0, height, 0, fmt, pixels), RES_BAD_ARG); + CHECK(image_setup(NULL, width, height, 0, fmt, pixels), RES_BAD_ARG); + CHECK(image_setup(&img, width, height, 0, fmt, pixels), RES_BAD_ARG); + CHECK(image_setup(NULL, 0, 0, pitch, fmt, pixels), RES_BAD_ARG); + CHECK(image_setup(&img, 0, 0, pitch, fmt, pixels), RES_BAD_ARG); + CHECK(image_setup(NULL, width, 0, pitch, fmt, pixels), RES_BAD_ARG); + CHECK(image_setup(&img, width, 0, pitch, fmt, pixels), RES_BAD_ARG); + CHECK(image_setup(NULL, 0, height, pitch, fmt, pixels), RES_BAD_ARG); + CHECK(image_setup(&img, 0, height, pitch, fmt, pixels), RES_BAD_ARG); + CHECK(image_setup(NULL, width, height, pitch, fmt, pixels), RES_BAD_ARG); + CHECK(image_setup(&img, width, height, pitch, fmt, pixels), RES_OK); + + fp = tmpfile(); + NCHECK(fp, NULL); + + CHECK(image_write_ppm_stream(NULL, 0, NULL), RES_BAD_ARG); + CHECK(image_write_ppm_stream(&img, 0, NULL), RES_BAD_ARG); + CHECK(image_write_ppm_stream(NULL, 0, fp), RES_BAD_ARG); + CHECK(image_write_ppm_stream(&img, 0, fp), RES_OK); + + CHECK(image_read_ppm_stream(NULL, NULL), RES_BAD_ARG); + CHECK(image_read_ppm_stream(&img, NULL), RES_BAD_ARG); + CHECK(image_read_ppm_stream(NULL, fp), RES_BAD_ARG); + CHECK(image_read_ppm_stream(&img, fp), RES_BAD_ARG); + rewind(fp); + CHECK(image_read_ppm_stream(&img, fp), RES_OK); + check_image_eq(&img, pixels, width, height, fmt); + + rewind(fp); + CHECK(image_write_ppm_stream(&img, 1, fp), RES_OK); + rewind(fp); + CHECK(image_read_ppm_stream(&img, fp), RES_OK); + check_image_eq(&img, pixels, width, height, fmt); + + CHECK(image_write_ppm(NULL, 0, NULL), RES_BAD_ARG); + CHECK(image_write_ppm(&img, 0, NULL), RES_BAD_ARG); + CHECK(image_write_ppm(NULL, 0, "test.ppm"), RES_BAD_ARG); + CHECK(image_write_ppm(&img, 0, "test.ppm"), RES_OK); + + CHECK(image_read_ppm(NULL, NULL), RES_BAD_ARG); + CHECK(image_read_ppm(&img, NULL), RES_BAD_ARG); + CHECK(image_read_ppm(NULL, "test_bad.ppm"), RES_BAD_ARG); + CHECK(image_read_ppm(&img, "test_bad.ppm"), RES_IO_ERR); + CHECK(image_read_ppm(&img, "test.ppm"), RES_OK); + + check_image_eq(&img, pixels, width, height, fmt); + + CHECK(image_write_ppm(&img, 1, "test.ppm"), RES_OK); + CHECK(image_read_ppm(&img, "test.ppm"), RES_OK); + check_image_eq(&img, pixels, width, height, fmt); + + CHECK(image_write_ppm_stream(&img, 1, stdout), RES_OK); + + fclose(fp); + + fp = tmpfile(); + NCHECK(fp, NULL); + CHECK(image_write_ppm_stream(&img, 0, fp), RES_OK); + check_image_read(fp, width, height, fmt, allocator); + fclose(fp); + + fp = tmpfile(); + NCHECK(fp, NULL); + CHECK(image_write_ppm_stream(&img, 1, fp), RES_OK); + check_image_read(fp, width, height, fmt, allocator); + fclose(fp); + + CHECK(image_release(&img), RES_OK); +} + +int +main(int argc, char** argv) +{ + uint8_t lut8[4][4][3] = { + {{0xFF,0xFF,0x00}, {0xFF,0x00,0x00}, {0xFF,0x00,0x00}, {0xFF,0xFF,0x00}}, + {{0xFF,0xFF,0xFF}, {0x00,0x00,0x00}, {0x00,0x00,0x00}, {0xFF,0xFF,0xFF}}, + {{0x00,0xFF,0x00}, {0x00,0xFF,0xFF}, {0x00,0xFF,0xFF}, {0x00,0xFF,0x00}}, + {{0x00,0x00,0xFF}, {0xFF,0x00,0xFF}, {0xFF,0x00,0xFF}, {0x00,0x00,0xFF}} + }; + uint16_t lut16[4][4][3] = { + { {0x0000,0x0000,0xFFFF}, {0xFFFF,0x0000,0xFFFF}, + {0xFFFF,0x0000,0xFFFF}, {0x0000,0x0000,0xFFFF} }, + { {0x0000,0xFFFF,0x0000}, {0x0000,0xFFFF,0xFFFF}, + {0x0000,0xFFFF,0xFFFF}, {0x0000,0xFFFF,0x0000} }, + { {0xFFFF,0xFFFF,0xFFFF}, {0x0000,0x0000,0x0000}, + {0x0000,0x0000,0x0000}, {0xFFFF,0xFFFF,0xFFFF} }, + { {0xFFFF,0xFFFF,0x0000}, {0xFFFF,0x0000,0x0000}, + {0xFFFF,0x0000,0x0000}, {0xFFFF,0xFFFF,0x0000} } + }; + struct mem_allocator allocator; + char* pixels; + int x, y, i = 0; + (void)argc, (void)argv; + + CHECK(mem_init_proxy_allocator(&allocator, &mem_default_allocator), RES_OK); + + pixels = MEM_ALLOC(&allocator, WIDTH*sizeof_image_format(IMAGE_RGB8)*HEIGHT); + NCHECK(pixels, NULL); + + i = 0; + FOR_EACH(y, 0, HEIGHT) { + FOR_EACH(x, 0, WIDTH) { + int j = ((x/32) & 1) + ((y/16)&1) * 2; + int k = ((x/8) & 1) + ((y/8)&1) * 2; + + ((uint8_t*)pixels)[i++] = lut8[j][k][0]; + ((uint8_t*)pixels)[i++] = lut8[j][k][1]; + ((uint8_t*)pixels)[i++] = lut8[j][k][2]; + }} + check_image(pixels, WIDTH, HEIGHT, IMAGE_RGB8, &allocator); + MEM_RM(&allocator, pixels); + + pixels = MEM_ALLOC(&allocator, WIDTH*sizeof_image_format(IMAGE_RGB16)*HEIGHT); + NCHECK(pixels, NULL); + + i = 0; + FOR_EACH(y, 0, HEIGHT) { + FOR_EACH(x, 0, WIDTH) { + int j = ((x/32) & 1) + ((y/16)&1) * 2; + int k = ((x/8) & 1) + ((y/8)&1) * 2; + ((uint16_t*)pixels)[i++] = lut16[j][k][0]; + ((uint16_t*)pixels)[i++] = lut16[j][k][1]; + ((uint16_t*)pixels)[i++] = lut16[j][k][2]; + }} + check_image(pixels, WIDTH, HEIGHT, IMAGE_RGB16, &allocator); + MEM_RM(&allocator, pixels); + + check_memory_allocator(&allocator); + mem_shutdown_proxy_allocator(&allocator); + CHECK(mem_allocated_size(), 0); + return 0; +}