cosmetix
[iv.d.git] / zmbv.d
blob7b148e1faff5425db2326530b0b2fc82bf56ae2a
1 /*
2 * Copyright (C) 2002-2013 The DOSBox Team
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 * D translation by Ketmar // Invisible Vector
19 // ZMBV video codec
20 module iv.zmbv /*is aliced*/;
23 // ////////////////////////////////////////////////////////////////////////// //
24 import iv.alice;
25 import iv.exex;
27 mixin(MyException!"ZMBVError");
30 // ////////////////////////////////////////////////////////////////////////// //
31 class Codec {
32 public:
33 enum Format {
34 None = 0x00,
35 //bpp1 = 0x01,
36 //bpp2 = 0x02,
37 //bpp4 = 0x03,
38 bpp8 = 0x04,
39 bpp15 = 0x05,
40 bpp16 = 0x06,
41 //bpp24 = 0x07,
42 bpp32 = 0x08
45 // ZMBV compression
46 enum Compression {
47 None = 0,
48 ZLib = 1
52 // returns Format.None for unknown bpp
53 static Format bpp2format(T) (T bpp) @safe pure nothrow @nogc if (__traits(isIntegral, T)) {
54 switch (bpp) {
55 case 8: return Format.bpp8;
56 case 15: return Format.bpp15;
57 case 16: return Format.bpp16;
58 case 32: return Format.bpp32;
59 default: return Format.None;
61 assert(0);
64 // returns 0 for unknown format
65 static ubyte format2bpp (Format fmt) @safe pure nothrow @nogc {
66 switch (fmt) {
67 case Format.bpp8: return 8;
68 case Format.bpp15: return 15;
69 case Format.bpp16: return 16;
70 case Format.bpp32: return 32;
71 default: return 0;
73 assert(0);
76 // returns 0 for unknown format
77 static ubyte format2pixelSize (Format fmt) @safe pure nothrow @nogc {
78 switch (fmt) {
79 case Format.bpp8: return 1;
80 case Format.bpp15: case Format.bpp16: return 2;
81 case Format.bpp32: return 4;
82 default: return 0;
84 assert(0);
87 static bool isValidFormat (Format fmt) @safe pure nothrow @nogc {
88 switch (fmt) {
89 case Format.bpp8:
90 case Format.bpp15:
91 case Format.bpp16:
92 case Format.bpp32:
93 return true;
94 default:
95 return false;
97 assert(0);
100 private:
101 import etc.c.zlib : z_stream;
103 enum MaxVector = 16;
105 // ZMBV format version
106 enum Version {
107 High = 0,
108 Low = 1
111 enum FrameMask {
112 Keyframe = 0x01,
113 DeltaPalette = 0x02
116 struct FrameBlock {
117 int start;
118 int dx, dy;
121 align(1) struct KeyframeHeader {
122 align(1):
123 ubyte versionHi;
124 ubyte versionLo;
125 ubyte compression;
126 ubyte format;
127 ubyte blockWidth;
128 ubyte blockHeight;
131 Compression mCompression = Compression.ZLib;
133 ubyte* oldFrame, newFrame;
134 ubyte* buf1, buf2, work;
135 usize bufSize;
136 usize workUsed;
138 usize blockCount;
139 FrameBlock* blocks;
141 ushort palSize;
142 ubyte[256*3] mPalette;
144 uint mHeight, mWidth, mPitch;
146 Format mFormat;
147 uint pixelSize;
149 z_stream zstream;
150 bool zstreamInited;
152 // used in encoder only, but moved here for freeBuffers()
153 usize outbufSize;
154 ubyte* outbuf;
156 public:
157 this (uint width, uint height) @trusted {
158 if (width < 1 || height < 1 || width > 32768 || height > 32768) throw new ZMBVError("invalid ZMBV dimensions");
159 mWidth = width;
160 mHeight = height;
161 mPitch = mWidth+2*MaxVector;
164 ~this () @safe nothrow /*@nogc*/ {
165 clear();
168 // release all allocated memory, finish zstream, etc
169 final void clear () @trusted nothrow /*@nogc*/ {
170 zlibDeinit();
171 freeBuffers();
174 protected abstract void zlibDeinit () @trusted nothrow /*@nogc*/;
176 final:
177 @property uint width () const @safe pure nothrow @nogc { return mWidth; }
178 @property uint height () const @safe pure nothrow @nogc { return mHeight; }
179 @property Format format () const @safe pure nothrow @nogc { return mFormat; }
181 protected:
182 void freeBuffers () @trusted nothrow @nogc {
183 import core.stdc.stdlib : free;
184 if (outbuf !is null) free(outbuf);
185 if (blocks !is null) free(blocks);
186 if (buf1 !is null) free(buf1);
187 if (buf2 !is null) free(buf2);
188 if (work !is null) free(work);
189 outbuf = null;
190 outbufSize = 0;
191 blocks = null;
192 buf1 = null;
193 buf2 = null;
194 work = null;
197 void setupBuffers (Format fmt, uint blockWidth, uint blockHeight) @trusted {
198 import core.stdc.stdlib : malloc;
199 import std.exception : enforce;
200 freeBuffers();
201 pixelSize = format2pixelSize(fmt);
202 if (pixelSize == 0) throw new ZMBVError("invalid ZMBV format");
203 if (blockWidth < 1 || blockHeight < 1) throw new ZMBVError("invalid ZMBV block size");
204 palSize = (fmt == Format.bpp8 ? 256 : 0);
205 bufSize = (mHeight+2*MaxVector)*mPitch*pixelSize+2048;
206 buf1 = cast(ubyte*)malloc(bufSize);
207 buf2 = cast(ubyte*)malloc(bufSize);
208 work = cast(ubyte*)malloc(bufSize);
209 scope(failure) freeBuffers();
210 enforce(buf1 !is null && buf2 !is null && work !is null);
212 uint xblocks = (mWidth/blockWidth);
213 uint xleft = mWidth%blockWidth;
214 if (xleft) ++xblocks;
216 uint yblocks = (mHeight/blockHeight);
217 uint yleft = mHeight%blockHeight;
218 if (yleft) ++yblocks;
220 blockCount = yblocks*xblocks;
221 blocks = cast(FrameBlock*)malloc(FrameBlock.sizeof*blockCount);
222 enforce(blocks !is null);
224 uint i = 0;
225 foreach (immutable y; 0..yblocks) {
226 foreach (immutable x; 0..xblocks) {
227 blocks[i].start = ((y*blockHeight)+MaxVector)*mPitch+(x*blockWidth)+MaxVector;
228 blocks[i].dx = (xleft && x == xblocks-1 ? xleft : blockWidth);
229 blocks[i].dy = (yleft && y == yblocks-1 ? yleft : blockHeight);
230 ++i;
234 buf1[0..bufSize] = 0;
235 buf2[0..bufSize] = 0;
236 work[0..bufSize] = 0;
237 oldFrame = buf1;
238 newFrame = buf2;
239 mFormat = fmt;
243 // swap old frame and new frame
244 void swapFrames () @safe nothrow @nogc {
245 auto copyFrame = newFrame;
246 newFrame = oldFrame;
247 oldFrame = copyFrame;
252 // ////////////////////////////////////////////////////////////////////////// //
253 class Encoder : Codec {
254 private:
255 static struct CodecVector {
256 int x, y;
258 CodecVector[441] vectorTable = void;
260 ubyte mCompressionLevel;
261 ulong frameCount;
262 usize linesDone;
263 usize writeDone;
265 public:
266 enum PrepareFlags {
267 None = 0,
268 Keyframe = 1
271 this (uint width, uint height, Format fmt=Format.None, int complevel=-1, Compression ctype=Compression.ZLib) @trusted {
272 super(width, height);
273 if (fmt != Format.None && !isValidFormat(fmt)) throw new ZMBVError("invalid ZMBV format");
274 if (ctype != Compression.None && ctype != Compression.ZLib) throw new ZMBVError("invalid ZMBV compression");
275 if (ctype == Compression.ZLib) {
276 if (complevel < 0) complevel = 4;
277 if (complevel > 9) complevel = 9;
278 } else {
279 complevel = 0;
281 mCompression = ctype;
282 mCompressionLevel = cast(ubyte)complevel;
283 mFormat = fmt;
284 createVectorTable();
285 frameCount = 0;
286 if (mCompression == Compression.ZLib) {
287 import etc.c.zlib : deflateInit, Z_OK;
288 if (deflateInit(&zstream, complevel) != Z_OK) throw new ZMBVError("can't initialize ZLib stream");
289 zstreamInited = true;
291 // allocate buffer here if we know the format
292 if (fmt != Format.None) fixOutBuffer();
295 protected override void zlibDeinit () @trusted nothrow /*@nogc*/ {
296 if (zstreamInited) {
297 import etc.c.zlib : deflateEnd;
298 try deflateEnd(&zstream); catch (Exception) {}
299 zstreamInited = false;
301 frameCount = 0; // do it here as we have no other hooks in clear()
304 // (re)allocate buffer for compressed data
305 private final fixOutBuffer () @trusted {
306 usize nbs = workBufferSize();
307 if (nbs == 0) throw new ZMBVError("internal error");
308 if (nbs > outbufSize) {
309 import core.stdc.stdlib : realloc;
310 // we need bigger buffer
311 void* nb = realloc(outbuf, nbs);
312 if (nb is null) throw new ZMBVError("out of memory for compression buffer");
313 outbufSize = nbs;
314 outbuf = cast(ubyte*)nb;
318 final:
319 @property Compression compression () const @safe pure nothrow @nogc { return mCompression; }
320 @property ubyte compressionLevel () const @safe pure nothrow @nogc { return mCompressionLevel; }
321 @property ulong processedFrames () const @safe pure nothrow @nogc { return frameCount; }
323 void prepareFrame (PrepareFlags flags, const(ubyte)[] pal=null, Format fmt=Format.None) @trusted {
324 import std.algorithm : min;
326 if (flags != PrepareFlags.None && flags != PrepareFlags.Keyframe) throw new ZMBVError("invalid flags");
327 if (fmt == Format.None) {
328 if (mFormat == Format.None) throw new ZMBVError("invalid format");
329 fmt = mFormat;
331 if (!isValidFormat(fmt)) throw new ZMBVError("invalid format");
333 if (fmt != mFormat || frameCount == 0) {
334 mFormat = fmt;
335 setupBuffers(fmt, 16, 16);
336 flags = PrepareFlags.Keyframe; // force a keyframe
339 swapFrames();
341 // (re)allocate buffer for compressed data
342 fixOutBuffer();
343 linesDone = 0;
344 writeDone = 1; // for firstByte
346 // set a pointer to the first byte which will contain info about this frame
347 ubyte* firstByte = outbuf;
348 *firstByte = 0; // frame flags
350 // reset the work buffer
351 workUsed = 0;
352 if (flags == PrepareFlags.Keyframe) {
353 // make a keyframe
354 *firstByte |= FrameMask.Keyframe;
355 auto header = cast(KeyframeHeader*)(outbuf+writeDone);
356 header.versionHi = Version.High;
357 header.versionLo = Version.Low;
358 header.compression = cast(byte)mCompression;
359 header.format = cast(ubyte)mFormat;
360 header.blockWidth = 16;
361 header.blockHeight = 16;
362 writeDone += KeyframeHeader.sizeof;
363 if (palSize) {
364 if (pal.length) {
365 immutable usize end = min(pal.length, mPalette.length);
366 mPalette[0..end] = pal[0..end];
367 if (end < mPalette.length) mPalette[end..$] = 0;
368 } else {
369 mPalette[] = 0;
371 // keyframes get the full palette
372 work[0..palSize*3] = mPalette[];
373 workUsed += palSize*3;
375 // restart deflate
376 if (mCompression == Compression.ZLib) {
377 import etc.c.zlib : deflateReset, Z_OK;
378 if (deflateReset(&zstream) != Z_OK) throw new ZMBVError("can't restart deflate stream");
380 } else {
381 if (palSize && pal.length) {
382 immutable usize end = min(pal.length, mPalette.length);
383 if (pal != mPalette) {
384 *firstByte |= FrameMask.DeltaPalette;
385 foreach (immutable i; 0..end) work[workUsed++] = mPalette[i]^pal[i];
386 mPalette[0..end] = pal[0..end];
392 // return false when frame is full (and line is not encoded)
393 void encodeLine (const(void)[] line) @trusted {
394 if (linesDone >= mHeight) throw new ZMBVError("too many lines");
395 immutable uint lineWidth = mWidth*pixelSize;
396 if (line.length < lineWidth) throw new ZMBVError("line too short");
397 immutable usize ofs = pixelSize*(MaxVector+(linesDone+MaxVector)*mPitch);
398 newFrame[ofs..ofs+lineWidth] = (cast(const(ubyte*))line.ptr)[0..lineWidth];
399 ++linesDone;
402 // return array with frame data
403 const(void)[] finishFrame () @trusted {
404 if (linesDone != mHeight) throw new ZMBVError("can't finish incomplete frame");
405 ubyte firstByte = *outbuf;
406 if (firstByte&FrameMask.Keyframe) {
407 // add the full frame data
408 usize ofs = pixelSize*(MaxVector+MaxVector*mPitch);
409 immutable llen = mWidth*pixelSize;
410 foreach (immutable i; 0..mHeight) {
411 work[workUsed..workUsed+llen] = newFrame[ofs..ofs+llen];
412 ofs += mPitch*pixelSize;
413 workUsed += llen;
415 } else {
416 // add the delta frame data
417 switch (mFormat) {
418 case Format.bpp8: addXorFrame!byte(); break;
419 case Format.bpp15: case Format.bpp16: addXorFrame!short(); break;
420 case Format.bpp32: addXorFrame!int(); break;
421 default: throw new ZMBVError("internal error");
424 if (mCompression == Compression.ZLib) {
425 // create the actual frame with compression
426 import etc.c.zlib : deflate, Z_OK, Z_SYNC_FLUSH;
427 zstream.next_in = work;
428 zstream.avail_in = cast(uint)workUsed;
429 zstream.total_in = 0;
430 zstream.next_out = outbuf+writeDone;
431 zstream.avail_out = cast(uint)(outbufSize-writeDone);
432 zstream.total_out = 0;
433 if (deflate(&zstream, Z_SYNC_FLUSH) != Z_OK) throw new ZMBVError("deflate error"); // the thing that should not be
434 ++frameCount;
435 return (cast(const(void)*)outbuf)[0..writeDone+zstream.total_out];
436 } else {
437 outbuf[writeDone..writeDone+workUsed] = work[0..workUsed];
438 ++frameCount;
439 return (cast(const(void)*)outbuf)[0..workUsed+writeDone];
443 private:
444 usize workBufferSize () const @safe pure nothrow @nogc {
445 usize f = format2pixelSize(mFormat);
446 return (f ? f*mWidth*mHeight+2*(1+(mWidth/8))*(1+(mHeight/8))+1024 : 0);
449 void createVectorTable () @safe pure nothrow @nogc {
450 import std.math : abs;
451 vectorTable[0].x = vectorTable[0].y = 0;
452 usize vectorCount = 1;
453 foreach (immutable s; 1..11) {
454 foreach (immutable y; 0-s..0+s+1) {
455 foreach (immutable x; 0-s..0+s+1) {
456 if (abs(x) == s || abs(y) == s) {
457 vectorTable[vectorCount].x = x;
458 vectorTable[vectorCount].y = y;
459 ++vectorCount;
466 // encoder templates
467 int possibleBlock(P) (int vx, int vy, const(FrameBlock*) block) @trusted nothrow @nogc {
468 int ret = 0;
469 auto pold = (cast(const(P)*)oldFrame)+block.start+(vy*mPitch)+vx;
470 auto pnew = (cast(const(P)*)newFrame)+block.start;
471 for (usize y = 0; y < block.dy; y += 4) {
472 for (usize x = 0; x < block.dx; x += 4) {
473 int test = 0-((pold[x]-pnew[x])&0x00ffffff);
474 ret -= (test>>31); // 0 or -1
476 pold += mPitch*4;
477 pnew += mPitch*4;
479 return ret;
482 int compareBlock(P) (int vx, int vy, const(FrameBlock*) block) @trusted nothrow @nogc {
483 int ret = 0;
484 auto pold = (cast(const(P)*)oldFrame)+block.start+(vy*mPitch)+vx;
485 auto pnew = (cast(const(P)*)newFrame)+block.start;
486 foreach (immutable y; 0..block.dy) {
487 foreach (immutable x; 0..block.dx) {
488 int test = 0-((pold[x]-pnew[x])&0x00ffffff);
489 ret -= (test>>31); // 0 or -1
491 pold += mPitch;
492 pnew += mPitch;
494 return ret;
497 void addXorBlock(P) (int vx, int vy, const(FrameBlock*) block) @trusted nothrow @nogc {
498 auto pold = (cast(const(P)*)oldFrame)+block.start+(vy*mPitch)+vx;
499 auto pnew = (cast(const(P)*)newFrame)+block.start;
500 auto dest = cast(P*)(work+workUsed);
501 workUsed += P.sizeof*(block.dx*block.dy);
502 foreach (immutable y; 0..block.dy) {
503 foreach (immutable x; 0..block.dx) {
504 *dest++ = pnew[x]^pold[x];
506 pold += mPitch;
507 pnew += mPitch;
511 void addXorFrame(P) () @trusted nothrow @nogc {
512 ubyte* vectors = work+workUsed;
513 // align the following xor data on 4 byte boundary
514 workUsed = (workUsed+blockCount*2+3)&~3;
515 const(FrameBlock)* block = blocks;
516 foreach (immutable b; 0..blockCount) {
517 int bestvx = 0, bestvy = 0;
518 int bestchange = compareBlock!P(0, 0, block);
519 if (bestchange >= 4) {
520 auto possibles = 64;
521 foreach (ref vi; vectorTable) {
522 auto vx = vi.x, vy = vi.y;
523 if (possibleBlock!P(vx, vy, block) < 4) {
524 auto testchange = compareBlock!P(vx, vy, block);
525 if (testchange < bestchange) {
526 bestchange = testchange;
527 bestvx = vx;
528 bestvy = vy;
529 if (bestchange < 4) break;
531 if (--possibles == 0) break;
535 vectors[b*2+0] = (bestvx<<1)&0xff;
536 vectors[b*2+1] = (bestvy<<1)&0xff;
537 if (bestchange) {
538 vectors[b*2+0] |= 1;
539 addXorBlock!P(bestvx, bestvy, block);
541 ++block;
547 // ////////////////////////////////////////////////////////////////////////// //
548 class Decoder : Codec {
549 private:
550 usize workPos;
551 bool mPaletteChanged;
552 Format mOldFormat;
553 ubyte blockWidth;
554 ubyte blockHeight;
556 public:
557 this (uint width, uint height) @trusted {
558 super(width, height);
559 mCompression = Compression.None;
560 mOldFormat = Format.None;
561 blockWidth = blockHeight = 0;
564 protected override void zlibDeinit () @trusted nothrow /*@nogc*/ {
565 if (zstreamInited) {
566 import etc.c.zlib : inflateEnd;
567 try inflateEnd(&zstream); catch (Exception) {}
568 zstreamInited = false;
572 final:
573 @property const(ubyte)[] palette () @trusted nothrow @nogc { return mPalette[0..768]; }
575 const(ubyte)[] line (usize idx) const @trusted nothrow @nogc {
576 if (idx >= mHeight) return null;
577 usize stpos = pixelSize*(MaxVector+MaxVector*mPitch)+mPitch*pixelSize*idx;
578 return newFrame[stpos..stpos+mWidth*pixelSize];
581 // was pallette changed on currently decoded frame?
582 bool paletteChanged () const @trusted nothrow @nogc { return mPaletteChanged; }
584 void decodeFrame (const(void)[] frameData) @trusted {
585 usize size = frameData.length;
586 mPaletteChanged = false;
587 if (frameData is null || size <= 1) throw new ZMBVError("invalid frame data");
588 auto data = cast(const(ubyte)*)frameData.ptr;
589 ubyte tag = *data++;
590 --size;
591 if (tag > 2) throw new ZMBVError("invalid frame data"); // for now we can have only 0, 1 or 2 in tag byte
592 if (tag&FrameMask.Keyframe) {
593 auto header = cast(const(KeyframeHeader*))data;
594 if (size <= KeyframeHeader.sizeof) throw new ZMBVError("invalid frame data");
595 size -= KeyframeHeader.sizeof;
596 data += KeyframeHeader.sizeof;
597 if (header.versionHi != Version.High || header.versionLo != Version.Low) throw new ZMBVError("invalid frame data");
598 if (header.compression > Compression.max) throw new ZMBVError("invalid frame data"); // invalid compression mode
599 if (!isValidFormat(cast(Format)header.format)) throw new ZMBVError("invalid frame data");
600 if (header.blockWidth < 1 || header.blockHeight < 1) throw new ZMBVError("invalid frame data");
601 if (mFormat != cast(Format)header.format || blockWidth != header.blockWidth || blockHeight != header.blockHeight) {
602 // new format or block size
603 mFormat = cast(Format)header.format;
604 blockWidth = header.blockWidth;
605 blockHeight = header.blockHeight;
606 setupBuffers(mFormat, blockWidth, blockHeight);
608 mCompression = cast(Compression)header.compression;
609 if (mCompression == Compression.ZLib) {
610 import etc.c.zlib : inflateInit, inflateReset, Z_OK;
611 if (!zstreamInited) {
612 if (inflateInit(&zstream) != Z_OK) throw new ZMBVError("can't initialize ZLib stream");
613 zstreamInited = true;
614 } else {
615 if (inflateReset(&zstream) != Z_OK) throw new ZMBVError("can't reset inflate stream");
619 if (size > bufSize) throw new ZMBVError("frame too big");
620 if (mCompression == Compression.ZLib) {
621 import etc.c.zlib : inflate, Z_OK, Z_SYNC_FLUSH;
622 zstream.next_in = cast(ubyte*)data;
623 zstream.avail_in = cast(uint)size;
624 zstream.total_in = 0;
625 zstream.next_out = work;
626 zstream.avail_out = cast(uint)bufSize;
627 zstream.total_out = 0;
628 if (inflate(&zstream, Z_SYNC_FLUSH/*Z_NO_FLUSH*/) != Z_OK) throw new ZMBVError("can't read inflate stream"); // the thing that should not be
629 workUsed = zstream.total_out;
630 } else {
631 if (size > 0) work[0..size] = data[0..size];
632 workUsed = size;
634 workPos = 0;
635 if (tag&FrameMask.Keyframe) {
636 if (palSize) {
637 if (workUsed < palSize*3) throw new ZMBVError("invalid frame data");
638 mPaletteChanged = true;
639 workPos = palSize*3;
640 mPalette[0..workPos] = work[0..workPos];
642 newFrame = buf1;
643 oldFrame = buf2;
644 ubyte* writeframe = newFrame+pixelSize*(MaxVector+MaxVector*mPitch);
645 foreach (immutable _; 0..mHeight) {
646 if (workPos+mWidth*pixelSize > workUsed) throw new ZMBVError("invalid frame data");
647 writeframe[0..mWidth*pixelSize] = work[workPos..workPos+mWidth*pixelSize];
648 writeframe += mPitch*pixelSize;
649 workPos += mWidth*pixelSize;
651 } else {
652 swapFrames();
653 if (tag&FrameMask.DeltaPalette) {
654 if (workUsed < palSize*3) throw new ZMBVError("invalid frame data");
655 mPaletteChanged = true;
656 foreach (immutable i; 0..palSize*3) mPalette[i] ^= work[workPos++];
658 switch (mFormat) {
659 case Format.bpp8: unxorFrame!ubyte(); break;
660 case Format.bpp15: case Format.bpp16: unxorFrame!ushort(); break;
661 case Format.bpp32: unxorFrame!uint(); break;
662 default: throw new ZMBVError("invalid format");
667 private:
668 // decoder templates
669 void unxorBlock(P) (int vx, int vy, const(FrameBlock*) block) @trusted nothrow @nogc {
670 auto pold = (cast(const(P)*)oldFrame)+block.start+(vy*mPitch)+vx;
671 auto pnew = (cast(P*)newFrame)+block.start;
672 auto src = cast(const(P)*)(work+workPos);
673 workPos += P.sizeof*(block.dx*block.dy);
674 foreach (immutable y; 0..block.dy) {
675 foreach (immutable x; 0..block.dx) {
676 pnew[x] = pold[x]^(*src++);
678 pold += mPitch;
679 pnew += mPitch;
683 void copyBlock(P) (int vx, int vy, const(FrameBlock*) block) @trusted nothrow @nogc {
684 auto pold = (cast(const(P)*)oldFrame)+block.start+(vy*mPitch)+vx;
685 auto pnew = (cast(P*)newFrame)+block.start;
686 foreach (immutable y; 0..block.dy) {
687 pnew[0..block.dx] = pold[0..block.dx];
688 pold += mPitch;
689 pnew += mPitch;
693 void unxorFrame(P) () @trusted nothrow @nogc {
694 auto vectors = cast(byte*)work+workPos;
695 workPos = (workPos+blockCount*2+3)&~3;
696 const(FrameBlock)* block = blocks;
697 foreach (immutable b; 0..blockCount) {
698 int delta = vectors[b*2+0]&1;
699 int vx = vectors[b*2+0]>>1;
700 int vy = vectors[b*2+1]>>1;
701 if (delta) unxorBlock!P(vx, vy, block); else copyBlock!P(vx, vy, block);
702 ++block;
708 // ////////////////////////////////////////////////////////////////////////// //
709 private enum AVI_HEADER_SIZE = 500;
712 final class AviWriter {
713 private:
714 import std.stdio : File;
716 public:
717 Encoder ec;
719 File fl;
720 ubyte* index;
721 uint indexsize, indexused;
722 uint fps;
723 uint frames;
724 uint written;
725 uint audioused; // always 0 for now
726 uint audiowritten;
727 uint audiorate; // 44100?
728 bool was_file_error;
730 this (string fname, uint width, uint height, uint fps) @trusted {
731 import core.stdc.stdlib : malloc;
732 import std.exception : enforce;
733 if (fps < 1 || fps > 255) throw new Exception("invalid fps");
734 ec = new Encoder(width, height, Encoder.Format.None, 9);
735 this.fps = fps;
736 this.fl = File(fname, "w");
738 ubyte[AVI_HEADER_SIZE] hdr;
739 fl.rawWrite(hdr);
741 indexsize = 16*4096;
742 indexused = 8;
743 index = cast(ubyte*)malloc(indexsize);
744 enforce(index !is null);
747 ~this () @trusted {
748 import core.stdc.stdlib : free;
749 if (index !is null) free(index);
750 index = null;
751 indexused = 0;
754 void close () @trusted {
755 import core.stdc.stdlib : free;
756 fixHeader();
757 fl.close();
758 if (index !is null) free(index);
759 index = null;
760 indexused = 0;
763 void fixHeader () @trusted {
764 ubyte[AVI_HEADER_SIZE] hdr;
765 usize header_pos = 0;
767 void AVIOUT4 (string s) @trusted {
768 assert(s.length == 4);
769 hdr[header_pos..header_pos+4] = cast(ubyte[])s;
770 header_pos += 4;
773 void AVIOUTw (ulong w) @trusted {
774 if (w > 0xffff) throw new Exception("invalid ushort");
775 hdr[header_pos++] = (w&0xff);
776 hdr[header_pos++] = ((w>>8)&0xff);
779 void AVIOUTd (ulong w, int line=__LINE__) @trusted {
780 import std.string : format;
781 if (w > 0xffffffffu) throw new Exception("invalid ulong: 0x%08x at line %s".format(w, line));
782 hdr[header_pos++] = (w&0xff);
783 hdr[header_pos++] = ((w>>8)&0xff);
784 hdr[header_pos++] = ((w>>16)&0xff);
785 hdr[header_pos++] = ((w>>24)&0xff);
788 uint main_list;
790 AVIOUT4("RIFF"); // riff header
791 AVIOUTd(AVI_HEADER_SIZE+written-8+indexused);
792 AVIOUT4("AVI ");
793 AVIOUT4("LIST"); // list header
794 main_list = cast(uint)header_pos;
795 AVIOUTd(0); // TODO size of list
796 AVIOUT4("hdrl");
798 AVIOUT4("avih");
799 AVIOUTd(56); // # of bytes to follow
800 AVIOUTd(1000000/fps); // microseconds per frame
801 AVIOUTd(0);
802 AVIOUTd(0); // PaddingGranularity (whatever that might be)
803 AVIOUTd(0x110); // Flags, 0x10 has index, 0x100 interleaved
804 AVIOUTd(frames); // TotalFrames
805 AVIOUTd(0); // InitialFrames
806 AVIOUTd(audiowritten > 0 ? 2 : 1); // Stream count
807 AVIOUTd(0); // SuggestedBufferSize
808 AVIOUTd(ec.width); // Width
809 AVIOUTd(ec.height); // Height
810 AVIOUTd(0); // TimeScale: Unit used to measure time
811 AVIOUTd(0); // DataRate: Data rate of playback
812 AVIOUTd(0); // StartTime: Starting time of AVI data
813 AVIOUTd(0); // DataLength: Size of AVI data chunk
815 // video stream list
816 AVIOUT4("LIST");
817 AVIOUTd(4+8+56+8+40); // size of the list
818 AVIOUT4("strl");
819 // video stream header
820 AVIOUT4("strh");
821 AVIOUTd(56); // # of bytes to follow
822 AVIOUT4("vids"); // type
823 AVIOUT4("ZMBV"); // handler
824 AVIOUTd(0); // Flags
825 AVIOUTd(0); // Reserved, MS says: wPriority, wLanguage
826 AVIOUTd(0); // InitialFrames
827 AVIOUTd(1000000); // Scale
828 AVIOUTd(1000000*fps); // Rate: Rate/Scale == samples/second
829 AVIOUTd(0); // Start
830 AVIOUTd(frames); // Length
831 AVIOUTd(0); // SuggestedBufferSize
832 AVIOUTd(0xffffffffu); // Quality
833 AVIOUTd(0); // SampleSize
834 AVIOUTd(0); // Frame
835 AVIOUTd(0); // Frame
836 // the video stream format
837 AVIOUT4("strf");
838 AVIOUTd(40); // # of bytes to follow
839 AVIOUTd(40); // Size
840 AVIOUTd(ec.width); // Width
841 AVIOUTd(ec.height); // Height
842 //OUTSHRT(1); OUTSHRT(24); // Planes, Count
843 AVIOUTd(0);
844 AVIOUT4("ZMBV"); // Compression
845 AVIOUTd(ec.width*ec.height*4); // SizeImage (in bytes?)
846 AVIOUTd(0); // XPelsPerMeter
847 AVIOUTd(0); // YPelsPerMeter
848 AVIOUTd(0); // ClrUsed: Number of colors used
849 AVIOUTd(0); // ClrImportant: Number of colors important
851 if (audiowritten > 0) {
852 // audio stream list
853 AVIOUT4("LIST");
854 AVIOUTd(4+8+56+8+16); // Length of list in bytes
855 AVIOUT4("strl");
856 // the audio stream header
857 AVIOUT4("strh");
858 AVIOUTd(56); // # of bytes to follow
859 AVIOUT4("auds");
860 AVIOUTd(0); // Format (Optionally)
861 AVIOUTd(0); // Flags
862 AVIOUTd(0); // Reserved, MS says: wPriority, wLanguage
863 AVIOUTd(0); // InitialFrames
864 AVIOUTd(4); // Scale
865 AVIOUTd(audiorate*4); // rate, actual rate is scale/rate
866 AVIOUTd(0); // Start
867 if (!audiorate) audiorate = 1;
868 AVIOUTd(audiowritten/4); // Length
869 AVIOUTd(0); // SuggestedBufferSize
870 AVIOUTd(~0); // Quality
871 AVIOUTd(4); // SampleSize
872 AVIOUTd(0); // Frame
873 AVIOUTd(0); // Frame
874 // the audio stream format
875 AVIOUT4("strf");
876 AVIOUTd(16); // # of bytes to follow
877 AVIOUTw(1); // Format, WAVE_ZMBV_FORMAT_PCM
878 AVIOUTw(2); // Number of channels
879 AVIOUTd(audiorate); // SamplesPerSec
880 AVIOUTd(audiorate*4); // AvgBytesPerSec
881 AVIOUTw(4); // BlockAlign
882 AVIOUTw(16); // BitsPerSample
884 long nmain = header_pos-main_list-4;
885 // finish stream list, i.e. put number of bytes in the list to proper pos
886 long njunk = AVI_HEADER_SIZE-8-12-header_pos;
887 AVIOUT4("JUNK");
888 AVIOUTd(njunk);
889 // fix the size of the main list
890 header_pos = main_list;
891 AVIOUTd(nmain);
892 header_pos = AVI_HEADER_SIZE-12;
893 AVIOUT4("LIST");
894 AVIOUTd(written+4); // Length of list in bytes
895 AVIOUT4("movi");
896 // first add the index table to the end
897 index[0] = 'i';
898 index[1] = 'd';
899 index[2] = 'x';
900 index[3] = '1';
902 uint d = indexused-8;
903 index[4] = (d&0xff);
904 index[5] = ((d>>8)&0xff);
905 index[6] = ((d>>16)&0xff);
906 index[7] = ((d>>24)&0xff);
907 fl.rawWrite(index[0..indexused]);
909 // now replace the header
910 fl.seek(0);
911 fl.rawWrite(hdr);
914 void writeChunk (string tag, const(void)[] data, uint flags, bool writeToIndex=false) @trusted {
915 if (tag.length != 4) throw new Exception("invalid tag name");
916 uint size = cast(uint)data.length;
917 fl.rawWrite(tag);
918 fl.rawWrite((&size)[0..1]);
919 ubyte* idx;
920 uint pos, d;
921 // write the actual data
922 uint writesize = (size+1)&~1;
923 if (size > 0) fl.rawWrite(data);
924 if (writesize != size) {
925 ubyte[1] b;
926 assert(writesize-size == 1); // just in case
927 fl.rawWrite(b);
929 pos = written+4;
930 written += writesize+8;
931 if (indexused+16 >= indexsize) {
932 import core.stdc.stdlib : realloc;
933 import std.exception : enforce;
934 void* ni = realloc(index, indexsize+16*4096);
935 enforce(ni !is null);
936 index = cast(ubyte*)ni;
937 indexsize += 16*4096;
940 void putu32 (usize pos, ulong n) @trusted nothrow @nogc {
941 idx[pos] = (n&0xff);
942 idx[pos] = ((n>>8)&0xff);
943 idx[pos] = ((n>>16)&0xff);
944 idx[pos] = ((n>>24)&0xff);
947 if (writeToIndex) {
948 idx = index+indexused;
949 indexused += 16;
950 idx[0] = tag[0];
951 idx[1] = tag[1];
952 idx[2] = tag[2];
953 idx[3] = tag[3];
954 putu32(4, flags);
955 putu32(8, pos);
956 putu32(12, size);
960 void writeChunkVideo (const(void)[] framedata) @trusted {
961 if (framedata.length == 0) throw new Exception("can't write empty frame");
962 ubyte b = (cast(const(ubyte)[])framedata)[0];
963 writeChunk("00dc", framedata, (b&0x01 ? 0x10 : 0), ((b&0x01) != 0));
964 ++frames;
968 void writeChunkAudio (const(void)[] data) @trusted {
969 if (data.length == 0) return;
970 writeChunk("01wb", data, 0);
971 audiowritten = cast(uint)data.length;