1 /********************************************************************************
2 * *
3 * X P M I n p u t / O u t p u t *
4 * *
5 *********************************************************************************
6 * Copyright (C) 2000,2020 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