commit 77fecf49100c5f98c513a07b90ae8ee6ae65045a
parent 0f3db1ca56a848b4157250011586562213eb1514
Author: vaplv <vaplv@free.fr>
Date: Wed, 29 Mar 2017 17:52:29 +0200
Add a image_ppm_read_stream function
Test the image ppm API.
Diffstat:
4 files changed, 345 insertions(+), 5 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,123 @@
#include "io_c99.h"
#endif
+enum ppm_id { P3, P6 };
+
+struct parser {
+ char buf[512];
+ char* tk;
+ FILE* stream;
+};
+
+/*******************************************************************************
+ * Helper functions
+ ******************************************************************************/
+static INLINE size_t
+sizeof_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;
+ }
+}
+
+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(str && id);
+ 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;
+}
+
+/*******************************************************************************
+ * Exported functions
+ ******************************************************************************/
res_T
image_ppm_write
(const char* path,
@@ -60,11 +179,7 @@ image_ppm_write_stream
char buf[BUFSIZ];
res_T res = RES_OK;
- if(width && height && Bpp && !buffer) {
- res = RES_BAD_ARG;
- goto error;
- }
- if(!fp) {
+ if(!width || !height || !Bpp || !buffer || !fp) {
res = RES_BAD_ARG;
goto error;
}
@@ -108,3 +223,68 @@ error:
goto exit;
}
+res_T
+image_ppm_read_stream
+ (FILE* fp,
+ struct mem_allocator* mem_allocator,
+ size_t* out_width,
+ size_t* out_height,
+ enum image_format* out_fmt,
+ char** out_buffer)
+{
+ struct parser parser;
+ struct mem_allocator* allocator;
+ char* buffer = NULL;
+ unsigned long width=0, height=0, max_val;
+ enum ppm_id id;
+ enum image_format fmt = IMAGE_RGB8;
+ res_T res = RES_OK;
+
+ allocator = mem_allocator ? mem_allocator : &mem_default_allocator;
+ if(!fp || !out_width || !out_height || !out_fmt || !out_buffer) {
+ res = RES_BAD_ARG;
+ goto error;
+ }
+
+ parser_init(&parser, fp);
+
+ /* 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 >= 65536) {
+ res = RES_BAD_ARG;
+ goto error;
+ }
+
+ /* Allocate the image buffer */
+ fmt = max_val <= 255 ? IMAGE_RGB8 : IMAGE_RGB16;
+ buffer = MEM_ALLOC(allocator, width * height * sizeof_format(fmt));
+ if(!buffer) {
+ res = RES_MEM_ERR;
+ goto error;
+ }
+
+ /* Read pixel data */
+ switch(id) {
+ case P3: res = parse_raw_pixels(&parser, width, height, fmt, buffer); break;
+ case P6: res = parse_bin_pixels(&parser, width, height, fmt, buffer); break;
+ default: FATAL("Unreachable code.\n"); break;
+ }
+
+exit:
+ if(out_width) *out_width = width;
+ if(out_height) *out_height = height;
+ if(out_fmt) *out_fmt = fmt;
+ if(out_buffer) *out_buffer = buffer;
+ return res;
+error:
+ if(buffer) MEM_RM(allocator, buffer), buffer = NULL;
+ goto exit;
+}
+
diff --git a/src/image.h b/src/image.h
@@ -18,6 +18,13 @@
#include "rsys.h"
+struct mem_allocator;
+
+enum image_format {
+ IMAGE_RGB8,
+ IMAGE_RGB16
+};
+
BEGIN_DECLS
RSYS_API res_T
@@ -36,6 +43,15 @@ image_ppm_write_stream
const int bytes_per_pixel,
const unsigned char* buffer);
+RSYS_API res_T
+image_ppm_read_stream
+ (FILE* stream,
+ struct mem_allocator* mem_allocator,
+ size_t* width,
+ size_t* height,
+ enum image_format* fmt,
+ char** buffer);
+
END_DECLS
#endif /* IMAGE_H */
diff --git a/src/test_image.c b/src/test_image.c
@@ -0,0 +1,143 @@
+/* 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"
+
+#define WIDTH 64
+#define HEIGHT 32
+
+int
+main(int argc, char** argv)
+{
+ unsigned char lut[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}}
+ };
+ unsigned char* pixels;
+ char* buf;
+ FILE* fp;
+ size_t w, h;
+ enum image_format fmt;
+ int x, y, i = 0;
+ (void)argc, (void)argv;
+
+ fp = tmpfile();
+ NCHECK(fp, NULL);
+
+ pixels = mem_alloc(WIDTH*HEIGHT*3);
+ 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;
+
+ pixels[i++] = lut[j][k][0];
+ pixels[i++] = lut[j][k][1];
+ pixels[i++] = lut[j][k][2];
+ }}
+
+ CHECK(image_ppm_write_stream(NULL, 0, 0, 0, NULL), RES_BAD_ARG);
+ CHECK(image_ppm_write_stream(fp, 0, 0, 0, NULL), RES_BAD_ARG);
+ CHECK(image_ppm_write_stream(NULL, WIDTH, 0, 0, NULL), RES_BAD_ARG);
+ CHECK(image_ppm_write_stream(fp, WIDTH, 0, 0, NULL), RES_BAD_ARG);
+ CHECK(image_ppm_write_stream(NULL, 0, HEIGHT, 0, NULL), RES_BAD_ARG);
+ CHECK(image_ppm_write_stream(fp, 0, HEIGHT, 0, NULL), RES_BAD_ARG);
+ CHECK(image_ppm_write_stream(NULL, WIDTH, HEIGHT, 0, NULL), RES_BAD_ARG);
+ CHECK(image_ppm_write_stream(fp, WIDTH, HEIGHT, 0, NULL), RES_BAD_ARG);
+ CHECK(image_ppm_write_stream(NULL, 0, 0, 3, NULL), RES_BAD_ARG);
+ CHECK(image_ppm_write_stream(fp, 0, 0, 3, NULL), RES_BAD_ARG);
+ CHECK(image_ppm_write_stream(NULL, WIDTH, 0, 3, NULL), RES_BAD_ARG);
+ CHECK(image_ppm_write_stream(fp, WIDTH, 0, 3, NULL), RES_BAD_ARG);
+ CHECK(image_ppm_write_stream(NULL, 0, HEIGHT, 3, NULL), RES_BAD_ARG);
+ CHECK(image_ppm_write_stream(fp, 0, HEIGHT, 3, NULL), RES_BAD_ARG);
+ CHECK(image_ppm_write_stream(NULL, WIDTH, HEIGHT, 3, NULL), RES_BAD_ARG);
+ CHECK(image_ppm_write_stream(fp, WIDTH, HEIGHT, 3, NULL), RES_BAD_ARG);
+ CHECK(image_ppm_write_stream(NULL, 0, 0, 0, pixels), RES_BAD_ARG);
+ CHECK(image_ppm_write_stream(fp, 0, 0, 0, pixels), RES_BAD_ARG);
+ CHECK(image_ppm_write_stream(NULL, WIDTH, 0, 0, pixels), RES_BAD_ARG);
+ CHECK(image_ppm_write_stream(fp, WIDTH, 0, 0, pixels), RES_BAD_ARG);
+ CHECK(image_ppm_write_stream(NULL, 0, HEIGHT, 0, pixels), RES_BAD_ARG);
+ CHECK(image_ppm_write_stream(fp, 0, HEIGHT, 0, pixels), RES_BAD_ARG);
+ CHECK(image_ppm_write_stream(NULL, WIDTH, HEIGHT, 0, pixels), RES_BAD_ARG);
+ CHECK(image_ppm_write_stream(fp, WIDTH, HEIGHT, 0, pixels), RES_BAD_ARG);
+ CHECK(image_ppm_write_stream(NULL, 0, 0, 3, pixels), RES_BAD_ARG);
+ CHECK(image_ppm_write_stream(fp, 0, 0, 3, pixels), RES_BAD_ARG);
+ CHECK(image_ppm_write_stream(NULL, WIDTH, 0, 3, pixels), RES_BAD_ARG);
+ CHECK(image_ppm_write_stream(fp, WIDTH, 0, 3, pixels), RES_BAD_ARG);
+ CHECK(image_ppm_write_stream(NULL, 0, HEIGHT, 3, pixels), RES_BAD_ARG);
+ CHECK(image_ppm_write_stream(fp, 0, HEIGHT, 3, pixels), RES_BAD_ARG);
+ CHECK(image_ppm_write_stream(NULL, WIDTH, HEIGHT, 3, pixels), RES_BAD_ARG);
+ CHECK(image_ppm_write_stream(fp, WIDTH, HEIGHT, 3, pixels), RES_OK);
+ rewind(fp);
+
+ CHECK(image_ppm_read_stream(NULL, NULL, NULL, NULL, NULL, NULL), RES_BAD_ARG);
+ CHECK(image_ppm_read_stream(fp, NULL, NULL, NULL, NULL, NULL), RES_BAD_ARG);
+ CHECK(image_ppm_read_stream(NULL, NULL, &w, NULL, NULL, NULL), RES_BAD_ARG);
+ CHECK(image_ppm_read_stream(fp, NULL, &w, NULL, NULL, NULL), RES_BAD_ARG);
+ CHECK(image_ppm_read_stream(NULL, NULL, NULL, &h, NULL, NULL), RES_BAD_ARG);
+ CHECK(image_ppm_read_stream(fp, NULL, NULL, &h, NULL, NULL), RES_BAD_ARG);
+ CHECK(image_ppm_read_stream(NULL, NULL, &w, &h, NULL, NULL), RES_BAD_ARG);
+ CHECK(image_ppm_read_stream(fp, NULL, &w, &h, NULL, NULL), RES_BAD_ARG);
+ CHECK(image_ppm_read_stream(NULL, NULL, NULL, NULL, &fmt, NULL), RES_BAD_ARG);
+ CHECK(image_ppm_read_stream(fp, NULL, NULL, NULL, &fmt, NULL), RES_BAD_ARG);
+ CHECK(image_ppm_read_stream(NULL, NULL, &w, NULL, &fmt, NULL), RES_BAD_ARG);
+ CHECK(image_ppm_read_stream(fp, NULL, &w, NULL, &fmt, NULL), RES_BAD_ARG);
+ CHECK(image_ppm_read_stream(NULL, NULL, NULL, &h, &fmt, NULL), RES_BAD_ARG);
+ CHECK(image_ppm_read_stream(fp, NULL, NULL, &h, &fmt, NULL), RES_BAD_ARG);
+ CHECK(image_ppm_read_stream(NULL, NULL, &w, &h, &fmt, NULL), RES_BAD_ARG);
+ CHECK(image_ppm_read_stream(fp, NULL, &w, &h, &fmt, NULL), RES_BAD_ARG);
+ CHECK(image_ppm_read_stream(NULL, NULL, NULL, NULL, NULL, &buf), RES_BAD_ARG);
+ CHECK(image_ppm_read_stream(fp, NULL, NULL, NULL, NULL, &buf), RES_BAD_ARG);
+ CHECK(image_ppm_read_stream(NULL, NULL, &w, NULL, NULL, &buf), RES_BAD_ARG);
+ CHECK(image_ppm_read_stream(fp, NULL, &w, NULL, NULL, &buf), RES_BAD_ARG);
+ CHECK(image_ppm_read_stream(NULL, NULL, NULL, &h, NULL, &buf), RES_BAD_ARG);
+ CHECK(image_ppm_read_stream(fp, NULL, NULL, &h, NULL, &buf), RES_BAD_ARG);
+ CHECK(image_ppm_read_stream(NULL, NULL, &w, &h, NULL, &buf), RES_BAD_ARG);
+ CHECK(image_ppm_read_stream(fp, NULL, &w, &h, NULL, &buf), RES_BAD_ARG);
+ CHECK(image_ppm_read_stream(NULL, NULL, NULL, NULL, &fmt, &buf), RES_BAD_ARG);
+ CHECK(image_ppm_read_stream(fp, NULL, NULL, NULL, &fmt, &buf), RES_BAD_ARG);
+ CHECK(image_ppm_read_stream(NULL, NULL, &w, NULL, &fmt, &buf), RES_BAD_ARG);
+ CHECK(image_ppm_read_stream(fp, NULL, &w, NULL, &fmt, &buf), RES_BAD_ARG);
+ CHECK(image_ppm_read_stream(NULL, NULL, NULL, &h, &fmt, &buf), RES_BAD_ARG);
+ CHECK(image_ppm_read_stream(fp, NULL, NULL, &h, &fmt, &buf), RES_BAD_ARG);
+ CHECK(image_ppm_read_stream(NULL, NULL, &w, &h, &fmt, &buf), RES_BAD_ARG);
+ CHECK(image_ppm_read_stream(fp, NULL, &w, &h, &fmt, &buf), RES_OK);
+ NCHECK(buf, NULL);
+ CHECK(w, WIDTH);
+ CHECK(h, HEIGHT);
+ CHECK(fmt, IMAGE_RGB8);
+
+ i = 0;
+ FOR_EACH(y, 0, HEIGHT) {
+ FOR_EACH(x, 0, WIDTH) {
+ CHECK(((unsigned char*)buf)[i], pixels[i]), ++i;
+ CHECK(((unsigned char*)buf)[i], pixels[i]), ++i;
+ CHECK(((unsigned char*)buf)[i], pixels[i]), ++i;
+ }}
+
+ CHECK(image_ppm_write_stream
+ (stdout, WIDTH, HEIGHT, 3, (unsigned char*)buf), RES_OK);
+
+ mem_rm(buf);
+ fclose(fp);
+ mem_rm(pixels);
+ CHECK(mem_allocated_size(), 0);
+ return 0;
+}