image.c (8637B)
1 /* Copyright (C) 2013-2023, 2025 Vincent Forest (vaplv@free.fr) 2 * 3 * The RSys library is free software: you can redistribute it and/or modify 4 * it under the terms of the GNU General Public License as published 5 * by the Free Software Foundation, either version 3 of the License, or 6 * (at your option) any later version. 7 * 8 * The RSys library is distributed in the hope that it will be useful, 9 * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 * GNU General Public License for more details. 12 * 13 * You should have received a copy of the GNU General Public License 14 * along with the RSys library. If not, see <http://www.gnu.org/licenses/>. */ 15 16 #define _POSIX_C_SOURCE 200112L /* snprintf support */ 17 #include "cstr.h" 18 #include "image.h" 19 #include "mem_allocator.h" 20 #include <stdio.h> 21 #include <string.h> 22 23 enum ppm_id { P3, P6 }; 24 25 struct parser { 26 char buf[512]; 27 char* tk; 28 FILE* stream; 29 }; 30 31 /******************************************************************************* 32 * Helper functions 33 ******************************************************************************/ 34 static void 35 parser_init(struct parser* parser, FILE* stream) 36 { 37 ASSERT(parser && stream); 38 parser->tk = NULL; 39 parser->stream = stream; 40 } 41 42 static char* 43 parser_next_token(struct parser* parser) 44 { 45 do { 46 if(parser->tk) parser->tk = strtok(NULL, " \t\n"); 47 48 if(!parser->tk) { 49 char* line = fgets(parser->buf, (int)sizeof(parser->buf), parser->stream); 50 if(line) parser->tk = strtok(line, " \t\n"); 51 } 52 53 if(parser->tk && parser->tk[0] == '#') 54 parser->tk = NULL; 55 56 } while(!parser->tk && !feof(parser->stream)); 57 return parser->tk; 58 } 59 60 static INLINE res_T 61 parse_ppm_id(const char* str, enum ppm_id* id) 62 { 63 ASSERT(id); 64 if(!str) return RES_BAD_ARG; 65 if(!strcmp(str, "P3")) { 66 *id = P3; 67 } else if(!strcmp(str, "P6")) { 68 *id = P6; 69 } else { 70 return RES_BAD_ARG; 71 } 72 return RES_OK; 73 } 74 75 static res_T 76 parse_raw_pixels 77 (struct parser* parser, 78 const size_t width, 79 const size_t height, 80 const enum image_format fmt, 81 char* buffer) 82 { 83 size_t i, n; 84 res_T res = RES_OK; 85 ASSERT(parser && width > 0 && height > 0 && buffer); 86 87 n = (size_t)(width * height * 3/*#channels*/); 88 FOR_EACH(i, 0, n) { 89 unsigned val; 90 91 res = cstr_to_uint(parser_next_token(parser), &val); 92 if(res != RES_OK) return res; 93 94 switch(fmt) { 95 case IMAGE_RGB8: 96 if(val > UINT8_MAX) return RES_BAD_ARG; 97 ((uint8_t*)buffer)[i] = (uint8_t)val; 98 break; 99 case IMAGE_RGB16: 100 if(val > UINT16_MAX) return RES_BAD_ARG; 101 ((uint16_t*)buffer)[i] = (uint16_t)val; 102 break; 103 default: FATAL("Unreachable code.\n"); break; 104 } 105 } 106 return RES_OK; 107 } 108 109 static INLINE res_T 110 parse_bin_pixels 111 (struct parser* parser, 112 const size_t width, 113 const size_t height, 114 const enum image_format fmt, 115 char* buffer) 116 { 117 size_t n, size; 118 ASSERT(parser && width > 0 && height > 0 && buffer); 119 switch(fmt) { 120 case IMAGE_RGB8: size = 1; break; 121 case IMAGE_RGB16: size = 2; break; 122 default: FATAL("Unreachable code.\n"); break; 123 } 124 n = (size_t)(width * height * 3/*#channels*/); 125 return (n == fread(buffer, size, n, parser->stream)) ? RES_OK : RES_BAD_ARG; 126 } 127 128 static res_T 129 write_bin_ppm(const struct image* img, FILE* stream) 130 { 131 size_t y; 132 ASSERT(img && stream); 133 134 FOR_EACH(y, 0, img->height) { 135 const char* row = img->pixels + y * img->pitch; 136 size_t n; 137 n = fwrite(row, sizeof_image_format(img->format), img->width, stream); 138 if(n < img->width) return RES_IO_ERR; 139 } 140 return RES_OK; 141 } 142 143 static res_T 144 write_raw_ppm(const struct image* img, FILE* stream) 145 { 146 size_t x, y; 147 ASSERT(img && stream); 148 149 FOR_EACH(y, 0, img->height) { 150 const char* row = img->pixels + y * img->pitch; 151 FOR_EACH(x, 0, img->width) { 152 const char* pixel = row + x * sizeof_image_format(img->format); 153 switch(img->format) { 154 case IMAGE_RGB8: 155 fprintf(stream, "%u %u %u\n", SPLIT3((uint8_t*)pixel)); 156 break; 157 case IMAGE_RGB16: 158 fprintf(stream, "%u %u %u\n", SPLIT3(((uint16_t*)pixel))); 159 break; 160 default: FATAL("Unreachable code.\n"); break; 161 } 162 } 163 } 164 return RES_OK; 165 } 166 167 /******************************************************************************* 168 * Exported functions 169 ******************************************************************************/ 170 res_T 171 image_init(struct mem_allocator* mem_allocator, struct image* img) 172 { 173 struct mem_allocator* allocator; 174 if(!img) return RES_BAD_ARG; 175 allocator = mem_allocator ? mem_allocator : &mem_default_allocator; 176 memset(img, 0, sizeof(struct image)); 177 img->allocator = allocator; 178 return RES_OK; 179 } 180 181 res_T 182 image_release(struct image* img) 183 { 184 if(!img) return RES_BAD_ARG; 185 if(img->pixels) MEM_RM(img->allocator, img->pixels); 186 return RES_OK; 187 } 188 189 res_T 190 image_setup 191 (struct image* img, 192 const size_t width, 193 const size_t height, 194 const size_t pitch, 195 const enum image_format format, 196 const char* pixels) 197 { 198 size_t size; 199 char* buffer = NULL; 200 res_T res = RES_OK; 201 202 if(!img || !width || !height || !pitch || pitch < width) { 203 res = RES_BAD_ARG; 204 goto error; 205 } 206 207 size = height * pitch; 208 if(size != img->height * img->pitch) { 209 buffer = MEM_ALLOC(img->allocator, size); 210 if(!buffer) { 211 res = RES_MEM_ERR; 212 goto error; 213 } 214 if(img->pixels) MEM_RM(img->allocator, img->pixels); 215 img->pixels = buffer; 216 } 217 218 if(pixels) { 219 memcpy(img->pixels, pixels, size); 220 } 221 222 img->width = width; 223 img->height = height; 224 img->pitch = pitch; 225 img->format = format; 226 227 exit: 228 return res; 229 error: 230 if(buffer) MEM_RM(img->allocator, buffer); 231 goto exit; 232 } 233 234 res_T 235 image_read_ppm(struct image* img, const char* filename) 236 { 237 FILE* stream = NULL; 238 res_T res = RES_OK; 239 240 if(!img || !filename) { 241 res = RES_BAD_ARG; 242 goto error; 243 } 244 245 stream = fopen(filename, "r"); 246 if(!stream) { 247 res = RES_IO_ERR; 248 goto error; 249 } 250 251 res = image_read_ppm_stream(img, stream); 252 if(res != RES_OK) goto error; 253 254 exit: 255 if(stream) fclose(stream); 256 return res; 257 error: 258 goto exit; 259 } 260 261 res_T 262 image_read_ppm_stream(struct image* img, FILE* stream) 263 { 264 struct parser parser; 265 size_t pitch; 266 unsigned long width=0, height=0, max_val=0; 267 enum ppm_id id; 268 enum image_format fmt; 269 res_T res = RES_OK; 270 271 if(!img || !stream) return RES_BAD_ARG; 272 273 parser_init(&parser, stream); 274 275 /* Read header */ 276 #define CALL(Func) { res = Func; if(res != RES_OK) goto error; } (void)0 277 CALL(parse_ppm_id(parser_next_token(&parser), &id)); 278 CALL(cstr_to_ulong(parser_next_token(&parser), &width)); 279 CALL(cstr_to_ulong(parser_next_token(&parser), &height)); 280 CALL(cstr_to_ulong(parser_next_token(&parser), &max_val)); 281 #undef CALL 282 283 /* Check header */ 284 if(!width || !height || !max_val || max_val > 65535) { 285 res = RES_BAD_ARG; 286 goto error; 287 } 288 289 /* Allocate the image buffer */ 290 fmt = max_val <= 255 ? IMAGE_RGB8 : IMAGE_RGB16; 291 pitch = width * sizeof_image_format(fmt); 292 res = image_setup(img, width, height, pitch, fmt, NULL); 293 if(res != RES_OK) goto error; 294 295 /* Read pixel data */ 296 switch(id) { 297 case P3: 298 res = parse_raw_pixels(&parser, width, height, fmt, img->pixels); 299 break; 300 case P6: 301 res = parse_bin_pixels(&parser, width, height, fmt, img->pixels); 302 break; 303 default: FATAL("Unreachable code.\n"); break; 304 } 305 306 exit: 307 return res; 308 error: 309 goto exit; 310 } 311 312 res_T 313 image_write_ppm 314 (const struct image* img, 315 const int binary, 316 const char* filename) 317 { 318 FILE* stream = NULL; 319 res_T res = RES_OK; 320 321 if(!img || !filename) { 322 res = RES_BAD_ARG; 323 goto error; 324 } 325 326 stream = fopen(filename, "w"); 327 if(!stream) { 328 res = RES_IO_ERR; 329 goto error; 330 } 331 332 res = image_write_ppm_stream(img, binary, stream); 333 if(res != RES_OK) goto error; 334 335 exit: 336 if(stream) fclose(stream); 337 return res; 338 error: 339 goto exit; 340 } 341 342 res_T 343 image_write_ppm_stream 344 (const struct image* img, 345 const int bin, 346 FILE* stream) 347 { 348 res_T res = RES_OK; 349 350 if(!img || !stream) { 351 res = RES_BAD_ARG; 352 goto error; 353 } 354 355 fprintf(stream, "%s %lu %lu\n", bin ? "P6" : "P3", 356 (unsigned long)img->width, 357 (unsigned long)img->height); 358 switch(img->format) { /* Write Max val */ 359 case IMAGE_RGB8: fprintf(stream, "255\n"); break; 360 case IMAGE_RGB16: fprintf(stream, "65535\n"); break; 361 default: FATAL("Unreachable code.\n"); break; 362 } 363 364 if(bin) { 365 res = write_bin_ppm(img, stream); 366 } else { 367 res = write_raw_ppm(img, stream); 368 } 369 if(res != RES_OK) goto error; 370 371 372 exit: 373 return res; 374 error: 375 goto exit; 376 }