1 /*
2 * FIG : Facility for Interactive Generation of figures
3 * Copyright (c) 1985-1988 by Supoj Sutanthavibul
4 * Parts Copyright (c) 1989-2015 by Brian V. Smith
5 * Parts Copyright (c) 1991 by Paul King
6 * Parts Copyright (c) 2016-2020 by Thomas Loimer
7 *
8 * Any party obtaining a copy of these files is granted, free of charge, a
9 * full and unrestricted irrevocable, world-wide, paid up, royalty-free,
10 * nonexclusive right and license to deal in this software and documentation
11 * files (the "Software"), including without limitation the rights to use,
12 * copy, modify, merge, publish, distribute, sublicense and/or sell copies of
13 * the Software, and to permit persons who receive copies from any such
14 * party to do so, with the only requirement being that the above copyright
15 * and this permission notice remain intact.
16 *
17 */
18
19 /*
20 * Some of the following code is extracted from giftopnm.c,
21 * from the netpbm package
22 */
23
24 /* +-------------------------------------------------------------------+ */
25 /* | Copyright 1990, David Koblas. | */
26 /* | Permission to use, copy, modify, and distribute this software | */
27 /* | and its documentation for any purpose and without fee is hereby | */
28 /* | granted, provided that the above copyright notice appear in all | */
29 /* | copies and that both that copyright notice and this permission | */
30 /* | notice appear in supporting documentation. This software is | */
31 /* | provided "as is" without express or implied warranty. | */
32 /* +-------------------------------------------------------------------+ */
33
34 #ifdef HAVE_CONFIG_H
35 #include "config.h" /* restrict */
36 #endif
37
38 #include <X11/Intrinsic.h> /* Boolean */
39 #include <X11/Xlib.h> /* True, False */
40 #include <errno.h>
41 #include <stdio.h>
42 #include <stdlib.h> /* mkstemp */
43 #include <string.h> /* mkstemp */
44 #include <unistd.h> /* close, unlink */
45
46 #include "resources.h" /* PATH_MAX */
47 #include "object.h"
48 #include "f_picobj.h"
49 #include "f_readpcx.h"
50 #include "w_msgpanel.h"
51
52
53 static Boolean ReadColorMap(FILE *fd, unsigned int number, struct Cmap *cmap);
54 static Boolean DoGIFextension(FILE *fd, int label);
55 static int GetDataBlock(FILE *fd, unsigned char *buf);
56
57 #define LOCALCOLORMAP 0x80
58 #define ReadOK(file,buffer,len) \
59 (fread((void *)buffer, (size_t)len, (size_t)1, (FILE *)file) != 0)
60 #define BitSet(byte, bit) (((byte) & (bit)) == (bit))
61
62 #define LM_to_uint(a,b) (((b)<<8)|(a))
63
64 struct {
65 unsigned int Width;
66 unsigned int Height;
67 struct Cmap ColorMap[MAX_COLORMAP_SIZE];
68 unsigned int BitPixel;
69 unsigned int ColorResolution;
70 unsigned int Background;
71 unsigned int AspectRatio;
72 } GifScreen;
73
74 struct {
75 int transparent;
76 int delayTime;
77 int inputFlag;
78 int disposal;
79 } Gif89 = { -1, -1, -1, 0 };
80
81 /* return codes: PicSuccess (1) : success
82 FileInvalid (-2) : invalid file
83 */
84
85
86
87 int
read_gif(F_pic * pic,struct xfig_stream * restrict pic_stream)88 read_gif(F_pic *pic, struct xfig_stream *restrict pic_stream)
89 {
90 char buf[BUFSIZ];
91 const char pcxname_fmt[] = "%s/xfig-pcx.XXXXXX";
92 char pcxname_buf[128];
93 char *pcxname = pcxname_buf;
94 char *cmd_fmt;
95 char *cmd = buf;
96 FILE *giftopcx;
97 struct Cmap localColorMap[MAX_COLORMAP_SIZE];
98 int i, stat, fd;
99 int useGlobalColormap;
100 unsigned int bitPixel, red, green, blue;
101 unsigned char c;
102 char version[4];
103 size_t size;
104 struct xfig_stream pcx;
105
106 if (!rewind_stream(pic_stream))
107 return FileInvalid;
108
109 /* command string to convert gif to pcx */
110 if (!system("{ giftopnm -version && ppmtopcx -version; } 2>/dev/null"))
111 cmd_fmt = "giftopnm -quiet | ppmtopcx -quiet >'%s'";
112 else if (!system("convert -version >/dev/null"))
113 cmd_fmt = "convert - pcx:'%s'";
114 else if (!system("gm -version >/dev/null"))
115 cmd_fmt = "gm convert - pcx:'%s'";
116 else {
117 file_msg("Cannot read gif files.");
118 file_msg("To read gif files, install either the netpbm, or the "
119 "imagemagick, or the graphicsmagick package.");
120 return FileInvalid;
121 }
122
123 /* first read header to look for any transparent color extension */
124 if (!ReadOK(pic_stream->fp, buf, 6)) {
125 return FileInvalid;
126 }
127
128 if (strncmp((char*)buf, "GIF", 3) != 0) {
129 return FileInvalid;
130 }
131
132 strncpy(version, (char*)(buf + 3), 3);
133 version[3] = '\0';
134
135 if ((strcmp(version, "87a") != 0) && (strcmp(version, "89a") != 0)) {
136 file_msg("Unknown GIF version %s", version);
137 return FileInvalid;
138 }
139
140 if (!ReadOK(pic_stream->fp, buf, 7)) {
141 return FileInvalid; /* failed to read screen descriptor */
142 }
143
144 GifScreen.Width = LM_to_uint(buf[0],buf[1]);
145 GifScreen.Height = LM_to_uint(buf[2],buf[3]);
146 GifScreen.BitPixel = 2<<(buf[4]&0x07);
147 GifScreen.ColorResolution = (((((int)buf[4])&0x70)>>3)+1);
148 GifScreen.Background = (unsigned int) buf[5];
149 GifScreen.AspectRatio = (unsigned int) buf[6];
150
151 if (BitSet(buf[4], LOCALCOLORMAP)) { /* Global Colormap */
152 if (!ReadColorMap(pic_stream->fp, GifScreen.BitPixel,
153 GifScreen.ColorMap)) {
154 return FileInvalid; /* error reading global colormap */
155 }
156 }
157
158 if (GifScreen.AspectRatio != 0 && GifScreen.AspectRatio != 49) {
159 if (appres.DEBUG)
160 fprintf(stderr, "warning - non-square pixels\n");
161 }
162
163 /* assume no transparent color for now */
164 Gif89.transparent = TRANSP_NONE;
165
166 /* read the full header to get any transparency information */
167 for (;;) {
168 if (!ReadOK(pic_stream->fp, &c, 1)) {
169 return FileInvalid; /* EOF / read error on image data */
170 }
171
172 if (c == ';') { /* GIF terminator, finish up */
173 return PicSuccess; /* all done */
174 }
175
176 if (c == '!') { /* Extension */
177 if (!ReadOK(pic_stream->fp, &c, 1))
178 file_msg("GIF read error on extension function code");
179 (void)DoGIFextension(pic_stream->fp, c);
180 continue;
181 }
182
183 if (c != ',') { /* Not a valid start character*/
184 continue;
185 }
186
187 if (!ReadOK(pic_stream->fp, buf, 9)) {
188 /* couldn't read left/top/width/height */
189 return FileInvalid;
190 }
191
192 useGlobalColormap = !BitSet(buf[8], LOCALCOLORMAP);
193
194 bitPixel = 1<<((buf[8]&0x07)+1);
195
196 if (!useGlobalColormap) {
197 if (!ReadColorMap(pic_stream->fp, bitPixel,
198 localColorMap)) {
199 file_msg("error reading local GIF colormap" );
200 return PicSuccess;
201 }
202 }
203 break; /* image starts here, header is done */
204 }
205
206 /* save transparent indicator */
207 pic->pic_cache->transp = Gif89.transparent;
208
209 /* now call giftopnm and ppmtopcx */
210
211 /* make name for temp output file */
212 size = sizeof pcxname_fmt + strlen(TMPDIR) - 2;
213 if (size > sizeof pcxname_buf && (pcxname = malloc(size)) == NULL) {
214 file_msg("Out of memory.");
215 return FileInvalid;
216 }
217 if (sprintf(pcxname, pcxname_fmt, TMPDIR) < 0) {
218 i = errno;
219 file_msg("Unable to write temporary file path to string");
220 file_msg("Error: %s", strerror(i));
221 if (pcxname != pcxname_buf)
222 free(pcxname);
223 return FileInvalid;
224 }
225 if ((fd = mkstemp(pcxname)) == -1) {
226 file_msg("Cannot create temporary file\n");
227 if (pcxname != pcxname_buf)
228 free(pcxname);
229 return FileInvalid;
230 }
231 close(fd);
232
233 /* make command to convert gif to pcx */
234 size += strlen(cmd_fmt) - 2; /* from above, size == sizeof pcxname */
235 if (size > sizeof buf && (cmd = malloc(size)) == NULL) {
236 file_msg("Out of memory.");
237 unlink(pcxname);
238 if (pcxname != pcxname_buf)
239 free(pcxname);
240 return FileInvalid;
241 }
242 if (sprintf(cmd, cmd_fmt, pcxname) < 0) {
243 i = errno;
244 file_msg("Cannot write command to convert gif to pcx.");
245 file_msg("Error: %s", strerror(i));
246 unlink(pcxname);
247 if (pcxname != pcxname_buf)
248 free(pcxname);
249 return FileInvalid;
250 }
251
252 giftopcx = popen(cmd, "w");
253 if (giftopcx == NULL) {
254 i = errno;
255 file_msg("Cannot open pipe to convert gif to pcx\n");
256 file_msg("Command: %s", cmd);
257 file_msg("Error: %s", strerror(i));
258 if (cmd != buf)
259 free(cmd);
260 unlink(pcxname);
261 if (pcxname != pcxname_buf)
262 free(pcxname);
263 return FileInvalid;
264 }
265
266 /* write the temporary pcx file */
267 rewind_stream(pic_stream);
268 while ((size = fread(buf, 1, sizeof buf, pic_stream->fp)) != 0)
269 fwrite(buf, size, 1, giftopcx);
270
271 if (pclose(giftopcx)) {
272 i = errno;
273 file_msg("Cannot convert gif to pcx\n");
274 file_msg("Command: %s", cmd);
275 file_msg("Error: %s", strerror(i));
276 if (cmd != buf)
277 free(cmd);
278 unlink(pcxname);
279 if (pcxname != pcxname_buf)
280 free(pcxname);
281 return FileInvalid;
282 }
283
284 if (cmd != buf)
285 free(cmd);
286
287 /*
288 * Construct a rudimentary struct xfig_stream that can be passed to
289 * read_pcx(). Tell read_pcx() that the FILE pointer is positioned at
290 * the start (*name == '\0') and that it is a regular file
291 * (*uncompress == '\0').
292 * ATTENTION, requires knowledge of fields of struct xfig_stream.
293 */
294 pcx.name_buf[0] = '\0';
295 pcx.name = pcx.name_buf;
296 pcx.uncompress = pcx.name_buf;
297
298 if ((pcx.fp = fopen(pcxname, "rb")) == NULL) {
299 file_msg("Cannot open temporary output file\n");
300 perror("Error");
301 unlink(pcxname);
302 if (pcxname != pcxname_buf)
303 free(pcxname);
304 return FileInvalid;
305 }
306
307 /* now call read_pcx to read the pcx file */
308 stat = read_pcx(pic, &pcx);
309
310 /* remove temp file */
311 fclose(pcx.fp);
312 unlink(pcxname);
313 if (pcxname != pcxname_buf)
314 free(pcxname);
315
316 pic->pic_cache->subtype = T_PIC_GIF;
317 /* now match original transparent colortable index with possibly new
318 colortable from ppmtopcx */
319 if (pic->pic_cache->transp != TRANSP_NONE) {
320 if (useGlobalColormap) {
321 red = GifScreen.ColorMap[pic->pic_cache->transp].red;
322 green = GifScreen.ColorMap[pic->pic_cache->transp].green;
323 blue = GifScreen.ColorMap[pic->pic_cache->transp].blue;
324 } else {
325 red = localColorMap[pic->pic_cache->transp].red;
326 green = localColorMap[pic->pic_cache->transp].green;
327 blue = localColorMap[pic->pic_cache->transp].blue;
328 }
329 for (i = 0; i < pic->pic_cache->numcols; ++i) {
330 if (pic->pic_cache->cmap[i].red == red &&
331 pic->pic_cache->cmap[i].green == green &&
332 pic->pic_cache->cmap[i].blue == blue)
333 break;
334 }
335 if (i < pic->pic_cache->numcols)
336 pic->pic_cache->transp = i;
337 }
338
339 return stat;
340 }
341
342 static Boolean
ReadColorMap(FILE * fd,unsigned int number,struct Cmap * cmap)343 ReadColorMap(FILE *fd, unsigned int number, struct Cmap *cmap)
344 {
345 unsigned int i;
346 unsigned char rgb[3];
347
348 for (i = 0; i < number; ++i) {
349 if (! ReadOK(fd, rgb, sizeof(rgb))) {
350 file_msg("bad GIF colormap" );
351 return False;
352 }
353 cmap[i].red = rgb[0];
354 cmap[i].green = rgb[1];
355 cmap[i].blue = rgb[2];
356 }
357 return True;
358 }
359
360 static Boolean
DoGIFextension(FILE * fd,int label)361 DoGIFextension(FILE *fd, int label)
362 {
363 static unsigned char buf[256];
364 char *str;
365
366 switch (label) {
367 case 0x01: /* Plain Text Extension */
368 str = "Plain Text Extension";
369 break;
370 case 0xff: /* Application Extension */
371 str = "Application Extension";
372 break;
373 case 0xfe: /* Comment Extension */
374 str = "Comment Extension";
375 while (GetDataBlock(fd, buf) != 0) {
376 ; /* GIF comment */
377 }
378 return False;
379 case 0xf9: /* Graphic Control Extension */
380 str = "Graphic Control Extension";
381 (void) GetDataBlock(fd, (unsigned char*) buf);
382 Gif89.disposal = (buf[0] >> 2) & 0x7;
383 Gif89.inputFlag = (buf[0] >> 1) & 0x1;
384 Gif89.delayTime = LM_to_uint(buf[1],buf[2]);
385 if ((buf[0] & 0x1) != 0)
386 Gif89.transparent = buf[3];
387
388 while (GetDataBlock(fd, buf) != 0)
389 ;
390 return False;
391 default:
392 str = (char *) buf;
393 sprintf(str, "UNKNOWN (0x%02x)", label);
394 break;
395 }
396
397 if (appres.DEBUG)
398 fprintf(stderr,"got a '%s' extension\n", str );
399
400 while (GetDataBlock(fd, buf) != 0)
401 ;
402
403 return False;
404 }
405
406 int ZeroDataBlock = False;
407
408 static int
GetDataBlock(FILE * fd,unsigned char * buf)409 GetDataBlock(FILE *fd, unsigned char *buf)
410 {
411 unsigned char count;
412
413 /* error in getting DataBlock size */
414 if (! ReadOK(fd,&count,1)) {
415 return -1;
416 }
417
418 ZeroDataBlock = count == 0;
419
420 /* error in reading DataBlock */
421 if ((count != 0) && (! ReadOK(fd, buf, count))) {
422 return -1;
423 }
424
425 return count;
426 }
427