1 /********************************************************************************
2 *                                                                               *
3 *                          X P M   I n p u t / O u t p u t                      *
4 *                                                                               *
5 *********************************************************************************
6 * Copyright (C) 2000,2021 by Jeroen van der Zijp.   All Rights Reserved.        *
7 *********************************************************************************
8 * This library is free software; you can redistribute it and/or modify          *
9 * it under the terms of the GNU Lesser General Public License as published by   *
10 * the Free Software Foundation; either version 3 of the License, or             *
11 * (at your option) any later version.                                           *
12 *                                                                               *
13 * This library is distributed in the hope that it will be useful,               *
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of                *
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                 *
16 * GNU Lesser General Public License for more details.                           *
17 *                                                                               *
18 * You should have received a copy of the GNU Lesser General Public License      *
19 * along with this program.  If not, see <http://www.gnu.org/licenses/>          *
20 ********************************************************************************/
21 #include "xincs.h"
22 #include "fxver.h"
23 #include "fxdefs.h"
24 #include "fxmath.h"
25 #include "fxascii.h"
26 #include "FXArray.h"
27 #include "FXHash.h"
28 #include "FXElement.h"
29 #include "FXStream.h"
30 #include "FXColors.h"
31 
32 
33 /*
34   Notes:
35   - The transparent color hopefully does not occur in the image.
36   - If the image is rendered opaque, the transparent is close to white.
37   - References: http://www-sop.inria.fr/koala/lehors/xpm.html
38   - XPM reader/writer is tweaked so that an XPM written to FXStream
39     can be read back in and read exactly as many bytes as were written.
40   - There may be other comment blocks in the file
41 */
42 
43 #define MAXPRINTABLE    92
44 #define MAXVALUE        96
45 #define HASH1(x,n)      (((unsigned int)(x)*13)%(n))            // Number [0..n-1]
46 #define HASH2(x,n)      (1|(((unsigned int)(x)*17)%((n)-1)))    // Number [1..n-2]
47 
48 using namespace FX;
49 
50 
51 /*******************************************************************************/
52 
53 namespace FX {
54 
55 
56 extern FXbool fxfsquantize(FXuchar* dst,const FXColor* src,FXColor* colormap,FXint& actualcolors,FXint w,FXint h,FXint maxcolors);
57 extern FXbool fxezquantize(FXuchar* dst,const FXColor* src,FXColor* colormap,FXint& actualcolors,FXint w,FXint h,FXint maxcolors);
58 extern FXbool fxwuquantize(FXuchar* dst,const FXColor* src,FXColor* colormap,FXint& actualcolors,FXint w,FXint h,FXint maxcolors);
59 
60 
61 // Declarations
62 #ifndef FXLOADXPM
63 extern FXAPI FXbool fxcheckXPM(FXStream& store);
64 extern FXAPI FXbool fxloadXPM(const FXchar **pix,FXColor*& data,FXint& width,FXint& height);
65 extern FXAPI FXbool fxloadXPM(FXStream& store,FXColor*& data,FXint& width,FXint& height);
66 extern FXAPI FXbool fxsaveXPM(FXStream& store,const FXColor *data,FXint width,FXint height,FXbool fast=true);
67 #endif
68 
69 // Furnish our own version
70 extern FXAPI FXint __sscanf(const FXchar* string,const FXchar* format,...);
71 extern FXAPI FXint __snprintf(FXchar* string,FXint length,const FXchar* format,...);
72 
73 
74 // Read till end of line
readline(FXStream & store,FXchar * buffer,FXuint size)75 static void readline(FXStream& store,FXchar* buffer,FXuint size){
76   FXuint i=0;
77   while(!store.eof() && i<size){
78     store >> buffer[i];
79     if(buffer[i]=='\r') continue;
80     if(buffer[i]=='\n') break;
81     i++;
82     }
83   buffer[i]=0;
84   }
85 
86 
87 // Read quoted text
readtext(FXStream & store,FXchar * buffer,FXuint size)88 static void readtext(FXStream& store,FXchar* buffer,FXuint size){
89   FXuint i=0;
90   FXchar ch;
91   store >> ch;
92   while(!store.eof() && ch!='"') store >> ch;
93   while(!store.eof() && i<size){
94     store >> ch;
95     if(ch=='"') break;
96     buffer[i++]=ch;
97     }
98   buffer[i]=0;
99   }
100 
101 
102 // Parse next word
nextword(const FXchar * & src,FXchar * dst)103 static FXint nextword(const FXchar*& src,FXchar* dst){
104   FXchar *ptr=dst;
105   while(*src && Ascii::isSpace(*src)) src++;
106   while(*src && !Ascii::isSpace(*src)) *ptr++=*src++;
107   *ptr=0;
108   return (FXint)(ptr-dst);
109   }
110 
111 
112 // Is key
iskey(const FXchar * str)113 static FXbool iskey(const FXchar *str){
114   return ((str[0]=='c' || str[0]=='s' || str[0]=='m' || str[0]=='g') && str[1]==0) || (str[0]=='g' && str[1]=='4' && str[2]==0);
115   }
116 
117 
118 // Check if stream contains a XPM
fxcheckXPM(FXStream & store)119 FXbool fxcheckXPM(FXStream& store){
120   FXuchar signature[9];
121   store.load(signature,9);
122   store.position(-9,FXFromCurrent);
123   return signature[0]=='/' && signature[1]=='*' && signature[2]==' ' && signature[3]=='X' && signature[4]=='P' && signature[5]=='M' && signature[6]==' ' && signature[7]=='*' && signature[8]=='/';
124   }
125 
126 
127 // Load image from array of strings
fxloadXPM(const FXchar ** pixels,FXColor * & data,FXint & width,FXint & height)128 FXbool fxloadXPM(const FXchar **pixels,FXColor*& data,FXint& width,FXint& height){
129   FXchar  lookuptable[1024][8],name[100],word[100],flag,best;
130   FXColor colortable[16384],*pix,color;
131   const FXchar *src,*line;
132   FXint   i,j,ncolors,cpp,c;
133 
134   // Null out
135   data=NULL;
136   width=0;
137   height=0;
138   color=0;
139 
140   // NULL pointer passed in
141   if(!pixels) return false;
142 
143   // Read pointer
144   line=*pixels++;
145 
146   // No size description line
147   if(!line) return false;
148 
149   // Parse size description
150   __sscanf(line,"%d %d %u %u",&width,&height,&ncolors,&cpp);
151 
152   // Check size
153   if(width<1 || height<1 || width>16384 || height>16384) return false;
154 
155   // Sensible inputs
156   if(cpp<1 || cpp>8 || ncolors<1) return false;
157 
158   // Limited number of colors for long lookup strings
159   if(cpp>2 && ncolors>1024) return false;
160 
161   // Allow more colors for short lookup strings
162   if(ncolors>16384) return false;
163 
164   //FXTRACE((100,"fxloadXPM: width=%d height=%d ncolors=%d cpp=%d\n",width,height,ncolors,cpp));
165 
166   // Read the color table
167   for(c=0; c<ncolors; c++){
168     line=*pixels++;
169     src=line+cpp;
170     nextword(src,word);
171     best='z';
172     while(iskey(word)){
173       flag=word[0];
174       name[0]=0;
175       while(nextword(src,word) && !iskey(word)){
176         fxstrlcat(name,word,sizeof(name));
177         }
178       if(flag<best){                    // c < g < m < s
179         color=colorFromName(name);
180         best=flag;
181         }
182       }
183     if(cpp==1){
184       colortable[(FXuchar)line[0]]=color;
185       }
186     else if(cpp==2){
187       colortable[(((FXuchar)line[1])<<7)+(FXuchar)line[0]]=color;
188       }
189     else{
190       colortable[c]=color;
191       fxstrlcpy(lookuptable[c],line,cpp);
192       }
193     }
194 
195   // Try allocate pixels
196   if(!allocElms(data,width*height)){
197     return false;
198     }
199 
200   // Read the pixels
201   for(i=0,pix=data; i<height; i++){
202     line=*pixels++;
203     for(j=0; j<width; j++){
204       if(cpp==1){
205         color=colortable[(FXuchar)line[0]];
206         }
207       else if(cpp==2){
208         color=colortable[(((FXuchar)line[1])<<7)+(FXuchar)line[0]];
209         }
210       else{
211         for(c=0; c<ncolors; c++){
212           if(strncmp(lookuptable[c],line,cpp)==0){ color=colortable[c]; break; }
213           }
214         }
215       line+=cpp;
216       *pix++=color;
217       }
218     }
219   return true;
220   }
221 
222 
223 /*******************************************************************************/
224 
225 
226 // Load image from stream
fxloadXPM(FXStream & store,FXColor * & data,FXint & width,FXint & height)227 FXbool fxloadXPM(FXStream& store,FXColor*& data,FXint& width,FXint& height){
228   FXchar lookuptable[1024][8],line[100],name[100],word[100],flag,best,ch;
229   FXColor colortable[16384],*pix,color;
230   const FXchar *src;
231   FXint i,j,ncolors,cpp,c;
232 
233   // Null out
234   data=NULL;
235   width=0;
236   height=0;
237   color=0;
238 
239   // Read header line
240   readline(store,name,sizeof(name));
241   if(!strstr(name,"XPM")) return false;
242 
243   // Read description
244   readtext(store,line,sizeof(line));
245 
246   // Parse size description
247   if(__sscanf(line,"%d %d %u %u",&width,&height,&ncolors,&cpp)!=4) return false;
248 
249   // Check size
250   if(width<1 || height<1 || width>16384 || height>16384) return false;
251 
252   // Sensible inputs
253   if(cpp<1 || cpp>8 || ncolors<1) return false;
254 
255   // Limited number of colors for long lookup strings
256   if(cpp>2 && ncolors>1024) return false;
257 
258   // Allow more colors for short lookup strings
259   if(ncolors>16384) return false;
260 
261   //FXTRACE((100,"fxloadXPM: width=%d height=%d ncolors=%d cpp=%d\n",width,height,ncolors,cpp));
262 
263   // Read the color table
264   for(c=0; c<ncolors; c++){
265     readtext(store,line,sizeof(line));
266     src=line+cpp;
267     nextword(src,word);
268     best='z';
269     while(iskey(word)){
270       flag=word[0];
271       name[0]=0;
272       while(nextword(src,word) && !iskey(word)){
273         fxstrlcat(name,word,sizeof(name));
274         }
275       if(flag<best){                    // c < g < m < s
276         color=colorFromName(name);
277         best=flag;
278         }
279       }
280     if(cpp==1){
281       colortable[(FXuchar)line[0]]=color;
282       }
283     else if(cpp==2){
284       colortable[(((FXuchar)line[1])<<7)+(FXuchar)line[0]]=color;
285       }
286     else{
287       colortable[c]=color;
288       fxstrlcpy(lookuptable[c],line,cpp);
289       }
290     }
291 
292   // Try allocate pixels
293   if(!allocElms(data,width*height)){
294     return false;
295     }
296 
297   // Read the pixels
298   for(i=0,pix=data; i<height; i++){
299     while(!store.eof() && (store>>ch,ch!='"')){}
300     for(j=0; j<width; j++){
301       store.load(line,cpp);
302       if(cpp==1){
303         color=colortable[(FXuchar)line[0]];
304         }
305       else if(cpp==2){
306         color=colortable[(((FXuchar)line[1])<<7)+(FXuchar)line[0]];
307         }
308       else{
309         for(c=0; c<ncolors; c++){
310           if(strncmp(lookuptable[c],line,cpp)==0){ color=colortable[c]; break; }
311           }
312         }
313       *pix++=color;
314       }
315     while(!store.eof() && (store>>ch,ch!='"')){}
316     }
317 
318   // We got the image, but we're not done yet; need to read few more bytes
319   // the number of bytes read here must match the number of bytes written
320   // by fxsaveXPM() so that the stream won't get out of sync
321   while(!store.eof()){
322     store >> ch;
323     if(ch=='\n') break;
324     }
325   return true;
326   }
327 
328 
329 /*******************************************************************************/
330 
331 
332 // Save image to a stream
fxsaveXPM(FXStream & store,const FXColor * data,FXint width,FXint height,FXbool fast)333 FXbool fxsaveXPM(FXStream& store,const FXColor *data,FXint width,FXint height,FXbool fast){
334   const FXchar printable[]=" .XoO+@#$%&*=-;:>,<1234567890qwertyuipasdfghjklzxcvbnmMNBVCZASDFGHJKLPIUYTREWQ!~^/()_`'][{}|";
335   const FXchar quote='"';
336   const FXchar comma=',';
337   const FXchar newline='\n';
338   FXColor   colormap[256];
339   FXint     numpixels=width*height;
340   FXint     ncolors,cpp,len,i,j,c1,c2;
341   FXchar    buffer[200];
342   FXColor   color;
343   FXuchar  *pixels,*ptr,pix;
344 
345   // Must make sense
346   if(!data || width<=0 || height<=0) return false;
347 
348   // Allocate temp buffer for pixels
349   if(!allocElms(pixels,numpixels)) return false;
350 
351   // First, try EZ quantization, because it is exact; a previously
352   // loaded XPM will be re-saved with exactly the same colors.
353   if(!fxezquantize(pixels,data,colormap,ncolors,width,height,256)){
354     if(fast){
355       fxfsquantize(pixels,data,colormap,ncolors,width,height,256);
356       }
357     else{
358       fxwuquantize(pixels,data,colormap,ncolors,width,height,256);
359       }
360     }
361 
362   FXASSERT(ncolors<=256);
363 
364   // How many characters needed to represent one pixel, characters per line
365   cpp=(ncolors>MAXPRINTABLE)?2:1;
366 
367   // Save header
368   store.save("/* XPM */\nstatic char * image[] = {\n",36);
369 
370   // Save values
371   len=__snprintf(buffer,sizeof(buffer),"\"%d %d %d %d\",\n",width,height,ncolors,cpp);
372   store.save(buffer,len);
373 
374   // Save the colors
375   for(i=0; i<ncolors; i++){
376     color=colormap[i];
377     c1=printable[i%MAXPRINTABLE];
378     c2=printable[i/MAXPRINTABLE];
379     if(FXALPHAVAL(color)){
380       len=__snprintf(buffer,sizeof(buffer),"\"%c%c c #%02x%02x%02x\",\n",c1,c2,FXREDVAL(color),FXGREENVAL(color),FXBLUEVAL(color));
381       store.save(buffer,len);
382       }
383     else{
384       len=__snprintf(buffer,sizeof(buffer),"\"%c%c c None\",\n",c1,c2);
385       store.save(buffer,len);
386       }
387     }
388 
389   // Save the image
390   ptr=pixels;
391   for(i=0; i<height; i++){
392     store << quote;
393     for(j=0; j<width; j++){
394       pix=*ptr++;
395       if(cpp==1){
396         store << printable[pix];
397         }
398       else{
399         store << printable[pix%MAXPRINTABLE];
400         store << printable[pix/MAXPRINTABLE];
401         }
402       }
403     store << quote;
404     if(i<height-1){ store << comma; store << newline; }
405     }
406   store.save("};\n",3);
407   freeElms(pixels);
408   return true;
409   }
410 
411 }
412 
413