1 /*
2  * in_xpm.cpp -- read an XPM file
3  * by pts@math.bme.hu at Fri Mar  1 09:56:54 CET 2002
4  */
5 /* Imp: test this code with various xpm files! */
6 
7 #ifdef __GNUC__
8 #ifndef __clang__
9 #pragma implementation
10 #endif
11 #endif
12 
13 #include "image.hpp"
14 
15 #if USE_IN_XPM
16 
17 #if OBJDEP
18 #  warning REQUIRES: mapping.o
19 #endif
20 
21 #ifndef USE_BIG_MEMORY
22 #define USE_BIG_MEMORY 0
23 #endif
24 
25 #include "mapping.hpp"
26 #include "error.hpp"
27 #include "xpmc.h"
28 
29 #include <string.h> /* memchr() */
30 #include "gensio.hpp"
31 
32 #define USGE(a,b) ((unsigned char)(a))>=((unsigned char)(b))
33 
34 #if 0
35 /** @return true iff params are different strings (not respecting case) */
36 static int my_strcase_neq(char *s1, char *s2) {
37   while ((USGE(*s1,'A') && USGE('Z',*s1) ? *s1+'a'-'A' : *s1) ==
38          (USGE(*s2,'A') && USGE('Z',*s2) ? *s2+'a'-'A' : *s2)) {
39     if (*s1=='\0') return 0;
40     s1++; s2++;
41   }
42   return 1;
43 }
44 #endif
45 
46 #define my_strcase_neq(s1,s2) GenBuffer::nocase_strcmp(s1,s2)
47 
48 /** @ return RGB long */
parse_rgb(char const * s)49 static Image::Sampled::rgb_t parse_rgb(char const*s) {
50   unsigned v=0, len;
51   Image::Sampled::rgb_t ret;
52   if (!s || !*s) return 0x2000000; /* not found */
53   // fprintf(stderr, "'%s'\n", s);
54   if (*s=='#') { /* an #RRGGBB web-style color spec; or #RRRRGGGGBBBB */
55     unsigned dif=0;
56     ++s;
57     while (dif<13 && (USGE(5,(s[dif]|32)-'a') || USGE(9,s[dif]-'0'))) dif++; /* find at most 13 hex digits */
58     if (s[dif]!='\0' || (dif!=12 && dif!=6)) return 0x2000000; /* not found, spec length error */
59     dif=(dif==12) ? 3 : 1;
60     // shr=24; while (shr!=0) {
61     ret= (Image::Sampled::rgb_t)( USGE(9,*s-'0') ? *s-'0' : 10+(*s|32)-'a' )<<20;
62     s++;
63     ret|=(Image::Sampled::rgb_t)( USGE(9,*s-'0') ? *s-'0' : 10+(*s|32)-'a' )<<16;
64     s+=dif; /* ignore lower two hex digits of 16-bit sample value */
65     ret|=(Image::Sampled::rgb_t)( USGE(9,*s-'0') ? *s-'0' : 10+(*s|32)-'a' )<<12;
66     s++;
67     ret|=(Image::Sampled::rgb_t)( USGE(9,*s-'0') ? *s-'0' : 10+(*s|32)-'a' )<<8;
68     s+=dif; /* ignore lower two hex digits of 16-bit sample value */
69     ret|=(Image::Sampled::rgb_t)( USGE(9,*s-'0') ? *s-'0' : 10+(*s|32)-'a' )<<4;
70     s++;
71     ret|=(Image::Sampled::rgb_t)( USGE(9,*s-'0') ? *s-'0' : 10+(*s|32)-'a' );
72     return ret;
73   }
74   /* vvv 223==255-32: ignore case when hashing */
75   char const *p=s;
76   while (*p!='\0') v=xpmColors_mul*v+(223&*(unsigned char const*)p++);
77   p=xpmColors_dat+xpmColors_ofs[(v&65535)%xpmColors_mod];
78   while (*p!='\0') {
79     len=strlen(p);
80     if (0==my_strcase_neq(p,s)) {
81       p+=len;
82       ret=(((Image::Sampled::rgb_t)((unsigned char const*)p)[1])<<16)+
83           (((Image::Sampled::rgb_t)((unsigned char const*)p)[2])<<8)+
84           (((Image::Sampled::rgb_t)((unsigned char const*)p)[3]));
85       return (ret==0x30201) ? 0x1000000 : ret; /* transparent color */
86     }
87     p+=len+4;
88   }
89   return 0x2000000; /* not found */
90 }
91 
92 class XPMTok {
93  public:
94   /* Imp: report line numbers on errors */
95   BEGIN_STATIC_ENUM1(int)
96     T_NO_UNGOT=-1,
97     T_COMMA=257 /* The comma token, outside strings. */
98   END_STATIC_ENUM()
99   /* XPM states */
100   BEGIN_STATIC_ENUM1(char)
101     ST_OUT=0, /* outside s string */
102     ST_STR=1, /* inside a string */
103     ST_EOF=2 /* EOF */
104   END_STATIC_ENUM()
105   int getcc();
106   /** Reads an unsigned int. */
107   Image::Sampled::dimen_t getDimen();
108   Image::Sampled::rgb_t getColor();
109   void read(char *buf, unsigned len);
110   inline void readInStr(char *buf, unsigned len);
XPMTok(FILE * f_)111   inline XPMTok(FILE *f_): f(f_), state(ST_OUT), ungot(T_NO_UNGOT) {}
ungetcc(int c)112   inline void ungetcc(int c) { if (c>=0) ungot=c; }
113   void getComma();
114  protected:
115   FILE *f;
116   /** Current state. */
117   char state;
118   int ungot;
119 };
120 
getcc()121 int XPMTok::getcc() {
122   int i;
123   if (ungot>=0 /*T_NO_UNGOT<0*/) { i=ungot; ungot=T_NO_UNGOT; return i; }
124   /* Imp: ignore C and C++ style comments (so we won't recognise strings inside comments) */
125   switch (state) {
126    case ST_OUT: st_out:
127     while (1) {
128       switch ((i=MACRO_GETC(f))) {
129        case -1: state=ST_EOF; return -1;
130        case ',': return T_COMMA;
131        case '"': state=ST_STR; goto st_str;
132        default: /* ignore outside strings */ break;
133       }
134     }
135    case ST_STR: st_str:
136     i=MACRO_GETC(f);
137     if (i==-1) { ue: Error::sev(Error::EERROR) << "XPM: unexpected EOF" << (Error*)0; }
138     else if (i=='"') { state=ST_OUT; goto st_out; }
139     else if (i=='\\') {
140       if ((i=MACRO_GETC(f))==-1) goto ue;
141       if (i=='\n') goto st_str;
142       /* Imp: handle octal, hexadecimal, \n etc. */
143     }
144     return i&255;
145    default: return -1;
146   }
147 }
getDimen()148 Image::Sampled::dimen_t XPMTok::getDimen() {
149   Image::Sampled::dimen_t ret=0, bak;
150   int i;
151   while ((i=getcc())==' ' || i=='\t') ;
152   if (USGE(i,'0') && USGE('9',i)) {
153     ret=i-'0';
154     while (USGE((i=getcc()),'0') && USGE('9',i)) {
155       bak=ret;
156       ret=ret*10+(i-'0');
157       if (ret/10!=bak) Error::sev(Error::EERROR) << "XPM: dimen overflow" << (Error*)0;
158     }
159     ungetcc(i);
160     return ret;
161   } else Error::sev(Error::EERROR) << "XPM: dimen expected" << (Error*)0;
162   return 0; /*notreached*/
163 }
getComma()164 void XPMTok::getComma() {
165   if (getcc()!=T_COMMA) Error::sev(Error::EERROR) << "XPM: comma expected at " << ftell(f) << (Error*)0;
166 }
read(char * buf,unsigned len)167 void XPMTok::read(char *buf, unsigned len) {
168   int i;
169   while (len--!=0) {
170     i=getcc();
171     // fprintf(stderr,"i=%d\n", i);
172     if (i>=0 && i<=255) *buf++=i; else Error::sev(Error::EERROR) << "XPM: data expected" << (Error*)0;
173   }
174 }
readInStr(char * buf,unsigned len)175 void XPMTok::readInStr(char *buf, unsigned len) {
176   /* Dat: this is OK noew */
177   // assert(state==ST_STR);
178   assert(ungot<0);
179   param_assert(len>=1);
180   if (state!=ST_STR) { len--; goto real; }
181   int i;
182   while (len--!=0) {
183     if ((i=MACRO_GETC(f))>=0 && i<=255 && i!='"' && i!='\\') *buf++=i;
184     else { assert(i>=0); ungetc(i,f); real:
185       i=getcc();
186       // fprintf(stderr,"i=%d\n", i);
187       if (i>=0 && i<=255) *buf++=i; else Error::sev(Error::EERROR) << "XPM: data expected" << (Error*)0;
188     }
189   }
190 }
getColor()191 Image::Sampled::rgb_t XPMTok::getColor() {
192   static char tmp[32];
193   int i;
194   while ((i=getcc())==' ' || i=='\t') ;
195   if (i=='g') { at_g:
196     i=getcc();
197     if (i!='4') ungetcc(i);
198     goto at_col;
199   } else if (i=='c' || i=='m' || i=='b' || i=='s') { at_col:
200     while ((i=getcc())==' ' || i=='\t') ;
201     char *p=tmp, *pend=tmp+sizeof(tmp)-1;
202     while (i>=33 && i<=126) {
203       if (p==pend) goto cexp; /* color name too long */
204       *p++=i;
205       i=getcc();
206     }
207     *p='\0';
208     if (i==' ' || i=='\t') { /* Maybe another color will come */
209       while ((i=getcc())==' ' || i=='\t') ;
210       if (i=='g') goto at_g;
211       else if (i=='c' || i=='m' || i=='b' || i=='s') goto at_col;
212     }
213     if (i!=T_COMMA) goto cexp;
214     Image::Sampled::rgb_t ret=parse_rgb(tmp);
215     if (ret==0x2000000) Error::sev(Error::EERROR) << "XPM: unknown color: " << tmp << (Error*)0;
216     return ret;
217   } else { cexp: Error::sev(Error::EERROR) << "XPM: color expected" << (Error*)0; }
218   return 0; /*notreached*/
219 }
220 
in_xpm_reader(Image::Loader::UFD * ufd,SimBuffer::Flat const &)221 static Image::Sampled *in_xpm_reader(Image::Loader::UFD *ufd, SimBuffer::Flat const&) {
222   // Error::sev(Error::EERROR) << "Cannot load XPM images yet." << (Error*)0;
223   XPMTok tok(((Filter::UngetFILED*)ufd)->getFILE(/*seekable:*/false));
224   Image::Sampled::dimen_t wd=tok.getDimen();
225   Image::Sampled::dimen_t ht=tok.getDimen();
226   Image::Sampled::dimen_t colors=tok.getDimen();
227   Image::Sampled::dimen_t cpp=tok.getDimen(); /* chars per pixel */
228 
229   /* width height ncolors cpp [x_hot y_hot] */
230   int i; /* multiple purpose */
231   while ((i=tok.getcc())==' ' || i=='\t' || USGE(9,i-'0')) ;
232   tok.ungetcc(i); tok.getComma();
233 
234   // Error::sev(Error::DEBUG) << "wd="<<wd<<" ht="<<ht<<" colors="<<colors<<" cpp="<<cpp << (Error*)0;
235   if (1UL*cpp*colors>65535) Error::sev(Error::EERROR) << "XPM: too many colors" << (Error*)0;
236   // if (cpp==1) {
237   // }
238   Image::Sampled::dimen_t transp=colors; /* No transparent colors yet. */
239   /* vvv Dat: last cpp bytes of tab are used for storing current pixel. */
240   char *tab=new char[cpp*(colors+1)], *p, *pend; /* BUGFIX: unsinged at Fri Nov 26 12:18:21 CET 2004 */
241   Image::Sampled::rgb_t *rgb=new Image::Sampled::rgb_t[colors], *rp;
242   for (rp=rgb,p=tab,pend=tab+cpp*colors; p!=pend; p+=cpp,rp++) {
243     tok.read(p,cpp);
244     *rp=tok.getColor();
245     if (*rp==0x1000000) {
246       if (transp!=colors) Error::sev(Error::WARNING) << "XPM: subsequent transparency might be blacked" << (Error*)0;
247                      else transp=rp-rgb;
248     }
249   }
250   /* Now: transp: index of the last transparent color */
251   /* Dat: since (0x1000000&0xffffff)==0, transparent colors will have palette enrty black */
252 
253   char *outbuf=0; /* avoid gcc warning */
254   Image::Sampled *ret;
255   Image::Indexed *iimg=(Image::Indexed*)NULLP;
256   tok.ungetcc(tok.T_COMMA);
257 
258   if (colors<=256) {
259     ret=iimg=new Image::Indexed(wd,ht,colors,8);
260     if (transp!=colors) iimg->setTransp(transp);
261   } else {
262     ret=new Image::RGB(wd,ht,8);
263     if (transp!=colors) Error::sev(Error::WARNING) << "XPM: too many colors, transparency blacked" << (Error*)0;
264   }
265   outbuf=ret->getRowbeg();
266 
267   if (cpp==1) { /* Easy job: make an Indexed image; defer .packPal() */
268     assert(colors<=256);
269     signed short bin[256], s;
270     memset(bin, 255, sizeof(bin)); /* Make bin[*]=-1 */
271     for (i=0;(unsigned)i<colors;i++) {
272       iimg->setPal(i, rgb[i]);
273       bin[(unsigned char)tab[i]]=i;
274     }
275     assert(p==pend);
276     while (ht--!=0) {
277       tok.getComma();
278       for (p=outbuf+ret->getRlen(); outbuf!=p; ) {
279         if ((i=tok.getcc())<0 || i>255) Error::sev(Error::EERROR) << "XPM: data expected" << (Error*)0;
280         if ((s=bin[i])<0) Error::sev(Error::EERROR) << "XPM: unpaletted color" << (Error*)0;
281         *outbuf++=s;
282       }
283     }
284   #if USE_BIG_MEMORY
285    } else if (cpp==2 && colors<=256) { /* Similarly easy job: make an Indexed image; defer .packPal() */
286     signed short *bin=new short[65536], s;
287     memset(bin, 255, sizeof(*bin) * 65536); /* Make bin[*]=-1 */
288     for (i=0,p=tab; (unsigned)i<colors; i++, p+=2) {
289       iimg->setPal(i, rgb[i]);
290       bin[(((unsigned char*)p)[0]<<8)+((unsigned char*)p)[1]]=i;
291     }
292     assert(p==pend);
293     while (ht--!=0) {
294       tok.getComma();
295       for (p=outbuf+ret->getRlen(); outbuf!=p; ) {
296         tok.readInStr(pend,2);
297         if ((s=bin[(((unsigned char*)pend)[0]<<8)+((unsigned char*)pend)[1]])<0) Error::sev(Error::EERROR) << "XPM: unpaletted color" << (Error*)0;
298         *outbuf++=s;
299       }
300     }
301     delete [] bin;
302   } else if (cpp==2 && colors<=65535) {
303     Image::Sampled::rgb_t rgb1;
304     unsigned short *bin=new unsigned short[65536], s;
305     memset(bin, 255, sizeof(*bin) * 65536); /* Make bin[*]=max */
306     for (i=0,p=tab; (unsigned)i<colors; i++, p+=2) bin[(((unsigned char*)p)[0]<<8)+((unsigned char*)p)[1]]=i;
307     while (ht--!=0) {
308       tok.getComma();
309       for (p=outbuf+ret->getRlen(); outbuf!=p; ) {
310         tok.readInStr(pend,2);
311         if ((s=bin[(((unsigned char*)pend)[0]<<8)+((unsigned char*)pend)[1]])==(unsigned short)-1) Error::sev(Error::EERROR) << "XPM: unpaletted color" << (Error*)0;
312         *outbuf++=(rgb1=rgb[s])>>16;
313         *outbuf++=rgb1>>8;
314         *outbuf++=rgb1;
315       }
316     }
317     delete [] bin;
318    #endif /* USE_BIG_MEMORY */
319   } else { /* Now comes the slow, but general solution */
320    #if USE_IN_XPM_MAPPING /* use Mapping */
321     if (colors<=256) {
322       Mapping::H h(1);
323       char c;
324       /* vvv `c' might become negative, but it is harmless */
325       for (p=tab,pend=tab+cpp*colors,c=0; p!=pend; p+=cpp,c++)
326         h.set(p, cpp, &c); /* every color-string should be unique; but no error message if it isn't */
327       while (ht--!=0) {
328         tok.getComma();
329         for (p=outbuf+ret->getRlen(); outbuf!=p; ) {
330           tok.readInStr(pend,cpp);
331           if (NULLP==(pend=h.get(pend, cpp))) Error::sev(Error::EERROR) << "XPM: unpaletted color" << (Error*)0;
332           *outbuf++=*pend;
333         }
334       }
335     } else { /* most general case with Mapping */
336       if (transp!=colors) Error::sev(Error::WARNING) << "XPM: too many colors, transparency blacked" << (Error*)0;
337       /* Dat: reading a JPEG downsampled to a 256-color XPM takes 3000 ms
338        * with Mapping, and 31000 ms without mapping. Nice speed increase.
339        */
340       Mapping::H h(3);
341       char tmpcol[3];
342       for (p=tab,pend=tab+cpp*colors,rp=rgb; p!=pend; p+=cpp,rp++) {
343         tmpcol[0]=rp[0]>>16;
344         tmpcol[1]=rp[0]>>8;
345         tmpcol[2]=rp[0];
346         h.set(p, cpp, tmpcol); /* every color-string should be unique; but no error message if it isn't */
347       }
348       while (ht--!=0) {
349         tok.getComma();
350         for (p=outbuf+ret->getRlen(); outbuf!=p; outbuf+=3) {
351           tok.readInStr(pend,cpp);
352           if (NULLP==(pend=h.get(pend, cpp))) Error::sev(Error::EERROR) << "XPM: unpaletted color" << (Error*)0;
353           memcpy(outbuf, pend, 3);
354         }
355       }
356     }
357    #else /* don't USE_IN_XPM_MAPPING */
358     Image::Sampled::dimen_t lastcol=0, x;
359     p=tab; /* cache pointer for the last color (lastcol) */
360     if (colors<=256) {
361       assert(cpp>1);
362       for (lastcol=0;lastcol<colors;lastcol++) iimg->setPal(lastcol, rgb[lastcol]);
363       lastcol=0;
364       while (ht--!=0) {
365         // putchar('.');
366         tok.getComma();
367         for (x=0;x<wd;x++) {
368           tok.read(pend,cpp);
369           if (0!=memcmp(p,pend,cpp)) {
370             p=tab; lastcol=0; while (p!=pend && 0!=memcmp(p,pend,cpp)) { p+=cpp; lastcol++; }
371             if (p==pend) Error::sev(Error::EERROR) << "XPM: unpaletted color" << (Error*)0;
372             if (rgb[lastcol]==0x1000000) p=tab+cpp*(lastcol=transp); /* fix single transp */
373           }
374           *outbuf++=lastcol;
375         }
376       }
377     } else { /* colors>256 */
378       while (ht--!=0) {
379         tok.getComma();
380         for (x=0;x<wd;x++) {
381           tok.read(pend,cpp);
382           if (0!=memcmp(p,pend,cpp)) {
383             p=tab; lastcol=0; while (p!=pend && 0!=memcmp(p,pend,cpp)) { p+=cpp; lastcol++; }
384             if (p==pend) Error::sev(Error::EERROR) << "XPM: unpaletted color" << (Error*)0;
385           }
386           *outbuf++=rgb[lastcol]>>16;
387           *outbuf++=rgb[lastcol]>>8;
388           *outbuf++=rgb[lastcol];
389         }
390       }
391     }
392    #endif
393   }
394   delete [] tab;
395   delete [] rgb;
396   /* Dat: we don't check for EOF. Imp: emit a warning? */
397   // Error::sev(Error::DEBUG) << "rp[-1]=" << rp[-1] << (Error*)0;
398   // while (-1!=(i=tok.getcc())) { putchar(i); }
399   /* fclose((FILE*)file_); */
400   return ret;
401 }
402 
in_xpm_checker(char buf[Image::Loader::MAGIC_LEN],char[Image::Loader::MAGIC_LEN],SimBuffer::Flat const &,Image::Loader::UFD *)403 static Image::Loader::reader_t in_xpm_checker(char buf[Image::Loader::MAGIC_LEN], char [Image::Loader::MAGIC_LEN], SimBuffer::Flat const&, Image::Loader::UFD*) {
404   return (0==memcmp(buf, "/* XPM */", 9)) ? in_xpm_reader : 0;
405 }
406 
407 #else
408 #  define in_xpm_checker NULLP
409 #endif /* USE_IN_XPM */
410 
411 Image::Loader in_xpm_loader = { "XPM", in_xpm_checker, 0 };
412