1 // based on gif.h by Charlie Tangora
3 // Email me : ctangora -at- gmail -dot- com
5 // This file offers a simple, very limited way to create animated GIFs directly in code.
7 // Those looking for particular cleverness are likely to be disappointed; it's pretty
8 // much a straight-ahead implementation of the GIF format with optional Floyd-Steinberg
9 // dithering. (It does at least use delta encoding - only the changed portions of each
12 // So resulting files are often quite large. The hope is that it will be handy nonetheless
13 // as a quick and easily-integrated way for programs to spit out animations.
15 // Only RGBA8 is currently supported as an input format. (The alpha is ignored.)
21 * import iv.gifwriter;
23 * auto tc = new TrueColorImage(100, 50);
24 * auto fo = File("zgif.gif", "w");
25 * auto gw = new GifWriter((buf) { fo.rawWrite(buf); }, tc.width, tc.height, 2);
27 * foreach (immutable n; 0..tc.width) {
28 * tc.setPixel(n, 0, Color.red);
31 * foreach (immutable n; 1..tc.height) {
32 * tc.setPixel(tc.width-1, n, Color.green);
35 * foreach_reverse (immutable n; 0..tc.width-1) {
36 * tc.setPixel(n, tc.height-1, Color.blue);
39 * foreach_reverse (immutable n; 0..tc.height-1) {
40 * tc.setPixel(0, n, Color.white);
47 module iv
.gifwriter
/*is aliced*/;
51 // ////////////////////////////////////////////////////////////////////////// //
52 static if (__traits(compiles
, (){import arsd
.color
;})) {
54 enum GifWriterHasArsdColor
= true;
56 enum GifWriterHasArsdColor
= false;
60 // ////////////////////////////////////////////////////////////////////////// //
61 enum kGifTransIndex
= 0;
70 // k-d tree over RGB space, organized in heap fashion
71 // i.e. left child of node i is node i*2, right child is node i*2+1
72 // nodes 256-511 are implicitly the leaves, containing a color
73 ubyte[256] treeSplitElt
;
77 // max, min, and abs functions
78 int gifIMax() (int l
, int r
) { pragma(inline
, true); return (l
> r ? l
: r
); }
79 int gifIMin() (int l
, int r
) { pragma(inline
, true); return (l
< r ? l
: r
); }
80 int gifIAbs() (int i
) { pragma(inline
, true); return (i
< 0 ?
-i
: i
); }
82 // walks the k-d tree to pick the palette entry for a desired color.
83 // Takes as in/out parameters the current best color and its error -
84 // only changes them if it finds a better color in its subtree.
85 // this is the major hotspot in the code at the moment.
86 void gifGetClosestPaletteColor (GifPalette
* pPal
, int r
, int g
, int b
, ref int bestInd
, ref int bestDiff
, int treeRoot
=1) {
87 // base case, reached the bottom of the tree
88 if (treeRoot
> (1<<pPal
.bitDepth
)-1) {
89 int ind
= treeRoot
-(1<<pPal
.bitDepth
);
90 if (ind
== kGifTransIndex
) return;
91 // check whether this color is better than the current winner
92 int r_err
= r
-(cast(int)pPal
.r
.ptr
[ind
]);
93 int g_err
= g
-(cast(int)pPal
.g
.ptr
[ind
]);
94 int b_err
= b
-(cast(int)pPal
.b
.ptr
[ind
]);
95 int diff
= gifIAbs(r_err
)+gifIAbs(g_err
)+gifIAbs(b_err
);
96 if (diff
< bestDiff
) {
102 // take the appropriate color (r, g, or b) for this node of the k-d tree
107 int splitComp
= comps
[pPal
.treeSplitElt
.ptr
[treeRoot
]];
109 int splitPos
= pPal
.treeSplit
.ptr
[treeRoot
];
110 if (splitPos
> splitComp
) {
111 // check the left subtree
112 gifGetClosestPaletteColor(pPal
, r
, g
, b
, bestInd
, bestDiff
, treeRoot
*2);
113 if (bestDiff
> splitPos
-splitComp
) {
114 // cannot prove there's not a better value in the right subtree, check that too
115 gifGetClosestPaletteColor(pPal
, r
, g
, b
, bestInd
, bestDiff
, treeRoot
*2+1);
118 gifGetClosestPaletteColor(pPal
, r
, g
, b
, bestInd
, bestDiff
, treeRoot
*2+1);
119 if (bestDiff
> splitComp
-splitPos
) {
120 gifGetClosestPaletteColor(pPal
, r
, g
, b
, bestInd
, bestDiff
, treeRoot
*2);
126 void gifSwapPixels (ubyte* image
, int pixA
, int pixB
) {
127 ubyte rA
= image
[pixA
*4];
128 ubyte gA
= image
[pixA
*4+1];
129 ubyte bA
= image
[pixA
*4+2];
130 ubyte aA
= image
[pixA
*4+3];
132 ubyte rB
= image
[pixB
*4];
133 ubyte gB
= image
[pixB
*4+1];
134 ubyte bB
= image
[pixB
*4+2];
135 ubyte aB
= image
[pixA
*4+3];
138 image
[pixA
*4+1] = gB
;
139 image
[pixA
*4+2] = bB
;
140 image
[pixA
*4+3] = aB
;
143 image
[pixB
*4+1] = gA
;
144 image
[pixB
*4+2] = bA
;
145 image
[pixB
*4+3] = aA
;
149 // just the partition operation from quicksort
150 int gifPartition (ubyte* image
, in int left
, in int right
, in int elt
, int pivotIndex
) {
151 immutable int pivotValue
= image
[(pivotIndex
)*4+elt
];
152 gifSwapPixels(image
, pivotIndex
, right
-1);
153 int storeIndex
= left
;
155 for (int ii
= left
; ii
< right
-1; ++ii
) {
156 int arrayVal
= image
[ii
*4+elt
];
157 if (arrayVal
< pivotValue
) {
158 gifSwapPixels(image
, ii
, storeIndex
);
160 } else if (arrayVal
== pivotValue
) {
162 gifSwapPixels(image
, ii
, storeIndex
);
168 gifSwapPixels(image
, storeIndex
, right
-1);
173 // perform an incomplete sort, finding all elements above and below the desired median
174 void gifPartitionByMedian (ubyte* image
, int left
, int right
, int com
, int neededCenter
) {
175 if (left
< right
-1) {
176 int pivotIndex
= left
+(right
-left
)/2;
177 pivotIndex
= gifPartition(image
, left
, right
, com
, pivotIndex
);
178 // only "sort" the section of the array that contains the median
179 if (pivotIndex
> neededCenter
) gifPartitionByMedian(image
, left
, pivotIndex
, com
, neededCenter
);
180 if (pivotIndex
< neededCenter
) gifPartitionByMedian(image
, pivotIndex
+1, right
, com
, neededCenter
);
185 // builds a palette by creating a balanced k-d tree of all pixels in the image
186 void gifSplitPalette (ubyte* image
, int numPixels
, int firstElt
, int lastElt
, int splitElt
, int splitDist
, int treeNode
, bool buildForDither
, GifPalette
* pal
) {
187 if (lastElt
<= firstElt || numPixels
== 0) return;
188 // base case, bottom of the tree
189 if (lastElt
== firstElt
+1) {
190 if (buildForDither
) {
191 // dithering needs at least one color as dark as anything in the image and at least one brightest color
192 // otherwise it builds up error and produces strange artifacts
194 // special case: the darkest color in the image
195 uint r
= 255, g
= 255, b
= 255;
196 for (int ii
= 0; ii
< numPixels
; ++ii
) {
197 r
= gifIMin(r
, image
[ii
*4+0]);
198 g
= gifIMin(g
, image
[ii
*4+1]);
199 b
= gifIMin(b
, image
[ii
*4+2]);
201 pal
.r
.ptr
[firstElt
] = cast(ubyte)r
;
202 pal
.g
.ptr
[firstElt
] = cast(ubyte)g
;
203 pal
.b
.ptr
[firstElt
] = cast(ubyte)b
;
206 if (firstElt
== (1<<pal
.bitDepth
)-1) {
207 // special case: the lightest color in the image
208 uint r
= 0, g
= 0, b
= 0;
209 for (int ii
= 0; ii
< numPixels
; ++ii
) {
210 r
= gifIMax(r
, image
[ii
*4+0]);
211 g
= gifIMax(g
, image
[ii
*4+1]);
212 b
= gifIMax(b
, image
[ii
*4+2]);
214 pal
.r
.ptr
[firstElt
] = cast(ubyte)r
;
215 pal
.g
.ptr
[firstElt
] = cast(ubyte)g
;
216 pal
.b
.ptr
[firstElt
] = cast(ubyte)b
;
221 // otherwise, take the average of all colors in this subcube
222 ulong r
= 0, g
= 0, b
= 0;
223 for (int ii
= 0; ii
< numPixels
; ++ii
) {
229 r
+= numPixels
/2; // round to nearest
237 pal
.r
.ptr
[firstElt
] = cast(ubyte)r
;
238 pal
.g
.ptr
[firstElt
] = cast(ubyte)g
;
239 pal
.b
.ptr
[firstElt
] = cast(ubyte)b
;
244 // find the axis with the largest range
245 int minR
= 255, maxR
= 0;
246 int minG
= 255, maxG
= 0;
247 int minB
= 255, maxB
= 0;
248 for (int ii
= 0; ii
< numPixels
; ++ii
) {
249 int r
= image
[ii
*4+0];
250 int g
= image
[ii
*4+1];
251 int b
= image
[ii
*4+2];
252 if (r
> maxR
) maxR
= r
;
253 if (r
< minR
) minR
= r
;
254 if (g
> maxG
) maxG
= g
;
255 if (g
< minG
) minG
= g
;
256 if (b
> maxB
) maxB
= b
;
257 if (b
< minB
) minB
= b
;
260 int rRange
= maxR
-minR
;
261 int gRange
= maxG
-minG
;
262 int bRange
= maxB
-minB
;
264 // and split along that axis. (incidentally, this means this isn't a "proper" k-d tree but I don't know what else to call it)
266 if (bRange
> gRange
) splitCom
= 2;
267 if (rRange
> bRange
&& rRange
> gRange
) splitCom
= 0;
269 int subPixelsA
= numPixels
*(splitElt
-firstElt
)/(lastElt
-firstElt
);
270 int subPixelsB
= numPixels
-subPixelsA
;
272 gifPartitionByMedian(image
, 0, numPixels
, splitCom
, subPixelsA
);
274 pal
.treeSplitElt
.ptr
[treeNode
] = cast(ubyte)splitCom
;
275 pal
.treeSplit
.ptr
[treeNode
] = image
[subPixelsA
*4+splitCom
];
277 gifSplitPalette(image
, subPixelsA
, firstElt
, splitElt
, splitElt
-splitDist
, splitDist
/2, treeNode
*2, buildForDither
, pal
);
278 gifSplitPalette(image
+subPixelsA
*4, subPixelsB
, splitElt
, lastElt
, splitElt
+splitDist
, splitDist
/2, treeNode
*2+1, buildForDither
, pal
);
282 // Finds all pixels that have changed from the previous image and
283 // moves them to the fromt of th buffer.
284 // This allows us to build a palette optimized for the colors of the
285 // changed pixels only.
286 int gifPickChangedPixels (const(ubyte)* lastFrame
, ubyte* frame
, int numPixels
) {
288 ubyte* writeIter
= frame
;
289 for (int ii
= 0; ii
< numPixels
; ++ii
) {
290 if (lastFrame
[0] != frame
[0] || lastFrame
[1] != frame
[1] || lastFrame
[2] != frame
[2]) {
291 writeIter
[0] = frame
[0];
292 writeIter
[1] = frame
[1];
293 writeIter
[2] = frame
[2];
304 // Creates a palette by placing all the image pixels in a k-d tree and then averaging the blocks at the bottom.
305 // This is known as the "modified median split" technique
306 void gifMakePalette (const(ubyte)* lastFrame
, const(ubyte)* nextFrame
, uint width
, uint height
, ubyte bitDepth
, bool buildForDither
, GifPalette
* pPal
) {
307 import core
.stdc
.stdlib
: malloc
, free
;
308 import core
.stdc
.string
: memcpy
;
310 pPal
.bitDepth
= bitDepth
;
312 // SplitPalette is destructive (it sorts the pixels by color) so
313 // we must create a copy of the image for it to destroy
314 int imageSize
= width
*height
*4*cast(int)ubyte.sizeof
;
315 ubyte* destroyableImage
= cast(ubyte*)malloc(imageSize
);
316 if (destroyableImage
is null) assert(0, "out of memory");
317 scope(exit
) free(destroyableImage
);
318 memcpy(destroyableImage
, nextFrame
, imageSize
);
320 int numPixels
= width
*height
;
321 if (lastFrame
) numPixels
= gifPickChangedPixels(lastFrame
, destroyableImage
, numPixels
);
323 immutable int lastElt
= 1<<bitDepth
;
324 immutable int splitElt
= lastElt
/2;
325 immutable int splitDist
= splitElt
/2;
327 gifSplitPalette(destroyableImage
, numPixels
, 1, lastElt
, splitElt
, splitDist
, 1, buildForDither
, pPal
);
329 //GIF_TEMP_FREE(destroyableImage);
331 // add the bottom node for the transparency index
332 pPal
.treeSplit
.ptr
[1<<(bitDepth
-1)] = 0;
333 pPal
.treeSplitElt
.ptr
[1<<(bitDepth
-1)] = 0;
335 pPal
.r
.ptr
[0] = pPal
.g
.ptr
[0] = pPal
.b
.ptr
[0] = 0;
339 // Implements Floyd-Steinberg dithering, writes palette value to alpha
340 void gifDitherImage (const(ubyte)* lastFrame
, const(ubyte)* nextFrame
, ubyte* outFrame
, uint width
, uint height
, GifPalette
* pPal
) {
341 import core
.stdc
.stdlib
: malloc
, free
;
342 int numPixels
= width
*height
;
344 // quantPixels initially holds color*256 for all pixels
345 // The extra 8 bits of precision allow for sub-single-color error values
347 int* quantPixels
= cast(int*)malloc(int.sizeof
*numPixels
*4);
348 if (quantPixels
is null) assert(0, "out of memory");
349 scope(exit
) free(quantPixels
);
351 for (int ii
= 0; ii
< numPixels
*4; ++ii
) {
352 ubyte pix
= nextFrame
[ii
];
353 int pix16
= int(pix
)*256;
354 quantPixels
[ii
] = pix16
;
357 for (uint yy
= 0; yy
< height
; ++yy
) {
358 for (uint xx
= 0; xx
< width
; ++xx
) {
359 int* nextPix
= quantPixels
+4*(yy
*width
+xx
);
360 const(ubyte)* lastPix
= (lastFrame ? lastFrame
+4*(yy
*width
+xx
) : null);
362 // Compute the colors we want (rounding to nearest)
363 int rr
= (nextPix
[0]+127)/256;
364 int gg
= (nextPix
[1]+127)/256;
365 int bb
= (nextPix
[2]+127)/256;
367 // if it happens that we want the color from last frame, then just write out a transparent pixel
368 if (lastFrame
&& lastPix
[0] == rr
&& lastPix
[1] == gg
&& lastPix
[2] == bb
) {
372 nextPix
[3] = kGifTransIndex
;
376 int bestDiff
= 1000000;
377 int bestInd
= kGifTransIndex
;
380 gifGetClosestPaletteColor(pPal
, rr
, gg
, bb
, bestInd
, bestDiff
);
382 // Write the result to the temp buffer
383 int r_err
= nextPix
[0]-cast(int)(pPal
.r
.ptr
[bestInd
])*256;
384 int g_err
= nextPix
[1]-cast(int)(pPal
.g
.ptr
[bestInd
])*256;
385 int b_err
= nextPix
[2]-cast(int)(pPal
.b
.ptr
[bestInd
])*256;
387 nextPix
[0] = pPal
.r
.ptr
[bestInd
];
388 nextPix
[1] = pPal
.g
.ptr
[bestInd
];
389 nextPix
[2] = pPal
.b
.ptr
[bestInd
];
390 nextPix
[3] = bestInd
;
392 // Propagate the error to the four adjacent locations
393 // that we haven't touched yet
394 int quantloc_7
= (yy
*width
+xx
+1);
395 int quantloc_3
= (yy
*width
+width
+xx
-1);
396 int quantloc_5
= (yy
*width
+width
+xx
);
397 int quantloc_1
= (yy
*width
+width
+xx
+1);
399 if (quantloc_7
< numPixels
) {
400 int* pix7
= quantPixels
+4*quantloc_7
;
401 pix7
[0] += gifIMax(-pix7
[0], r_err
*7/16);
402 pix7
[1] += gifIMax(-pix7
[1], g_err
*7/16);
403 pix7
[2] += gifIMax(-pix7
[2], b_err
*7/16);
406 if (quantloc_3
< numPixels
) {
407 int* pix3
= quantPixels
+4*quantloc_3
;
408 pix3
[0] += gifIMax(-pix3
[0], r_err
*3/16);
409 pix3
[1] += gifIMax(-pix3
[1], g_err
*3/16);
410 pix3
[2] += gifIMax(-pix3
[2], b_err
*3/16);
413 if (quantloc_5
< numPixels
) {
414 int* pix5
= quantPixels
+4*quantloc_5
;
415 pix5
[0] += gifIMax(-pix5
[0], r_err
*5/16);
416 pix5
[1] += gifIMax(-pix5
[1], g_err
*5/16);
417 pix5
[2] += gifIMax(-pix5
[2], b_err
*5/16);
420 if (quantloc_1
< numPixels
) {
421 int* pix1
= quantPixels
+4*quantloc_1
;
422 pix1
[0] += gifIMax(-pix1
[0], r_err
/16);
423 pix1
[1] += gifIMax(-pix1
[1], g_err
/16);
424 pix1
[2] += gifIMax(-pix1
[2], b_err
/16);
429 // Copy the palettized result to the output buffer
430 for (int ii
= 0; ii
< numPixels
*4; ++ii
) outFrame
[ii
] = cast(ubyte)quantPixels
[ii
];
431 //outFrame[0..numPixels*4] = quantPixels[0..numPixels*4];
435 // Picks palette colors for the image using simple thresholding, no dithering
436 void gifThresholdImage (const(ubyte)* lastFrame
, const(ubyte)* nextFrame
, ubyte* outFrame
, uint width
, uint height
, GifPalette
* pPal
) {
437 uint numPixels
= width
*height
;
438 for (uint ii
= 0; ii
< numPixels
; ++ii
) {
439 // if a previous color is available, and it matches the current color, set the pixel to transparent
440 if (lastFrame
&& lastFrame
[0] == nextFrame
[0] && lastFrame
[1] == nextFrame
[1] && lastFrame
[2] == nextFrame
[2]) {
441 outFrame
[0] = lastFrame
[0];
442 outFrame
[1] = lastFrame
[1];
443 outFrame
[2] = lastFrame
[2];
444 outFrame
[3] = kGifTransIndex
;
446 // palettize the pixel
447 int bestDiff
= 1000000;
449 gifGetClosestPaletteColor(pPal
, nextFrame
[0], nextFrame
[1], nextFrame
[2], bestInd
, bestDiff
);
450 // Write the resulting color to the output buffer
451 outFrame
[0] = pPal
.r
.ptr
[bestInd
];
452 outFrame
[1] = pPal
.g
.ptr
[bestInd
];
453 outFrame
[2] = pPal
.b
.ptr
[bestInd
];
454 outFrame
[3] = cast(ubyte)bestInd
;
456 if (lastFrame
) lastFrame
+= 4;
463 // Simple structure to write out the LZW-compressed portion of the image
465 struct gifBitStatus
{
466 ubyte bitIndex
; // how many bits in the partial byte written so far
467 ubyte bytev
; // current partial byte
469 ubyte[256] chunk
; // bytes are written in here until we have 256 of them, then written to the file
473 // insert a single bit
474 void gifWriteBit (ref gifBitStatus stat
, uint bit
) {
476 bit
= bit
<<stat
.bitIndex
;
479 if (stat
.bitIndex
> 7) {
480 // move the newly-finished byte to the chunk buffer
481 stat
.chunk
[stat
.chunkIndex
++] = stat
.bytev
;
482 // and start a new byte
489 // write all bytes so far to the file
490 void gifWriteChunk (GifWriter
.WriterCB wr
, ref gifBitStatus stat
) {
491 ubyte[1] b
= cast(ubyte)stat
.chunkIndex
;
493 wr(stat
.chunk
[0..stat
.chunkIndex
]);
500 void gifWriteCode (GifWriter
.WriterCB wr
, ref gifBitStatus stat
, uint code
, uint length
) {
501 for (uint ii
= 0; ii
< length
; ++ii
) {
502 gifWriteBit(stat
, code
);
504 if (stat
.chunkIndex
== 255) gifWriteChunk(wr
, stat
);
509 // The LZW dictionary is a 256-ary tree constructed as the file is encoded, this is one node
515 // write a 256-color (8-bit) image palette to the file
516 void gifWritePalette (const(GifPalette
)* pPal
, GifWriter
.WriterCB wr
) {
517 // first color: transparency
520 assert(pPal
.bitDepth
<= 8);
521 for (ii
= 1; ii
< (1<<pPal
.bitDepth
); ++ii
) {
522 b
.ptr
[ii
*3+0] = cast(ubyte)pPal
.r
.ptr
[ii
];
523 b
.ptr
[ii
*3+1] = cast(ubyte)pPal
.g
.ptr
[ii
];
524 b
.ptr
[ii
*3+2] = cast(ubyte)pPal
.b
.ptr
[ii
];
530 // write the image header, LZW-compress and write out the image
531 void gifWriteLzwImage (GifWriter
.WriterCB wr
, gifLzwNode
* codetree
, ubyte* image
, uint left
, uint top
, uint width
, uint height
, uint delay
, GifPalette
* pPal
) {
532 //import core.stdc.stdlib : malloc, free;
533 import core
.stdc
.string
: memset
;
535 void writeByte (ubyte b
) { wr((&b
)[0..1]); }
537 // graphics control extension
538 writeByte(cast(ubyte)(0x21));
539 writeByte(cast(ubyte)(0xf9));
540 writeByte(cast(ubyte)(0x04));
541 writeByte(cast(ubyte)(0x05)); // leave prev frame in place, this frame has transparency
542 writeByte(cast(ubyte)(delay
&0xff));
543 writeByte(cast(ubyte)((delay
>>8)&0xff));
544 writeByte(cast(ubyte)(kGifTransIndex
)); // transparent color index
545 writeByte(cast(ubyte)(0));
547 writeByte(cast(ubyte)(0x2c)); // image descriptor block
549 writeByte(cast(ubyte)(left
&0xff)); // corner of image in canvas space
550 writeByte(cast(ubyte)((left
>>8)&0xff));
551 writeByte(cast(ubyte)(top
&0xff));
552 writeByte(cast(ubyte)((top
>>8)&0xff));
554 writeByte(cast(ubyte)(width
&0xff)); // width and height of image
555 writeByte(cast(ubyte)((width
>>8)&0xff));
556 writeByte(cast(ubyte)(height
&0xff));
557 writeByte(cast(ubyte)((height
>>8)&0xff));
559 //writeByte(cast(ubyte)(0)); // no local color table, no transparency
560 //writeByte(cast(ubyte)(0x80)); // no local color table, but transparency
562 writeByte(cast(ubyte)(0x80+pPal
.bitDepth
-1)); // local color table present, 2^bitDepth entries
563 gifWritePalette(pPal
, wr
);
565 immutable int minCodeSize
= pPal
.bitDepth
;
566 immutable uint clearCode
= 1<<pPal
.bitDepth
;
568 writeByte(cast(ubyte)(minCodeSize
)); // min code size 8 bits
570 //gifLzwNode* codetree = cast(gifLzwNode*)malloc(gifLzwNode.sizeof*4096);
571 //if (codetree is null) assert(0, "out of memory");
572 //scope(exit) free(codetree);
574 memset(codetree
, 0, gifLzwNode
.sizeof
*4096);
576 uint codeSize
= minCodeSize
+1;
577 uint maxCode
= clearCode
+1;
584 gifWriteCode(wr
, stat
, clearCode
, codeSize
); // start with a fresh LZW dictionary
586 bool fixCodeSize () {
587 if (maxCode
>= (1U<<codeSize
)) {
588 // dictionary entry count has broken a size barrier, we need more bits for codes
591 if (maxCode
== 4095) {
592 // the dictionary is full, clear it out and begin anew
593 gifWriteCode(wr
, stat
, clearCode
, codeSize
); // clear tree
594 memset(codetree
, 0, gifLzwNode
.sizeof
*4096);
596 codeSize
= minCodeSize
+1;
597 maxCode
= clearCode
+1;
603 for (uint yy
= 0; yy
< height
; ++yy
) {
604 for (uint xx
= 0; xx
< width
; ++xx
) {
605 ubyte nextValue
= image
[(yy
*width
+xx
)*4+3];
607 // "loser mode" - no compression, every single code is followed immediately by a clear
608 //WriteCode( f, stat, nextValue, codeSize );
609 //WriteCode( f, stat, 256, codeSize );
612 // first value in a new run
614 } else if (codetree
[curCode
].m_next
[nextValue
]) {
615 // current run already in the dictionary
616 curCode
= codetree
[curCode
].m_next
[nextValue
];
618 // finish the current run, write a code
619 gifWriteCode(wr
, stat
, curCode
, codeSize
);
620 // insert the new run into the dictionary
621 codetree
[curCode
].m_next
[nextValue
] = cast(ushort)(++maxCode
);
628 // compression footer
629 gifWriteCode(wr
, stat
, curCode
, codeSize
);
631 if (!fixCodeSize()) gifWriteCode(wr
, stat
, clearCode
, codeSize
);
632 gifWriteCode(wr
, stat
, clearCode
+1, minCodeSize
+1);
634 // write out the last partial chunk
635 while (stat
.bitIndex
) gifWriteBit(stat
, 0);
636 if (stat
.chunkIndex
) gifWriteChunk(wr
, stat
);
638 writeByte(cast(ubyte)(0)); // image block terminator
642 // ////////////////////////////////////////////////////////////////////////// //
644 public final class GifWriter
{
646 alias WriterCB
= void delegate (const(ubyte)[] buf
); /// buffer writer
658 gifLzwNode
[4096] codetree
;
662 /** Creates a gif writer.
664 * The delay value is the time between frames in hundredths of a second.
665 * Note that not all viewers pay much attention to this value.
668 * Create a GifWriter class. Pass subsequent frames to writeFrame().
669 * Finally, call finish() to close the file handle and free memory.
672 * writeBytesCB = file write delegate; should write the whole buffer or throw; will never be called with zero-length buffer
673 * width = maximum picture width
674 * height = maximum picture height
675 * delay = delay between frames, in 1/100 of second
676 * bitDepth = resulting image bit depth; [1..8]
677 * dither = dither resulting image (image may or may not look better when dithered ;-)
679 this (WriterCB writeBytesCB
, uint width
, uint height
, uint delay
, ubyte bitDepth
=8, bool dither
=false) {
680 if (writeBytesCB
is null) throw new Exception("no write delegate");
681 if (width
< 1 || height
< 1 || width
> 16383 || height
> 16383) throw new Exception("invalid dimensions");
682 if (bitDepth
< 1 || bitDepth
> 8) throw new Exception("invalid bit depth");
683 writeBytes
= writeBytesCB
;
684 origBitDepth
= bitDepth
;
689 scope(failure
) errored
= true;
690 setup(width
, height
, delay
, bitDepth
, dither
);
693 /** Writes out a new frame to a GIF in progress.
696 * image = frame RGBA data, width*height*4 bytes
697 * delay = delay between frames, in 1/100 of second
698 * width = frame width
699 * height = frame height
701 void writeFrame (const(void)[] image
, uint delay
=uint.max
, uint width
=0, uint height
=0) {
702 if (errored
) throw new Exception("error writing gif data");
703 if (finished
) throw new Exception("can't add frame to finished gif");
704 if (width
== 0) width
= origW
;
705 if (height
== 0) height
= origH
;
706 if (delay
== uint.max
) delay
= origDelay
;
707 if (image
.length
< width
*height
*4) throw new Exception("image buffer too small");
708 scope(failure
) errored
= true;
709 const(ubyte)* oldImg
= (firstFrame ?
null : oldImage
.ptr
);
711 gifMakePalette((origDither ?
null : oldImg
), cast(const(ubyte)*)image
.ptr
, width
, height
, origBitDepth
, origDither
, &pal
);
713 gifDitherImage(oldImg
, cast(const(ubyte)*)image
.ptr
, oldImage
.ptr
, width
, height
, &pal
);
715 gifThresholdImage(oldImg
, cast(const(ubyte)*)image
.ptr
, oldImage
.ptr
, width
, height
, &pal
);
717 gifWriteLzwImage(writeBytes
, codetree
.ptr
, oldImage
.ptr
, 0, 0, width
, height
, delay
, &pal
);
720 static if (GifWriterHasArsdColor
) {
721 /** Writes out a new frame to a GIF in progress.
724 * mimage = frame data, width*height pixels
725 * delay = delay between frames, in 1/100 of second
727 void writeFrame() (MemoryImage mimage
, uint delay
=uint.max
) {
728 if (errored
) throw new Exception("error writing gif data");
729 if (finished
) throw new Exception("can't add frame to finished gif");
730 if (mimage
is null || mimage
.width
< 1 || mimage
.height
< 1) return;
731 if (mimage
.width
!= origW || mimage
.height
!= origH
) throw new Exception("invalid image dimensions");
732 if (delay
== uint.max
) delay
= origDelay
;
733 scope(failure
) errored
= true;
734 auto tcimg
= mimage
.getAsTrueColorImage();
735 writeFrame(tcimg
.imageData
.bytes
, delay
, 0, 0);
739 /** Writes the EOF code.
741 * Many if not most viewers will still display a GIF properly if the EOF code is missing,
742 * but it's still a good idea to write it out.
745 if (errored
) throw new Exception("error writing gif data");
746 if (finished
) throw new Exception("can't finish finished gif");
747 scope(failure
) errored
= true;
748 writeByte(0x3b); // end of file
753 /** Flips image data vertically.
755 * This can be used to flip result of `glReadPixels()`, for example.
758 * img = frame RGBA data, width*height*4 bytes
759 * width = frame width
760 * height = frame height
762 static void flipY (void[] img
, uint width
, uint height
) {
763 if (width
< 1 || height
< 1) return;
764 if (img
.length
< width
*height
*4) throw new Exception("image buffer too small");
766 uint dpos
= (height
-1)*(width
*4);
767 auto image
= cast(ubyte*)img
.ptr
;
768 foreach (immutable y
; 0..height
/2) {
769 foreach (immutable x
; 0..width
*4) {
770 ubyte t
= image
[spos
+x
];
771 image
[spos
+x
] = image
[dpos
+x
];
780 void writeByte (ubyte b
) { writeBytes((&b
)[0..1]); }
781 void writeBuf (const(void)[] buf
) { if (buf
.length
) writeBytes(cast(const(ubyte)[])buf
); }
783 void setup (uint width
, uint height
, uint delay
, ubyte bitDepth
, bool dither
) {
787 oldImage
.length
= width
*height
*4;
792 writeByte(cast(ubyte)(width
&0xff));
793 writeByte(cast(ubyte)((width
>>8)&0xff));
794 writeByte(cast(ubyte)(height
&0xff));
795 writeByte(cast(ubyte)((height
>>8)&0xff));
797 writeByte(cast(ubyte)(0xf0)); // there is an unsorted global color table of 2 entries
798 writeByte(cast(ubyte)(0)); // background color
799 writeByte(cast(ubyte)(0)); // pixels are square (we need to specify this because it's 1989)
801 // now the "global" palette (really just a dummy palette)
803 writeByte(cast(ubyte)(0));
804 writeByte(cast(ubyte)(0));
805 writeByte(cast(ubyte)(0));
806 // color 1: also black
807 writeByte(cast(ubyte)(0));
808 writeByte(cast(ubyte)(0));
809 writeByte(cast(ubyte)(0));
813 writeByte(cast(ubyte)(0x21)); // extension
814 writeByte(cast(ubyte)(0xff)); // application specific
815 writeByte(cast(ubyte)(11)); // length 11
816 writeBuf("NETSCAPE2.0"); // yes, really
817 writeByte(cast(ubyte)(3)); // 3 bytes of NETSCAPE2.0 data
819 writeByte(cast(ubyte)(1)); // JUST BECAUSE
820 writeByte(cast(ubyte)(0)); // loop infinitely (byte 0)
821 writeByte(cast(ubyte)(0)); // loop infinitely (byte 1)
823 writeByte(cast(ubyte)(0)); // block terminator