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