rsys

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

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 }