1 /* -*- mode: c; c-basic-offset: 2 -*- */
2 /* ungifwrt.c - Functions to write unGIFs -- GIFs with run-length compression,
3 not LZW compression. Idea due to Hutchinson Avenue Software Corporation
4 <http://www.hasc.com>.
5 Copyright (C) 1997-2021 Eddie Kohler, ekohler@gmail.com
6 This file is part of the LCDF GIF library.
7
8 The LCDF GIF library is free software. It is distributed under the GNU
9 General Public License, version 2; you can copy, distribute, or alter it at
10 will, as long as this notice is kept intact and this source code is made
11 available. There is no warranty, express or implied. */
12
13 #if HAVE_CONFIG_H
14 # include <config.h>
15 #elif !defined(__cplusplus) && !defined(inline)
16 /* Assume we don't have inline by default */
17 # define inline
18 #endif
19 #include <lcdfgif/gif.h>
20 #include <stdarg.h>
21 #include <string.h>
22 #include <limits.h>
23 #ifdef __cplusplus
24 extern "C" {
25 #endif
26
27 #define WRITE_BUFFER_SIZE 255
28
29 struct Gif_Writer {
30 FILE *f;
31 uint8_t *v;
32 uint32_t pos;
33 uint32_t cap;
34 Gif_CompressInfo gcinfo;
35 int global_size;
36 int local_size;
37 int errors;
38 int cleared;
39 Gif_Code* rle_next;
40 void (*byte_putter)(uint8_t, struct Gif_Writer *);
41 void (*block_putter)(const uint8_t *, size_t, struct Gif_Writer *);
42 };
43
44
45 #define gifputbyte(b, grr) ((*grr->byte_putter)(b, grr))
46 #define gifputblock(b, l, grr) ((*grr->block_putter)(b, l, grr))
47
48 static inline void
gifputunsigned(uint16_t uns,Gif_Writer * grr)49 gifputunsigned(uint16_t uns, Gif_Writer *grr)
50 {
51 gifputbyte(uns & 0xFF, grr);
52 gifputbyte(uns >> 8, grr);
53 }
54
55
56 static void
file_byte_putter(uint8_t b,Gif_Writer * grr)57 file_byte_putter(uint8_t b, Gif_Writer *grr)
58 {
59 fputc(b, grr->f);
60 }
61
62 static void
file_block_putter(const uint8_t * block,size_t size,Gif_Writer * grr)63 file_block_putter(const uint8_t *block, size_t size, Gif_Writer *grr)
64 {
65 if (fwrite(block, 1, size, grr->f) != size)
66 grr->errors = 1;
67 }
68
69
70 static void
memory_byte_putter(uint8_t b,Gif_Writer * grr)71 memory_byte_putter(uint8_t b, Gif_Writer *grr)
72 {
73 if (grr->pos >= grr->cap) {
74 grr->cap = (grr->cap ? grr->cap * 2 : 1024);
75 Gif_ReArray(grr->v, uint8_t, grr->cap);
76 }
77 if (grr->v) {
78 grr->v[grr->pos] = b;
79 grr->pos++;
80 }
81 }
82
83 static void
memory_block_putter(const uint8_t * data,size_t len,Gif_Writer * grr)84 memory_block_putter(const uint8_t *data, size_t len, Gif_Writer *grr)
85 {
86 while (grr->pos + len >= grr->cap) {
87 grr->cap = (grr->cap ? grr->cap * 2 : 1024);
88 Gif_ReArray(grr->v, uint8_t, grr->cap);
89 }
90 if (grr->v) {
91 memcpy(grr->v + grr->pos, data, len);
92 grr->pos += len;
93 }
94 }
95
96
97 static int
gif_writer_init(Gif_Writer * grr,FILE * f,const Gif_CompressInfo * gcinfo)98 gif_writer_init(Gif_Writer* grr, FILE* f, const Gif_CompressInfo* gcinfo)
99 {
100 grr->f = f;
101 grr->v = NULL;
102 grr->pos = grr->cap = 0;
103 if (gcinfo)
104 grr->gcinfo = *gcinfo;
105 else
106 Gif_InitCompressInfo(&grr->gcinfo);
107 grr->errors = 0;
108 grr->cleared = 0;
109 /* 27.Jul.2001: Must allocate GIF_MAX_CODE + 1 because we assign to
110 rle_next[GIF_MAX_CODE]! Thanks, Jeff Brown <jabrown@ipn.caida.org>, for
111 supplying the buggy files. */
112 grr->rle_next = Gif_NewArray(Gif_Code, GIF_MAX_CODE + 1);
113 if (f) {
114 grr->byte_putter = file_byte_putter;
115 grr->block_putter = file_block_putter;
116 } else {
117 grr->byte_putter = memory_byte_putter;
118 grr->block_putter = memory_block_putter;
119 }
120 return grr->rle_next != 0;
121 }
122
123 static void
gif_writer_cleanup(Gif_Writer * grr)124 gif_writer_cleanup(Gif_Writer* grr)
125 {
126 Gif_DeleteArray(grr->v);
127 Gif_DeleteArray(grr->rle_next);
128 }
129
130
131 static inline const uint8_t *
gif_imageline(Gif_Image * gfi,unsigned pos)132 gif_imageline(Gif_Image *gfi, unsigned pos)
133 {
134 unsigned y, x;
135 if (gfi->width == 0)
136 return NULL;
137 y = pos / gfi->width;
138 x = pos - y * gfi->width;
139 if (y == (unsigned) gfi->height)
140 return NULL;
141 else if (!gfi->interlace)
142 return gfi->img[y] + x;
143 else
144 return gfi->img[Gif_InterlaceLine(y, gfi->height)] + x;
145 }
146
147 static inline unsigned
gif_line_endpos(Gif_Image * gfi,unsigned pos)148 gif_line_endpos(Gif_Image *gfi, unsigned pos)
149 {
150 unsigned y = pos / gfi->width;
151 return (y + 1) * gfi->width;
152 }
153
154 /* Write GIFs compressed with run-length encoding, an idea from code by
155 Hutchinson Avenue Software Corporation <http://www.hasc.com> found in
156 Thomas Boutell's gd library <http://www.boutell.com>. */
157
158 static int
write_compressed_data(Gif_Image * gfi,int min_code_bits,Gif_Writer * grr)159 write_compressed_data(Gif_Image *gfi,
160 int min_code_bits, Gif_Writer *grr)
161 {
162 uint8_t stack_buffer[512 - 24];
163 uint8_t *buf = stack_buffer;
164 unsigned bufpos = 0;
165 unsigned bufcap = sizeof(stack_buffer) * 8;
166
167 unsigned pos;
168 unsigned clear_bufpos, clear_pos;
169 unsigned line_endpos;
170 const uint8_t *imageline;
171
172 unsigned run;
173 #ifndef GIF_NO_COMPRESSION
174 #define RUN_EWMA_SHIFT 4
175 #define RUN_EWMA_SCALE 19
176 #define RUN_INV_THRESH ((unsigned) (1 << RUN_EWMA_SCALE) / 3000)
177 unsigned run_ewma;
178 Gif_Code* rle_next = grr->rle_next;
179 #endif
180
181 Gif_Code next_code = 0;
182 Gif_Code output_code;
183 uint8_t suffix;
184
185 int cur_code_bits;
186
187 /* Here we go! */
188 gifputbyte(min_code_bits, grr);
189 #define CLEAR_CODE ((Gif_Code) (1 << min_code_bits))
190 #define EOI_CODE ((Gif_Code) (CLEAR_CODE + 1))
191 #define CUR_BUMP_CODE (1 << cur_code_bits)
192 grr->cleared = 0;
193
194 cur_code_bits = min_code_bits + 1;
195 /* next_code set by first runthrough of output clear_code */
196 GIF_DEBUG(("clear(%d) eoi(%d) bits(%d) ", CLEAR_CODE, EOI_CODE, cur_code_bits));
197
198 output_code = CLEAR_CODE;
199 /* Because output_code is clear_code, we'll initialize next_code, et al.
200 below. */
201
202 pos = clear_pos = clear_bufpos = 0;
203 line_endpos = gfi->width;
204 imageline = gif_imageline(gfi, pos);
205
206 while (1) {
207
208 /*****
209 * Output 'output_code' to the memory buffer. */
210 if (bufpos + 32 >= bufcap) {
211 unsigned ncap = bufcap * 2 + (24 << 3);
212 uint8_t *nbuf = Gif_NewArray(uint8_t, ncap >> 3);
213 if (!nbuf)
214 goto error;
215 memcpy(nbuf, buf, bufcap >> 3);
216 if (buf != stack_buffer)
217 Gif_DeleteArray(buf);
218 buf = nbuf;
219 bufcap = ncap;
220 }
221
222 {
223 unsigned endpos = bufpos + cur_code_bits;
224 do {
225 if (bufpos & 7)
226 buf[bufpos >> 3] |= output_code << (bufpos & 7);
227 else if (bufpos & 0x7FF)
228 buf[bufpos >> 3] = output_code >> (bufpos - endpos + cur_code_bits);
229 else {
230 buf[bufpos >> 3] = 255;
231 endpos += 8;
232 }
233
234 bufpos += 8 - (bufpos & 7);
235 } while (bufpos < endpos);
236 bufpos = endpos;
237 }
238
239
240 /*****
241 * Handle special codes. */
242
243 if (output_code == CLEAR_CODE) {
244 /* Clear data and prepare gfc */
245 cur_code_bits = min_code_bits + 1;
246 next_code = EOI_CODE + 1;
247 #ifndef GIF_NO_COMPRESSION
248 {
249 Gif_Code c;
250 for (c = 0; c < CLEAR_CODE; ++c)
251 rle_next[c] = CLEAR_CODE;
252 }
253 run_ewma = 1 << RUN_EWMA_SCALE;
254 #endif
255 run = 0;
256 clear_pos = clear_bufpos = 0;
257
258 GIF_DEBUG(("clear "));
259
260 } else if (output_code == EOI_CODE)
261 break;
262
263 else {
264 if (next_code > CUR_BUMP_CODE && cur_code_bits < GIF_MAX_CODE_BITS)
265 /* bump up compression size */
266 ++cur_code_bits;
267
268 #ifndef GIF_NO_COMPRESSION
269 /* Adjust current run length average. */
270 run = (run << RUN_EWMA_SCALE) + (1 << (RUN_EWMA_SHIFT - 1));
271 if (run < run_ewma)
272 run_ewma -= (run_ewma - run) >> RUN_EWMA_SHIFT;
273 else
274 run_ewma += (run - run_ewma) >> RUN_EWMA_SHIFT;
275 #else
276 if (cur_code_bits != min_code_bits) {
277 /* never bump up compression size -- keep cur_code_bits small by
278 generating clear_codes */
279 output_code = CLEAR_CODE;
280 continue;
281 }
282 #endif
283
284 /* Reset run length. */
285 run = 0;
286 }
287
288
289 /*****
290 * Find the next code to output. */
291
292 /* If height is 0 -- no more pixels to write -- we output work_node next
293 time around. */
294 if (imageline) {
295 output_code = suffix = *imageline;
296
297 while (1) {
298 imageline++;
299 pos++;
300 if (pos == line_endpos) {
301 imageline = gif_imageline(gfi, pos);
302 line_endpos += gfi->width;
303 }
304 run++;
305 #ifndef GIF_NO_COMPRESSION
306 if (!imageline || *imageline != suffix || rle_next[output_code] == CLEAR_CODE)
307 break;
308 output_code = rle_next[output_code];
309 #else
310 break;
311 #endif
312 }
313
314 /* Output the current code. */
315 if (next_code < GIF_MAX_CODE) {
316 #ifndef GIF_NO_COMPRESSION
317 if (imageline && *imageline == suffix) {
318 rle_next[output_code] = next_code;
319 rle_next[next_code] = CLEAR_CODE;
320 }
321 #endif
322 next_code++;
323 } else
324 next_code = GIF_MAX_CODE + 1; /* to match "> CUR_BUMP_CODE" above */
325
326 /* Check whether to clear table. */
327 if (next_code > 4094) {
328 int do_clear = grr->gcinfo.flags & GIF_WRITE_EAGER_CLEAR;
329
330 #ifndef GIF_NO_COMPRESSION
331 if (!do_clear) {
332 unsigned pixels_left = gfi->width * gfi->height - pos;
333 if (pixels_left) {
334 /* Always clear if run_ewma gets small relative to
335 min_code_bits. Otherwise, clear if #images/run is smaller
336 than an empirical threshold, meaning it will take more than
337 3000 or so average runs to complete the image. */
338 if (run_ewma < ((36U << RUN_EWMA_SCALE) / min_code_bits)
339 || pixels_left > UINT_MAX / RUN_INV_THRESH
340 || run_ewma < pixels_left * RUN_INV_THRESH)
341 do_clear = 1;
342 }
343 }
344 #else
345 do_clear = 1;
346 #endif
347
348 if ((do_clear || run < 7) && !clear_pos) {
349 clear_pos = pos - run;
350 clear_bufpos = bufpos;
351 } else if (!do_clear && run > 50)
352 clear_pos = clear_bufpos = 0;
353
354 if (do_clear) {
355 GIF_DEBUG(("rewind %u pixels/%d bits ", pos - clear_pos, bufpos + cur_code_bits - clear_bufpos));
356 output_code = CLEAR_CODE;
357 pos = clear_pos;
358 imageline = gif_imageline(gfi, pos);
359 line_endpos = gif_line_endpos(gfi, pos);
360 bufpos = clear_bufpos;
361 buf[bufpos >> 3] &= (1 << (bufpos & 7)) - 1;
362 grr->cleared = 1;
363 }
364 }
365 } else
366 output_code = EOI_CODE;
367 }
368
369 /* Output memory buffer to stream. */
370 bufpos = (bufpos + 7) >> 3;
371 buf[(bufpos - 1) & 0xFFFFFF00] = (bufpos - 1) & 0xFF;
372 buf[bufpos] = 0;
373 gifputblock(buf, bufpos + 1, grr);
374
375 if (buf != stack_buffer)
376 Gif_DeleteArray(buf);
377 return 1;
378
379 error:
380 if (buf != stack_buffer)
381 Gif_DeleteArray(buf);
382 return 0;
383 }
384
385
386 static int
calculate_min_code_bits(Gif_Image * gfi,const Gif_Writer * grr)387 calculate_min_code_bits(Gif_Image *gfi, const Gif_Writer *grr)
388 {
389 int colors_used = -1, min_code_bits, i;
390
391 if (grr->gcinfo.flags & GIF_WRITE_CAREFUL_MIN_CODE_SIZE) {
392 /* calculate m_c_b based on colormap */
393 if (grr->local_size > 0)
394 colors_used = grr->local_size;
395 else if (grr->global_size > 0)
396 colors_used = grr->global_size;
397
398 } else if (gfi->img) {
399 /* calculate m_c_b from uncompressed data */
400 int x, y, width = gfi->width, height = gfi->height;
401 colors_used = 0;
402 for (y = 0; y < height && colors_used < 128; y++) {
403 uint8_t *data = gfi->img[y];
404 for (x = width; x > 0; x--, data++)
405 if (*data > colors_used)
406 colors_used = *data;
407 }
408 colors_used++;
409
410 } else if (gfi->compressed) {
411 /* take m_c_b from compressed image */
412 colors_used = 1 << gfi->compressed[0];
413
414 } else {
415 /* should never happen */
416 colors_used = 256;
417 }
418
419 min_code_bits = 2; /* min_code_bits of 1 isn't allowed */
420 i = 4;
421 while (i < colors_used) {
422 min_code_bits++;
423 i *= 2;
424 }
425
426 return min_code_bits;
427 }
428
429
430 static int get_color_table_size(const Gif_Stream *gfs, Gif_Image *gfi,
431 Gif_Writer *grr);
432
433 static void
save_compression_result(Gif_Image * gfi,Gif_Writer * grr,int ok)434 save_compression_result(Gif_Image *gfi, Gif_Writer *grr, int ok)
435 {
436 if (!(grr->gcinfo.flags & GIF_WRITE_SHRINK)
437 || (ok && (!gfi->compressed || gfi->compressed_len > grr->pos))) {
438 if (gfi->compressed)
439 (*gfi->free_compressed)((void *) gfi->compressed);
440 if (ok) {
441 gfi->compressed_len = grr->pos;
442 gfi->compressed_errors = 0;
443 gfi->compressed = grr->v;
444 gfi->free_compressed = Gif_Free;
445 grr->v = 0;
446 grr->cap = 0;
447 } else
448 gfi->compressed = 0;
449 }
450 grr->pos = 0;
451 }
452
453 int
Gif_FullCompressImage(Gif_Stream * gfs,Gif_Image * gfi,const Gif_CompressInfo * gcinfo)454 Gif_FullCompressImage(Gif_Stream *gfs, Gif_Image *gfi,
455 const Gif_CompressInfo *gcinfo)
456 {
457 int ok = 0;
458 uint8_t min_code_bits;
459 Gif_Writer grr;
460
461 if (!gif_writer_init(&grr, NULL, gcinfo)) {
462 if (!(grr.gcinfo.flags & GIF_WRITE_SHRINK))
463 Gif_ReleaseCompressedImage(gfi);
464 goto done;
465 }
466
467 grr.global_size = get_color_table_size(gfs, 0, &grr);
468 grr.local_size = get_color_table_size(gfs, gfi, &grr);
469
470 min_code_bits = calculate_min_code_bits(gfi, &grr);
471 ok = write_compressed_data(gfi, min_code_bits, &grr);
472 save_compression_result(gfi, &grr, ok);
473
474 if ((grr.gcinfo.flags & (GIF_WRITE_OPTIMIZE | GIF_WRITE_EAGER_CLEAR))
475 == GIF_WRITE_OPTIMIZE
476 && grr.cleared && ok) {
477 grr.gcinfo.flags |= GIF_WRITE_EAGER_CLEAR | GIF_WRITE_SHRINK;
478 if (write_compressed_data(gfi, min_code_bits, &grr))
479 save_compression_result(gfi, &grr, 1);
480 }
481
482 done:
483 gif_writer_cleanup(&grr);
484 return ok;
485 }
486
487
488 static int
get_color_table_size(const Gif_Stream * gfs,Gif_Image * gfi,Gif_Writer * grr)489 get_color_table_size(const Gif_Stream *gfs, Gif_Image *gfi, Gif_Writer *grr)
490 {
491 Gif_Colormap *gfcm = (gfi ? gfi->local : gfs->global);
492 int ncol, totalcol, i;
493
494 if (!gfcm || gfcm->ncol <= 0)
495 return 0;
496
497 /* Make sure ncol is reasonable */
498 ncol = gfcm->ncol;
499
500 /* Possibly bump up 'ncol' based on 'transparent' values, if
501 careful_min_code_bits */
502 if (grr->gcinfo.flags & GIF_WRITE_CAREFUL_MIN_CODE_SIZE) {
503 if (gfi && gfi->transparent >= ncol)
504 ncol = gfi->transparent + 1;
505 else if (!gfi)
506 for (i = 0; i < gfs->nimages; i++)
507 if (gfs->images[i]->transparent >= ncol)
508 ncol = gfs->images[i]->transparent + 1;
509 }
510
511 /* Make sure the colormap is a power of two entries! */
512 /* GIF format doesn't allow a colormap with only 1 entry. */
513 if (ncol > 256)
514 ncol = 256;
515 for (totalcol = 2; totalcol < ncol; totalcol *= 2)
516 /* nada */;
517
518 return totalcol;
519 }
520
521 static void
write_color_table(Gif_Colormap * gfcm,int totalcol,Gif_Writer * grr)522 write_color_table(Gif_Colormap *gfcm, int totalcol, Gif_Writer *grr)
523 {
524 Gif_Color *c = gfcm->col;
525 int i, ncol = gfcm->ncol;
526
527 for (i = 0; i < ncol && i < totalcol; i++, c++) {
528 gifputbyte(c->gfc_red, grr);
529 gifputbyte(c->gfc_green, grr);
530 gifputbyte(c->gfc_blue, grr);
531 }
532
533 /* Pad out colormap with black. */
534 for (; i < totalcol; i++) {
535 gifputbyte(0, grr);
536 gifputbyte(0, grr);
537 gifputbyte(0, grr);
538 }
539 }
540
541
542 static int
write_image(Gif_Stream * gfs,Gif_Image * gfi,Gif_Writer * grr)543 write_image(Gif_Stream *gfs, Gif_Image *gfi, Gif_Writer *grr)
544 {
545 uint8_t min_code_bits, packed = 0;
546 grr->local_size = get_color_table_size(gfs, gfi, grr);
547
548 gifputbyte(',', grr);
549 gifputunsigned(gfi->left, grr);
550 gifputunsigned(gfi->top, grr);
551 gifputunsigned(gfi->width, grr);
552 gifputunsigned(gfi->height, grr);
553
554 if (grr->local_size > 0) {
555 int size = 2;
556 packed |= 0x80;
557 while (size < grr->local_size)
558 size *= 2, packed++;
559 }
560
561 if (gfi->interlace) packed |= 0x40;
562 gifputbyte(packed, grr);
563
564 if (grr->local_size > 0)
565 write_color_table(gfi->local, grr->local_size, grr);
566
567 /* calculate min_code_bits here (because calculation may involve
568 recompression, if GIF_WRITE_CAREFUL_MIN_CODE_SIZE is true) */
569 min_code_bits = calculate_min_code_bits(gfi, grr);
570
571 /* use existing compressed data if it exists. This will tend to whip
572 people's asses who uncompress an image, keep the compressed data around,
573 but modify the uncompressed data anyway. That sucks. */
574 if (gfi->compressed
575 && (!(grr->gcinfo.flags & GIF_WRITE_CAREFUL_MIN_CODE_SIZE)
576 || gfi->compressed[0] == min_code_bits)) {
577 uint8_t *compressed = gfi->compressed;
578 uint32_t compressed_len = gfi->compressed_len;
579 while (compressed_len > 0) {
580 uint16_t amt = (compressed_len > 0x7000 ? 0x7000 : compressed_len);
581 gifputblock(compressed, amt, grr);
582 compressed += amt;
583 compressed_len -= amt;
584 }
585
586 } else if (!gfi->img) {
587 Gif_UncompressImage(gfs, gfi);
588 write_compressed_data(gfi, min_code_bits, grr);
589 Gif_ReleaseUncompressedImage(gfi);
590
591 } else
592 write_compressed_data(gfi, min_code_bits, grr);
593
594 return 1;
595 }
596
597
598 static void
write_logical_screen_descriptor(Gif_Stream * gfs,Gif_Writer * grr)599 write_logical_screen_descriptor(Gif_Stream *gfs, Gif_Writer *grr)
600 {
601 uint8_t packed = 0x70; /* high resolution colors */
602 grr->global_size = get_color_table_size(gfs, 0, grr);
603
604 Gif_CalculateScreenSize(gfs, 0);
605 gifputunsigned(gfs->screen_width, grr);
606 gifputunsigned(gfs->screen_height, grr);
607
608 if (grr->global_size > 0) {
609 uint16_t size = 2;
610 packed |= 0x80;
611 while (size < grr->global_size)
612 size *= 2, packed++;
613 }
614
615 gifputbyte(packed, grr);
616 if (gfs->background < grr->global_size)
617 gifputbyte(gfs->background, grr);
618 else
619 gifputbyte(255, grr);
620 gifputbyte(0, grr); /* no aspect ratio information */
621
622 if (grr->global_size > 0)
623 write_color_table(gfs->global, grr->global_size, grr);
624 }
625
626
627 /* extension byte table:
628 0x01 plain text extension
629 0xCE name*
630 0xF9 graphic control extension
631 0xFE comment extension
632 0xFF application extension
633 */
634
635 static void
write_graphic_control_extension(Gif_Image * gfi,Gif_Writer * grr)636 write_graphic_control_extension(Gif_Image *gfi, Gif_Writer *grr)
637 {
638 uint8_t packed = 0;
639 gifputbyte('!', grr);
640 gifputbyte(0xF9, grr);
641 gifputbyte(4, grr);
642 if (gfi->transparent >= 0) packed |= 0x01;
643 packed |= (gfi->disposal & 0x07) << 2;
644 gifputbyte(packed, grr);
645 gifputunsigned(gfi->delay, grr);
646 gifputbyte((uint8_t)gfi->transparent, grr);
647 gifputbyte(0, grr);
648 }
649
650
651 static void
blast_data(const uint8_t * data,int len,Gif_Writer * grr)652 blast_data(const uint8_t *data, int len, Gif_Writer *grr)
653 {
654 while (len > 0) {
655 int s = len > 255 ? 255 : len;
656 gifputbyte(s, grr);
657 gifputblock(data, s, grr);
658 data += s;
659 len -= s;
660 }
661 gifputbyte(0, grr);
662 }
663
664
665 static void
write_name_extension(char * id,Gif_Writer * grr)666 write_name_extension(char *id, Gif_Writer *grr)
667 {
668 gifputbyte('!', grr);
669 gifputbyte(0xCE, grr);
670 blast_data((uint8_t *)id, strlen(id), grr);
671 }
672
673
674 static void
write_comment_extensions(Gif_Comment * gfcom,Gif_Writer * grr)675 write_comment_extensions(Gif_Comment *gfcom, Gif_Writer *grr)
676 {
677 int i;
678 for (i = 0; i < gfcom->count; i++) {
679 gifputbyte('!', grr);
680 gifputbyte(0xFE, grr);
681 blast_data((const uint8_t *)gfcom->str[i], gfcom->len[i], grr);
682 }
683 }
684
685
686 static void
write_netscape_loop_extension(uint16_t value,Gif_Writer * grr)687 write_netscape_loop_extension(uint16_t value, Gif_Writer *grr)
688 {
689 gifputblock((const uint8_t *)"!\xFF\x0BNETSCAPE2.0\x03\x01", 16, grr);
690 gifputunsigned(value, grr);
691 gifputbyte(0, grr);
692 }
693
694
695 static void
write_generic_extension(Gif_Extension * gfex,Gif_Writer * grr)696 write_generic_extension(Gif_Extension *gfex, Gif_Writer *grr)
697 {
698 uint32_t pos = 0;
699 if (gfex->kind < 0) return; /* ignore our private extensions */
700
701 gifputbyte('!', grr);
702 gifputbyte(gfex->kind, grr);
703 if (gfex->kind == 255) { /* an application extension */
704 if (gfex->applength) {
705 gifputbyte(gfex->applength, grr);
706 gifputblock((const uint8_t*) gfex->appname, gfex->applength, grr);
707 }
708 }
709 if (gfex->packetized)
710 gifputblock(gfex->data, gfex->length, grr);
711 else {
712 while (pos + 255 < gfex->length) {
713 gifputbyte(255, grr);
714 gifputblock(gfex->data + pos, 255, grr);
715 pos += 255;
716 }
717 if (pos < gfex->length) {
718 uint32_t len = gfex->length - pos;
719 gifputbyte(len, grr);
720 gifputblock(gfex->data + pos, len, grr);
721 }
722 }
723 gifputbyte(0, grr);
724 }
725
726 static int
write_gif(Gif_Stream * gfs,Gif_Writer * grr)727 write_gif(Gif_Stream *gfs, Gif_Writer *grr)
728 {
729 Gif_Extension* gfex;
730 int ok = 0;
731 int i;
732
733 {
734 uint8_t isgif89a = 0;
735 if (gfs->end_comment || gfs->end_extension_list || gfs->loopcount > -1)
736 isgif89a = 1;
737 for (i = 0; i < gfs->nimages && !isgif89a; i++) {
738 Gif_Image* gfi = gfs->images[i];
739 if (gfi->identifier || gfi->transparent != -1 || gfi->disposal
740 || gfi->delay || gfi->comment || gfi->extension_list)
741 isgif89a = 1;
742 }
743 if (isgif89a)
744 gifputblock((const uint8_t *)"GIF89a", 6, grr);
745 else
746 gifputblock((const uint8_t *)"GIF87a", 6, grr);
747 }
748
749 write_logical_screen_descriptor(gfs, grr);
750
751 if (gfs->loopcount > -1)
752 write_netscape_loop_extension(gfs->loopcount, grr);
753
754 for (i = 0; i < gfs->nimages; i++)
755 if (!Gif_IncrementalWriteImage(grr, gfs, gfs->images[i]))
756 goto done;
757
758 for (gfex = gfs->end_extension_list; gfex; gfex = gfex->next)
759 write_generic_extension(gfex, grr);
760 if (gfs->end_comment)
761 write_comment_extensions(gfs->end_comment, grr);
762
763 gifputbyte(';', grr);
764 ok = 1;
765
766 done:
767 return ok;
768 }
769
770
771 int
Gif_FullWriteFile(Gif_Stream * gfs,const Gif_CompressInfo * gcinfo,FILE * f)772 Gif_FullWriteFile(Gif_Stream *gfs, const Gif_CompressInfo *gcinfo,
773 FILE *f)
774 {
775 Gif_Writer grr;
776 int ok = gif_writer_init(&grr, f, gcinfo)
777 && write_gif(gfs, &grr);
778 gif_writer_cleanup(&grr);
779 return ok;
780 }
781
782
783 Gif_Writer*
Gif_IncrementalWriteFileInit(Gif_Stream * gfs,const Gif_CompressInfo * gcinfo,FILE * f)784 Gif_IncrementalWriteFileInit(Gif_Stream* gfs, const Gif_CompressInfo* gcinfo,
785 FILE *f)
786 {
787 Gif_Writer* grr = Gif_New(Gif_Writer);
788 if (!grr || !gif_writer_init(grr, f, gcinfo)) {
789 Gif_Delete(grr);
790 return NULL;
791 }
792 gifputblock((const uint8_t *)"GIF89a", 6, grr);
793 write_logical_screen_descriptor(gfs, grr);
794 if (gfs->loopcount > -1)
795 write_netscape_loop_extension(gfs->loopcount, grr);
796 return grr;
797 }
798
799 int
Gif_IncrementalWriteImage(Gif_Writer * grr,Gif_Stream * gfs,Gif_Image * gfi)800 Gif_IncrementalWriteImage(Gif_Writer* grr, Gif_Stream* gfs, Gif_Image* gfi)
801 {
802 Gif_Extension *gfex;
803 for (gfex = gfi->extension_list; gfex; gfex = gfex->next)
804 write_generic_extension(gfex, grr);
805 if (gfi->comment)
806 write_comment_extensions(gfi->comment, grr);
807 if (gfi->identifier)
808 write_name_extension(gfi->identifier, grr);
809 if (gfi->transparent != -1 || gfi->disposal || gfi->delay)
810 write_graphic_control_extension(gfi, grr);
811 return write_image(gfs, gfi, grr);
812 }
813
814 int
Gif_IncrementalWriteComplete(Gif_Writer * grr,Gif_Stream * gfs)815 Gif_IncrementalWriteComplete(Gif_Writer* grr, Gif_Stream* gfs)
816 {
817 Gif_Extension* gfex;
818 for (gfex = gfs->end_extension_list; gfex; gfex = gfex->next)
819 write_generic_extension(gfex, grr);
820 if (gfs->end_comment)
821 write_comment_extensions(gfs->end_comment, grr);
822 gifputbyte(';', grr);
823 gif_writer_cleanup(grr);
824 Gif_Delete(grr);
825 return 1;
826 }
827
828
829 #undef Gif_CompressImage
830 #undef Gif_WriteFile
831
832 int
Gif_CompressImage(Gif_Stream * gfs,Gif_Image * gfi)833 Gif_CompressImage(Gif_Stream *gfs, Gif_Image *gfi)
834 {
835 return Gif_FullCompressImage(gfs, gfi, 0);
836 }
837
838 int
Gif_WriteFile(Gif_Stream * gfs,FILE * f)839 Gif_WriteFile(Gif_Stream *gfs, FILE *f)
840 {
841 return Gif_FullWriteFile(gfs, 0, f);
842 }
843
844
845 #ifdef __cplusplus
846 }
847 #endif
848