1 /********************************************************************************
2 *                                                                               *
3 *                    I R I S   R G B   I n p u t / O u t p u t                  *
4 *                                                                               *
5 *********************************************************************************
6 * Copyright (C) 2002,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 "FXArray.h"
26 #include "FXHash.h"
27 #include "FXElement.h"
28 #include "FXStream.h"
29 
30 
31 
32 /*
33   Notes:
34   - Need to implement RLE compression some time.
35   - ARGB means Alpha(0), Red(1), Green(2), Blue(3) in memory.
36   - Uses a wee bit of memory during the load; but the advantage is that it
37     doesn't seek around on the file but loads all data sequentially.
38   - We now handle Luminance, Luminance/Alpha, RGB, and RGBA flavors of IRIS RGB.
39 */
40 
41 using namespace FX;
42 
43 /*******************************************************************************/
44 
45 namespace FX {
46 
47 
48 #ifndef FXLOADRGB
49 extern FXAPI FXbool fxcheckRGB(FXStream& store);
50 extern FXAPI FXbool fxloadRGB(FXStream& store,FXColor*& data,FXint& width,FXint& height);
51 extern FXAPI FXbool fxsaveRGB(FXStream& store,const FXColor *data,FXint width,FXint height);
52 #endif
53 
54 
55 // RLE decompress with some checks against overruns
expand(FXuchar dst[],FXint dlen,const FXuchar src[],FXint slen)56 static void expand(FXuchar dst[],FXint dlen,const FXuchar src[],FXint slen){
57   FXuchar pixel,count;
58   FXint d=0,s=0;
59   while(s<slen){
60     pixel=src[s++];
61     count=pixel&0x7F;
62     if(count==0) break;         // Normal termination with RLE 0-code
63     if(d+count>dlen) break;     // Not enough space in destination
64     if(pixel&0x80){             // Literal bytes
65       if(s+count>slen) break;   // Not enough bytes in source
66       while(count--){
67         dst[d++]=src[s++];
68         }
69       }
70     else{                       // Repeated bytes
71       if(s+1>slen) break;       // Not enough bytes in source
72       pixel=src[s++];
73       while(count--){
74         dst[d++]=pixel;
75         }
76       }
77     }
78   }
79 
80 
81 #if 0
82 static FXint compress(lbuf, rlebuf, z, cnt)
83      byte *lbuf, *rlebuf;
84      int   z, cnt;
85 
86 
87 
88 // RLE compress with some checks against overruns
89 static FXint compress(FXuchar dst[],FXint dlen,const FXuchar src[],FXint slen){
90   byte *iptr, *ibufend, *sptr, *optr;
91   short todo, cc;
92   long  count;
93 
94   lbuf    += z;
95   iptr    = lbuf;
96   ibufend = iptr+cnt*4;
97   optr    = rlebuf;
98 
99   while (iptr<ibufend) {
100     sptr = iptr;
101     iptr += 8;
102     while((iptr<ibufend) && ((iptr[-8]!=iptr[-4]) || (iptr[-4]!=iptr[0])))
103       iptr += 4;
104     iptr -= 8;
105 
106     count = (iptr-sptr)/4;
107 
108     while (count) {
109       todo = count>126 ? 126 : count;
110       count -= todo;
111       *optr++ = 0x80|todo;
112 
113       while (todo--) {
114 	*optr++ = *sptr;
115 	sptr += 4;
116         }
117       }
118 
119     sptr = iptr;
120     cc = *iptr;
121     iptr += 4;
122     while ((iptr<ibufend) && (*iptr == cc))  iptr += 4;
123 
124     count = (iptr-sptr)/4;
125     while (count) {
126       todo = count>126 ? 126:count;
127       count -= todo;
128       *optr++ = todo;
129       *optr++ = cc;
130       }
131     }
132 
133   *optr++ = 0;
134   return (optr - rlebuf);
135   }
136 #endif
137 
138 
139 // Convert planar grey scale to RGBA
bwtorgba(FXColor * l,const FXuchar * b,FXint n)140 static void bwtorgba(FXColor *l,const FXuchar *b,FXint n){
141   while(n--){
142     l[0]=FXRGB(b[0],b[0],b[0]);
143     l++;
144     b++;
145     }
146   }
147 
148 
149 // Convert planar luminance-alpha to interleaved RGBA
latorgba(FXColor * l,const FXuchar * b,const FXuchar * a,FXint n)150 static void latorgba(FXColor *l,const FXuchar *b,const FXuchar *a,FXint n){
151   while(n--){
152     l[0]=FXRGBA(b[0],b[0],b[0],a[0]);
153     l++;
154     b++;
155     a++;
156     }
157   }
158 
159 
160 // Convert planar rgb to interleaved RGBA
rgbtorgba(FXColor * l,const FXuchar * r,const FXuchar * g,const FXuchar * b,FXint n)161 static void rgbtorgba(FXColor *l,const FXuchar *r,const FXuchar *g,const FXuchar *b,FXint n){
162   while(n--){
163     l[0]=FXRGB(r[0],g[0],b[0]);
164     l++;
165     r++;
166     g++;
167     b++;
168     }
169   }
170 
171 
172 // Convert planar rgba to interleaved RGBA
rgbatorgba(FXColor * l,const FXuchar * r,const FXuchar * g,const FXuchar * b,const FXuchar * a,FXint n)173 static void rgbatorgba(FXColor *l,const FXuchar *r,const FXuchar *g,const FXuchar *b,const FXuchar *a,FXint n){
174   while(n--){
175     l[0]=FXRGBA(r[0],g[0],b[0],a[0]);
176     l++;
177     r++;
178     g++;
179     b++;
180     a++;
181     }
182   }
183 
184 
185 // Check if stream contains a RGB
fxcheckRGB(FXStream & store)186 FXbool fxcheckRGB(FXStream& store){
187   FXuchar signature[2];
188   store.load(signature,2);
189   store.position(-2,FXFromCurrent);
190   return signature[0]==0x01 && signature[1]==0xDA;
191   }
192 
193 
194 // Load image from stream
fxloadRGB(FXStream & store,FXColor * & data,FXint & width,FXint & height)195 FXbool fxloadRGB(FXStream& store,FXColor*& data,FXint& width,FXint& height){
196   FXlong   base=store.position();
197   FXbool   swap=store.swapBytes();
198   FXbool   result=false;
199   FXushort magic;
200   FXuchar  storage;
201   FXuchar  bpc;
202   FXushort dimension;
203   FXushort w;
204   FXushort h;
205   FXushort channels;
206   FXuint   maxpix;
207   FXuint   minpix;
208   FXuint   dummy;
209   FXchar   name[80];
210   FXuint   colormap;
211 
212   // Null out
213   data=NULL;
214   width=0;
215   height=0;
216 
217   // Remember swap state
218   store.setBigEndian(true);
219 
220   // Load header
221   store >> magic;       // MAGIC (2)
222   store >> storage;     // STORAGE (1)
223   store >> bpc;         // BPC (1)
224   store >> dimension;   // DIMENSION (2)
225   store >> w;           // XSIZE (2)
226   store >> h;           // YSIZE (2)
227   store >> channels;    // ZSIZE (2)
228   store >> minpix;      // PIXMIN (4)
229   store >> maxpix;      // PIXMAX (4)
230   store >> dummy;       // DUMMY (4)
231   store.load(name,80);  // IMAGENAME (80)
232   store >> colormap;    // Colormap ID (4)
233 
234   FXTRACE((100,"fxloadRGB: magic=%d name=%s width=%d height=%d nchannels=%d dimension=%d storage=%d bpc=%d\n",magic,name,w,h,channels,dimension,storage,bpc));
235 
236   // Check magic number and other parameters
237   if(magic==474 && 1<=channels && channels<=4 && bpc==1 && 0<w && 0<h){
238     FXint tablen=h*channels;    // Number of chunk start/chunk length table entries
239     FXint size=w*h;             // Total number of pixels
240     FXint total=channels*size;  // Total number of samples
241     FXuchar *planar;
242 
243     // Skip to data
244     store.position(404,FXFromCurrent);
245 
246     // Allocate planar array
247     if(allocElms(planar,total)){
248 
249       // Allocate image data
250       if(allocElms(data,size)){
251         FXint i,j,k;
252 
253         // Set width and height
254         width=w;
255         height=h;
256 
257         // Compressed
258         if(storage){
259           FXuint *starttab;
260           FXuint *lengthtab;
261 
262           // Allocate line tables
263           if(allocElms(starttab,tablen<<1)){
264             lengthtab=&starttab[tablen];
265 
266             // Read line tables
267             store.load(starttab,tablen);
268             store.load(lengthtab,tablen);
269 
270             // Offset of RLE chunks in the file
271             FXuint sub=store.position()-base;
272             FXuint chunklen=0;
273             FXuchar *chunk;
274 
275             // Fix up the line table & figure space for RLE chunks
276             // Intelligent RGB writers (not ours ;-)) may re-use RLE
277             // chunks for more than 1 line...
278             for(i=0; i<tablen; i++){
279               starttab[i]-=sub;
280               chunklen=FXMAX(chunklen,(starttab[i]+lengthtab[i]));
281               }
282 
283             // Make room for the compressed lines
284             if(allocElms(chunk,chunklen)){
285 
286               // Load all RLE chunks in one fell swoop
287               store.load(chunk,chunklen);
288 
289               // Decompress chunks into planar
290               for(k=0; k<tablen; ++k){
291                 expand(&planar[k*width],width,&chunk[starttab[k]],lengthtab[k]);
292                 }
293 
294               // Free RLE chunks
295               freeElms(chunk);
296               result=true;
297               }
298 
299             // Free line tables
300             freeElms(starttab);
301             }
302           }
303 
304         // Uncompressed
305         else{
306           store.load(planar,total);
307           }
308 
309         // Combine the channels properly
310         switch(channels){
311           case 1:
312             for(i=0,j=(height-1)*width; 0<=j; i+=width,j-=width){
313               bwtorgba(&data[i],&planar[j],width);
314               }
315             break;
316           case 2:
317             for(i=0,j=(height-1)*width; 0<=j; i+=width,j-=width){
318               latorgba(&data[i],&planar[j],&planar[j+size],width);
319               }
320             break;
321           case 3:
322             for(i=0,j=(height-1)*width; 0<=j; i+=width,j-=width){
323               rgbtorgba(&data[i],&planar[j],&planar[j+size],&planar[j+size+size],width);
324               }
325             break;
326           case 4:
327             for(i=0,j=(height-1)*width; 0<=j; i+=width,j-=width){
328               rgbatorgba(&data[i],&planar[j],&planar[j+size],&planar[j+size+size],&planar[j+size+size+size],width);
329               }
330             break;
331           }
332 
333         // We're good
334         result=true;
335         }
336       freeElms(planar);
337       }
338     }
339 
340   // Reset swap status
341   store.swapBytes(swap);
342   return result;
343   }
344 
345 
346 /*******************************************************************************/
347 
348 
349 // Save a bmp file to a stream
fxsaveRGB(FXStream & store,const FXColor * data,FXint width,FXint height)350 FXbool fxsaveRGB(FXStream& store,const FXColor *data,FXint width,FXint height){
351   FXushort magic=474;
352   FXuchar  storage=0;
353   FXuchar  bpc=1;
354   FXushort dimension=3;
355   FXushort w=width;
356   FXushort h=height;
357   FXushort channels=3;
358   FXuint   maxpix=255;
359   FXuint   minpix=0;
360   FXuint   dummy=0;
361   FXuint   colormap=0;
362   FXint    size=width*height;
363   FXuchar  temp[512];
364   FXuchar *array;
365   FXint    i,j,k;
366   FXbool   swap;
367 
368   // Must make sense
369   if(data && 0<width && 0<height){
370 
371     // Reorganize in planes
372     if(allocElms(array,size*channels)){
373 
374       // Remember swap state
375       swap=store.swapBytes();
376       store.setBigEndian(true);
377 
378       // Save header
379       store << magic;             // MAGIC (2)
380       store << storage;           // STORAGE (1)
381       store << bpc;               // BPC (1)
382       store << dimension;         // DIMENSION (2)
383       store << w;                 // XSIZE (2)
384       store << h;                 // YSIZE (2)
385       store << channels;          // ZSIZE (2)
386       store << minpix;            // PIXMIN (4)
387       store << maxpix;            // PIXMAX (4)
388       store << dummy;             // DUMMY (4)
389       memset(temp,0,80);          // Clean it
390       memcpy(temp,"IRIS RGB",8);  // Write name
391       store.save(temp,80);        // IMAGENAME (80)
392       store << colormap;             // COLORMAP (4)
393       memset(temp,0,404);         // Clean it
394       store.save(temp,404);       // DUMMY (404)
395 
396       // Copy
397       for(j=height-1,k=0; j>=0; --j){
398         for(i=0; i<width; ++i,++k){
399           array[j*width+i]=((const FXuchar*)&data[k])[2];
400           array[j*width+i+size]=((const FXuchar*)&data[k])[1];
401           array[j*width+i+size+size]=((const FXuchar*)&data[k])[0];
402           }
403         }
404 
405       // Save it
406       store.save(array,size*channels);
407 
408       // Clean up temp memory
409       freeElms(array);
410 
411       // Reset swap status
412       store.swapBytes(swap);
413       return true;
414       }
415     }
416   return false;
417   }
418 
419 }
420