egra: do not record cells outside of the vertical canvas (framebuffer) in agg mini...
[iv.d.git] / gifwriter.d
blob168afa740ebb764545f12183882bcad0bad58e90
1 // based on gif.h by Charlie Tangora
2 // Public domain.
3 // Email me : ctangora -at- gmail -dot- com
4 //
5 // This file offers a simple, very limited way to create animated GIFs directly in code.
6 //
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
10 // frame are saved.)
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.)
16 /**
17 * Example:
18 * ---
19 * import std.stdio;
20 * import arsd.color;
21 * import iv.gifwriter;
22 * void main () {
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);
26 * gw.writeFrame(tc);
27 * foreach (immutable n; 0..tc.width) {
28 * tc.setPixel(n, 0, Color.red);
29 * gw.writeFrame(tc);
30 * }
31 * foreach (immutable n; 1..tc.height) {
32 * tc.setPixel(tc.width-1, n, Color.green);
33 * gw.writeFrame(tc);
34 * }
35 * foreach_reverse (immutable n; 0..tc.width-1) {
36 * tc.setPixel(n, tc.height-1, Color.blue);
37 * gw.writeFrame(tc);
38 * }
39 * foreach_reverse (immutable n; 0..tc.height-1) {
40 * tc.setPixel(0, n, Color.white);
41 * gw.writeFrame(tc);
42 * }
43 * gw.finish();
44 * }
45 * ---
47 module iv.gifwriter /*is aliced*/;
48 private:
49 import iv.alice;
51 // ////////////////////////////////////////////////////////////////////////// //
52 static if (__traits(compiles, (){import arsd.color;})) {
53 import arsd.color;
54 enum GifWriterHasArsdColor = true;
55 } else {
56 enum GifWriterHasArsdColor = false;
60 // ////////////////////////////////////////////////////////////////////////// //
61 enum kGifTransIndex = 0;
63 struct GifPalette {
64 ubyte bitDepth;
66 ubyte[256] r;
67 ubyte[256] g;
68 ubyte[256] b;
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;
74 ubyte[256] treeSplit;
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) {
97 bestInd = ind;
98 bestDiff = diff;
100 return;
102 // take the appropriate color (r, g, or b) for this node of the k-d tree
103 int[3] comps = void;
104 comps[0] = r;
105 comps[1] = g;
106 comps[2] = b;
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);
117 } else {
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];
137 image[pixA*4] = rB;
138 image[pixA*4+1] = gB;
139 image[pixA*4+2] = bB;
140 image[pixA*4+3] = aB;
142 image[pixB*4] = rA;
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;
154 bool split = 0;
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);
159 ++storeIndex;
160 } else if (arrayVal == pivotValue) {
161 if (split) {
162 gifSwapPixels(image, ii, storeIndex);
163 ++storeIndex;
165 split = !split;
168 gifSwapPixels(image, storeIndex, right-1);
169 return storeIndex;
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
193 if (firstElt == 1) {
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;
204 return;
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;
217 return;
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) {
224 r += image[ii*4+0];
225 g += image[ii*4+1];
226 b += image[ii*4+2];
229 r += numPixels/2; // round to nearest
230 g += numPixels/2;
231 b += numPixels/2;
233 r /= numPixels;
234 g /= numPixels;
235 b /= numPixels;
237 pal.r.ptr[firstElt] = cast(ubyte)r;
238 pal.g.ptr[firstElt] = cast(ubyte)g;
239 pal.b.ptr[firstElt] = cast(ubyte)b;
241 return;
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)
265 int splitCom = 1;
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) {
287 int numChanged = 0;
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];
294 ++numChanged;
295 writeIter += 4;
297 lastFrame += 4;
298 frame += 4;
300 return numChanged;
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
346 // to be propagated
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) {
369 nextPix[0] = rr;
370 nextPix[1] = gg;
371 nextPix[2] = bb;
372 nextPix[3] = kGifTransIndex;
373 continue;
376 int bestDiff = 1000000;
377 int bestInd = kGifTransIndex;
379 // Search the palete
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;
445 } else {
446 // palettize the pixel
447 int bestDiff = 1000000;
448 int bestInd = 1;
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;
457 outFrame += 4;
458 nextFrame += 4;
463 // Simple structure to write out the LZW-compressed portion of the image
464 // one bit at a time
465 struct gifBitStatus {
466 ubyte bitIndex; // how many bits in the partial byte written so far
467 ubyte bytev; // current partial byte
468 uint chunkIndex;
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) {
475 bit = bit&1;
476 bit = bit<<stat.bitIndex;
477 stat.bytev |= bit;
478 ++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
483 stat.bitIndex = 0;
484 stat.bytev = 0;
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;
492 wr(b[]);
493 wr(stat.chunk[0..stat.chunkIndex]);
494 stat.bitIndex = 0;
495 stat.bytev = 0;
496 stat.chunkIndex = 0;
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);
503 code = code>>1;
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
510 struct gifLzwNode {
511 ushort[256] m_next;
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
518 int ii;
519 ubyte[256*3] b = 0;
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];
526 wr(b[0..ii*3]);
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);
575 int curCode = -1;
576 uint codeSize = minCodeSize+1;
577 uint maxCode = clearCode+1;
579 gifBitStatus stat;
580 stat.bytev = 0;
581 stat.bitIndex = 0;
582 stat.chunkIndex = 0;
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
589 ++codeSize;
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);
595 curCode = -1;
596 codeSize = minCodeSize+1;
597 maxCode = clearCode+1;
598 return true;
600 return false;
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 );
611 if (curCode < 0) {
612 // first value in a new run
613 curCode = nextValue;
614 } else if (codetree[curCode].m_next[nextValue]) {
615 // current run already in the dictionary
616 curCode = codetree[curCode].m_next[nextValue];
617 } else {
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);
622 fixCodeSize();
623 curCode = nextValue;
628 // compression footer
629 gifWriteCode(wr, stat, curCode, codeSize);
630 ++maxCode;
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 // ////////////////////////////////////////////////////////////////////////// //
643 /// gif writer class
644 public final class GifWriter {
645 public:
646 alias WriterCB = void delegate (const(ubyte)[] buf); /// buffer writer
648 private:
649 WriterCB writeBytes;
650 ubyte[] oldImage;
651 bool firstFrame;
652 bool errored;
653 bool finished;
654 ubyte origBitDepth;
655 bool origDither;
656 uint origW, origH;
657 uint origDelay;
658 gifLzwNode[4096] codetree;
659 GifPalette pal;
661 public:
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.
667 * USAGE:
668 * Create a GifWriter class. Pass subsequent frames to writeFrame().
669 * Finally, call finish() to close the file handle and free memory.
671 * Params:
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;
685 origDither = dither;
686 origW = width;
687 origH = height;
688 origDelay = delay;
689 scope(failure) errored = true;
690 setup(width, height, delay, bitDepth, dither);
693 /** Writes out a new frame to a GIF in progress.
695 * Params:
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);
710 firstFrame = false;
711 gifMakePalette((origDither ? null : oldImg), cast(const(ubyte)*)image.ptr, width, height, origBitDepth, origDither, &pal);
712 if (origDither) {
713 gifDitherImage(oldImg, cast(const(ubyte)*)image.ptr, oldImage.ptr, width, height, &pal);
714 } else {
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.
723 * Params:
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.
744 void finish () {
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
749 oldImage = null;
750 finished = true;
753 /** Flips image data vertically.
755 * This can be used to flip result of `glReadPixels()`, for example.
757 * Params:
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");
765 uint spos = 0;
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];
772 image[dpos+x] = t;
774 spos += width*4;
775 dpos -= width*4;
779 private:
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) {
784 firstFrame = true;
786 // allocate
787 oldImage.length = width*height*4;
789 writeBuf("GIF89a");
791 // screen descriptor
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)
802 // color 0: black
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));
811 if (delay != 0) {
812 // animation header
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