Coverage Report

Created: 2026-06-09 06:12

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/freeimage-svn/FreeImage/trunk/Source/FreeImage/PluginXPM.cpp
Line
Count
Source
1
// ==========================================================
2
// XPM Loader and Writer
3
//
4
// Design and implementation by
5
// - Ryan Rubley (ryan@lostreality.org)
6
//
7
// COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, WITHOUT WARRANTY
8
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES
9
// THAT THE COVERED CODE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE
10
// OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED
11
// CODE IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT
12
// THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY
13
// SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL
14
// PART OF THIS LICENSE. NO USE OF ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER
15
// THIS DISCLAIMER.
16
//
17
// Use at your own risk!
18
// ==========================================================
19
20
#ifdef _MSC_VER 
21
#pragma warning (disable : 4786) // identifier was truncated to 'number' characters
22
#endif
23
24
// IMPLEMENTATION NOTES:
25
// ------------------------
26
// Initial design and implementation by
27
// - Karl-Heinz Bussian (khbussian@moss.de)
28
// - Hervé Drolon (drolon@infonie.fr)
29
// Completely rewritten from scratch by Ryan Rubley (ryan@lostreality.org)
30
// in order to address the following major fixes:
31
// * Supports any number of chars per pixel (not just 1 or 2)
32
// * Files with 2 chars per pixel but <= 256colors are loaded as 256 color (not 24bit)
33
// * Loads much faster, uses much less memory
34
// * supports #rgb #rrrgggbbb and #rrrrggggbbbb colors (not just #rrggbb)
35
// * supports symbolic color names
36
// ==========================================================
37
38
#include "FreeImage.h"
39
#include "Utilities.h"
40
41
// ==========================================================
42
// Plugin Interface
43
// ==========================================================
44
static int s_format_id;
45
46
// ==========================================================
47
// Internal Functions
48
// ==========================================================
49
50
// read in and skip all junk until we find a certain char
51
static BOOL
52
0
FindChar(FreeImageIO *io, fi_handle handle, BYTE look_for) {
53
0
  BYTE c;
54
0
  io->read_proc(&c, sizeof(BYTE), 1, handle);
55
0
  while(c != look_for) {
56
0
    if( io->read_proc(&c, sizeof(BYTE), 1, handle) != 1 )
57
0
      return FALSE;
58
0
  }
59
0
  return TRUE;
60
0
}
61
62
// find start of string, read data until ending quote found, allocate memory and return a string
63
static char *
64
0
ReadString(FreeImageIO *io, fi_handle handle) {
65
0
  if( !FindChar(io, handle,'"') )
66
0
    return NULL;
67
0
  BYTE c;
68
0
  std::string s;
69
0
  io->read_proc(&c, sizeof(BYTE), 1, handle);
70
0
  while(c != '"') {
71
0
    s += c;
72
0
    if( io->read_proc(&c, sizeof(BYTE), 1, handle) != 1 )
73
0
      return NULL;
74
0
  }
75
0
  char *cstr = (char *)malloc(s.length()+1);
76
0
  strcpy(cstr,s.c_str());
77
0
  return cstr;
78
0
}
79
80
static char *
81
0
Base92(unsigned int num) {
82
0
  static char b92[16]; //enough for more then 64 bits
83
0
  static char digit[] = " .XoO+@#$%&*=-;:>,<1234567890qwertyuipasdfghjklzxcvbnmMNBVCZASDFGHJKLPIUYTREWQ!~^/()_`'][{}|";
84
0
  b92[15] = '\0';
85
0
  int i = 14;
86
0
  do {
87
0
    b92[i--] = digit[num % 92];
88
0
    num /= 92;
89
0
  } while( num && i >= 0 );
90
0
  return b92+i+1;
91
0
}
92
93
// ==========================================================
94
// Plugin Implementation
95
// ==========================================================
96
97
static const char * DLL_CALLCONV
98
2
Format() {
99
2
  return "XPM";
100
2
}
101
102
static const char * DLL_CALLCONV
103
0
Description() {
104
0
  return "X11 Pixmap Format";
105
0
}
106
107
static const char * DLL_CALLCONV
108
0
Extension() {
109
0
  return "xpm";
110
0
}
111
112
static const char * DLL_CALLCONV
113
0
RegExpr() {
114
0
  return "^[ \\t]*/\\* XPM \\*/[ \\t]$";
115
0
}
116
117
static const char * DLL_CALLCONV
118
0
MimeType() {
119
0
  return "image/x-xpixmap";
120
0
}
121
122
static BOOL DLL_CALLCONV
123
19.3k
Validate(FreeImageIO *io, fi_handle handle) {
124
19.3k
  char buffer[256];
125
126
  // checks the first 256 characters for the magic string
127
19.3k
  int count = io->read_proc(buffer, 1, 256, handle);
128
19.3k
  if(count <= 9) return FALSE;
129
4.78M
  for(int i = 0; i < (count - 9); i++) {
130
4.76M
    if(strncmp(&buffer[i], "/* XPM */", 9) == 0)
131
0
      return TRUE;
132
4.76M
  }
133
19.3k
  return FALSE;
134
19.3k
}
135
136
static BOOL DLL_CALLCONV
137
0
SupportsExportDepth(int depth) {
138
0
  return (
139
0
      (depth == 8) ||
140
0
      (depth == 24)
141
0
    );
142
0
}
143
144
static BOOL DLL_CALLCONV
145
0
SupportsExportType(FREE_IMAGE_TYPE type) {
146
0
  return (type == FIT_BITMAP) ? TRUE : FALSE;
147
0
}
148
149
static BOOL DLL_CALLCONV
150
0
SupportsNoPixels() {
151
0
  return TRUE;
152
0
}
153
154
// ----------------------------------------------------------
155
156
static FIBITMAP * DLL_CALLCONV
157
0
Load(FreeImageIO *io, fi_handle handle, int page, int flags, void *data) {
158
0
  char msg[256];
159
0
    FIBITMAP *dib = NULL;
160
161
0
    if (!handle) return NULL;
162
163
0
    try {
164
0
    char *str;
165
    
166
0
    BOOL header_only = (flags & FIF_LOAD_NOPIXELS) == FIF_LOAD_NOPIXELS;
167
    
168
    //find the starting brace
169
0
    if( !FindChar(io, handle,'{') )
170
0
      throw "Could not find starting brace";
171
172
    //read info string
173
0
    str = ReadString(io, handle);
174
0
    if(!str)
175
0
      throw "Error reading info string";
176
177
0
    int width, height, colors, cpp;
178
0
    if( sscanf(str, "%d %d %d %d", &width, &height, &colors, &cpp) != 4 ) {
179
0
      free(str);
180
0
      throw "Improperly formed info string";
181
0
    }
182
0
    free(str);
183
184
    // check info string
185
0
    if((width <= 0) || (height <= 0) || (colors <= 0) || (cpp <= 0)) {
186
0
      throw "Improperly formed info string";
187
0
    }
188
189
0
        if (colors > 256) {
190
0
      dib = FreeImage_AllocateHeader(header_only, width, height, 24, FI_RGBA_RED_MASK, FI_RGBA_GREEN_MASK, FI_RGBA_BLUE_MASK);
191
0
    } else {
192
0
      dib = FreeImage_AllocateHeader(header_only, width, height, 8);
193
0
    }
194
195
    //build a map of color chars to rgb values
196
0
    std::map<std::string,FILE_RGBA> rawpal; //will store index in Alpha if 8bpp
197
0
    for(int i = 0; i < colors; i++ ) {
198
0
      FILE_RGBA rgba;
199
200
0
      str = ReadString(io, handle);
201
0
      if(!str || (strlen(str) < (size_t)cpp))
202
0
        throw "Error reading color strings";
203
204
0
      std::string chrs(str,cpp); //create a string for the color chars using the first cpp chars
205
0
      char *keys = str + cpp; //the color keys for these chars start after the first cpp chars
206
207
      //translate all the tabs to spaces
208
0
      char *tmp = keys;
209
0
      while( strchr(tmp,'\t') ) {
210
0
        tmp = strchr(tmp,'\t');
211
0
        *tmp++ = ' ';
212
0
      }
213
214
      //prefer the color visual
215
0
      if( strstr(keys," c ") ) {
216
0
        char *clr = strstr(keys," c ") + 3;
217
0
        while( *clr == ' ' ) clr++; //find the start of the hex rgb value
218
0
        if( *clr == '#' ) {
219
0
          int red = 0, green = 0, blue = 0, n;
220
0
          clr++;
221
          //end string at first space, if any found
222
0
          if( strchr(clr,' ') )
223
0
            *(strchr(clr,' ')) = '\0';
224
          //parse hex color, it can be #rgb #rrggbb #rrrgggbbb or #rrrrggggbbbb
225
0
          switch( strlen(clr) ) {
226
0
            case 3: n = sscanf(clr,"%01x%01x%01x",&red,&green,&blue);
227
0
              red |= (red << 4);
228
0
              green |= (green << 4);
229
0
              blue |= (blue << 4);
230
0
              break;
231
0
            case 6: n = sscanf(clr,"%02x%02x%02x",&red,&green,&blue);
232
0
              break;
233
0
            case 9: n = sscanf(clr,"%03x%03x%03x",&red,&green,&blue);
234
0
              red >>= 4;
235
0
              green >>= 4;
236
0
              blue >>= 4;
237
0
              break;
238
0
            case 12: n = sscanf(clr,"%04x%04x%04x",&red,&green,&blue);
239
0
              red >>= 8;
240
0
              green >>= 8;
241
0
              blue >>= 8;
242
0
              break;
243
0
            default:
244
0
              n = 0;
245
0
              break;
246
0
          }
247
0
          if( n != 3 ) {
248
0
            free(str);
249
0
            throw "Improperly formed hex color value";
250
0
          }
251
0
          rgba.r = (BYTE)red;
252
0
          rgba.g = (BYTE)green;
253
0
          rgba.b = (BYTE)blue;
254
0
        } else if( !strncmp(clr,"None",4) || !strncmp(clr,"none",4) ) {
255
0
          rgba.r = rgba.g = rgba.b = 0xFF;
256
0
        } else {
257
0
          char *tmp = clr;
258
259
          //scan forward for each space, if its " x " or " xx " end the string there
260
          //this means its probably some other visual data beyond that point and not
261
          //part of the color name.  How many named color end with a 1 or 2 character
262
          //word? Probably none in our list at least.
263
0
          while( (tmp = strchr(tmp,' ')) != NULL ) {
264
0
            if( tmp[1] != ' ' ) {
265
0
              if( (tmp[2] == ' ') || (tmp[2] != ' ' && tmp[3] == ' ') ) {
266
0
                tmp[0] = '\0';
267
0
                break;
268
0
              }
269
0
            }
270
0
            tmp++;
271
0
          }
272
273
          //remove any trailing spaces
274
0
          tmp = clr+strlen(clr)-1;
275
0
          while( *tmp == ' ' ) {
276
0
            *tmp = '\0';
277
0
            tmp--;
278
0
          }
279
280
0
          if (!FreeImage_LookupX11Color(clr,  &rgba.r, &rgba.g, &rgba.b)) {
281
0
            sprintf(msg, "Unknown color name '%s'", str);
282
0
            free(str);
283
0
            throw msg;
284
0
          }
285
0
        }
286
0
      } else {
287
0
        free(str);
288
0
        throw "Only color visuals are supported";
289
0
      }
290
291
      //add color to map
292
0
      rgba.a = (BYTE)((colors > 256) ? 0 : i);
293
0
      rawpal[chrs] = rgba;
294
295
      //build palette if needed
296
0
      if( colors <= 256 ) {
297
0
        RGBQUAD *pal = FreeImage_GetPalette(dib);
298
0
        pal[i].rgbBlue = rgba.b;
299
0
        pal[i].rgbGreen = rgba.g;
300
0
        pal[i].rgbRed = rgba.r;
301
0
      }
302
303
0
      free(str);
304
0
    }
305
    //done parsing color map
306
307
0
    if(header_only) {
308
      // header only mode
309
0
      return dib;
310
0
    }
311
312
    //read in pixel data
313
0
    for(int y = 0; y < height; y++ ) {
314
0
      BYTE *line = FreeImage_GetScanLine(dib, height - y - 1);
315
0
      str = ReadString(io, handle);
316
0
      if(!str)
317
0
        throw "Error reading pixel strings";
318
0
      char *pixel_ptr = str;
319
320
0
      for(int x = 0; x < width; x++ ) {
321
        //locate the chars in the color map
322
0
        std::string chrs(pixel_ptr,cpp);
323
0
        FILE_RGBA rgba = rawpal[chrs];
324
325
0
        if( colors > 256 ) {
326
0
          line[FI_RGBA_BLUE] = rgba.b;
327
0
          line[FI_RGBA_GREEN] = rgba.g;
328
0
          line[FI_RGBA_RED] = rgba.r;
329
0
          line += 3;
330
0
        } else {
331
0
          *line = rgba.a;
332
0
          line++;
333
0
        }
334
335
0
        pixel_ptr += cpp;
336
0
      }
337
338
0
      free(str);
339
0
    }
340
    //done reading pixel data
341
342
0
    return dib;
343
0
  } catch(const char *text) {
344
0
       FreeImage_OutputMessageProc(s_format_id, text);
345
346
0
       if( dib != NULL )
347
0
           FreeImage_Unload(dib);
348
349
0
       return NULL;
350
0
    }
351
0
}
352
353
static BOOL DLL_CALLCONV
354
0
Save(FreeImageIO *io, FIBITMAP *dib, fi_handle handle, int page, int flags, void *data) {
355
0
  if ((dib != NULL) && (handle != NULL)) {
356
0
    char header[] = "/* XPM */\nstatic char *freeimage[] = {\n/* width height num_colors chars_per_pixel */\n\"",
357
0
    start_colors[] = "\",\n/* colors */\n\"",
358
0
    start_pixels[] = "\",\n/* pixels */\n\"",
359
0
    new_line[] = "\",\n\"",
360
0
    footer[] = "\"\n};\n",
361
0
    buf[256]; //256 is more then enough to sprintf 4 ints into, or the base-92 chars and #rrggbb line
362
363
0
    if( io->write_proc(header, (unsigned int)strlen(header), 1, handle) != 1 )
364
0
      return FALSE;
365
366
0
    int width = FreeImage_GetWidth(dib), height = FreeImage_GetHeight(dib), bpp = FreeImage_GetBPP(dib);
367
0
    RGBQUAD *pal = FreeImage_GetPalette(dib);
368
0
    int x,y;
369
370
    //map base92 chrs to the rgb value to create the palette
371
0
    std::map<DWORD,FILE_RGB> chrs2color;
372
    //map 8bpp index or 24bpp rgb value to the base92 chrs to create pixel data
373
0
    typedef union {
374
0
      DWORD index;
375
0
      FILE_RGBA rgba;
376
0
    } DWORDRGBA;
377
0
    std::map<DWORD,std::string> color2chrs;
378
379
    //loop thru entire dib, if new color, inc num_colors and add to both maps
380
0
    int num_colors = 0;
381
0
    for(y = 0; y < height; y++ ) {
382
0
      BYTE *line = FreeImage_GetScanLine(dib, height - y - 1);
383
0
      for(x = 0; x < width; x++ ) {
384
0
        FILE_RGB rgb;
385
0
        DWORDRGBA u;
386
0
        if( bpp > 8 ) {
387
0
          u.rgba.b = rgb.b = line[FI_RGBA_BLUE];
388
0
          u.rgba.g = rgb.g = line[FI_RGBA_GREEN];
389
0
          u.rgba.r = rgb.r = line[FI_RGBA_RED];
390
0
          u.rgba.a = 0;
391
0
          line += 3;
392
0
        } else {
393
0
          u.index = *line;
394
0
          rgb.b = pal[u.index].rgbBlue;
395
0
          rgb.g = pal[u.index].rgbGreen;
396
0
          rgb.r = pal[u.index].rgbRed;
397
0
          line++;
398
0
        }
399
0
        if( color2chrs.find(u.index) == color2chrs.end() ) { //new color
400
0
          std::string chrs(Base92(num_colors));
401
0
          color2chrs[u.index] = chrs;
402
0
          chrs2color[num_colors] = rgb;
403
0
          num_colors++;
404
0
        }
405
0
      }
406
0
    }
407
408
0
    int cpp = (int)(log((double)num_colors)/log(92.0)) + 1;
409
410
0
    sprintf(buf, "%d %d %d %d", FreeImage_GetWidth(dib), FreeImage_GetHeight(dib), num_colors, cpp );
411
0
    if( io->write_proc(buf, (unsigned int)strlen(buf), 1, handle) != 1 )
412
0
      return FALSE;
413
414
0
    if( io->write_proc(start_colors, (unsigned int)strlen(start_colors), 1, handle) != 1 )
415
0
      return FALSE;
416
417
    //write colors, using map of chrs->rgb
418
0
    for(x = 0; x < num_colors; x++ ) {
419
0
      sprintf(buf, "%*s c #%02x%02x%02x", cpp, Base92(x), chrs2color[x].r, chrs2color[x].g, chrs2color[x].b );
420
0
      if( io->write_proc(buf, (unsigned int)strlen(buf), 1, handle) != 1 )
421
0
        return FALSE;
422
0
      if( x == num_colors - 1 ) {
423
0
        if( io->write_proc(start_pixels, (unsigned int)strlen(start_pixels), 1, handle) != 1 )
424
0
          return FALSE;
425
0
      } else {
426
0
        if( io->write_proc(new_line, (unsigned int)strlen(new_line), 1, handle) != 1 )
427
0
          return FALSE;
428
0
      }
429
0
    }
430
431
432
    //write pixels, using map of rgb(if 24bpp) or index(if 8bpp)->chrs
433
0
    for(y = 0; y < height; y++ ) {
434
0
      BYTE *line = FreeImage_GetScanLine(dib, height - y - 1);
435
0
      for(x = 0; x < width; x++ ) {
436
0
        DWORDRGBA u;
437
0
        if( bpp > 8 ) {
438
0
          u.rgba.b = line[FI_RGBA_BLUE];
439
0
          u.rgba.g = line[FI_RGBA_GREEN];
440
0
          u.rgba.r = line[FI_RGBA_RED];
441
0
          u.rgba.a = 0;
442
0
          line += 3;
443
0
        } else {
444
0
          u.index = *line;
445
0
          line++;
446
0
        }
447
0
        sprintf(buf, "%*s", cpp, (char *)color2chrs[u.index].c_str());
448
0
        if( io->write_proc(buf, cpp, 1, handle) != 1 )
449
0
          return FALSE;
450
0
      }
451
0
      if( y == height - 1 ) {
452
0
        if( io->write_proc(footer, (unsigned int)strlen(footer), 1, handle) != 1 )
453
0
          return FALSE;
454
0
      } else {
455
0
        if( io->write_proc(new_line, (unsigned int)strlen(new_line), 1, handle) != 1 )
456
0
          return FALSE;
457
0
      }
458
0
    }
459
460
0
    return TRUE;
461
0
  } else {
462
0
    return FALSE;
463
0
  }
464
0
}
465
466
// ==========================================================
467
//   Init
468
// ==========================================================
469
470
void DLL_CALLCONV
471
InitXPM(Plugin *plugin, int format_id)
472
2
{
473
2
    s_format_id = format_id;
474
475
2
  plugin->format_proc = Format;
476
2
  plugin->description_proc = Description;
477
2
  plugin->extension_proc = Extension;
478
2
  plugin->regexpr_proc = RegExpr;
479
2
  plugin->open_proc = NULL;
480
2
  plugin->close_proc = NULL;
481
2
  plugin->pagecount_proc = NULL;
482
2
  plugin->pagecapability_proc = NULL;
483
2
  plugin->load_proc = Load;
484
2
  plugin->save_proc = Save;
485
2
  plugin->validate_proc = Validate;
486
2
  plugin->mime_proc = MimeType;
487
2
  plugin->supports_export_bpp_proc = SupportsExportDepth;
488
2
  plugin->supports_export_type_proc = SupportsExportType;
489
  plugin->supports_icc_profiles_proc = NULL;
490
2
  plugin->supports_no_pixels_proc = SupportsNoPixels;
491
2
}
492