mirror of https://github.com/adamdruppe/arsd.git
Merge branch 'master' of git://192.168.1.10/home/me/arsd
This commit is contained in:
commit
414762f19b
|
@ -0,0 +1,237 @@
|
|||
/++
|
||||
Support for [animated png|https://wiki.mozilla.org/APNG_Specification] files.
|
||||
+/
|
||||
module arsd.apng;
|
||||
|
||||
import arsd.png;
|
||||
|
||||
// acTL
|
||||
// must be in the file before the IDAT
|
||||
struct AnimationControlChunk {
|
||||
uint num_frames;
|
||||
uint num_plays;
|
||||
}
|
||||
|
||||
// fcTL
|
||||
struct FrameControlChunk {
|
||||
align(1):
|
||||
// this should go up each time, for frame control AND for frame data, each increases.
|
||||
uint sequence_number;
|
||||
uint width;
|
||||
uint height;
|
||||
uint x_offset;
|
||||
uint y_offset;
|
||||
ushort delay_num;
|
||||
ushort delay_den;
|
||||
APNG_DISPOSE_OP dispose_op;
|
||||
APNG_BLEND_OP blend_op;
|
||||
|
||||
static assert(dispose_op.offsetof == 24);
|
||||
static assert(blend_op.offsetof == 25);
|
||||
}
|
||||
|
||||
// fdAT
|
||||
class ApngFrame {
|
||||
|
||||
ApngAnimation parent;
|
||||
|
||||
this(ApngAnimation parent) {
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
FrameControlChunk frameControlChunk;
|
||||
|
||||
ubyte[] compressedDatastream;
|
||||
|
||||
ubyte[] data;
|
||||
void populateData() {
|
||||
if(data !is null)
|
||||
return;
|
||||
|
||||
import std.zlib;
|
||||
|
||||
auto raw = cast(ubyte[]) uncompress(compressedDatastream);
|
||||
auto bpp = bytesPerPixel(parent.header);
|
||||
|
||||
auto width = frameControlChunk.width;
|
||||
auto height = frameControlChunk.height;
|
||||
|
||||
auto bytesPerLine = bytesPerLineOfPng(parent.header.depth, parent.header.type, width);
|
||||
|
||||
int idataIdx;
|
||||
ubyte[] idata;
|
||||
|
||||
idata.length = width * height * (parent.header.type == 3 ? 1 : 4);
|
||||
|
||||
ubyte[] previousLine;
|
||||
foreach(y; 0 .. height) {
|
||||
auto filter = raw[0];
|
||||
raw = raw[1 .. $];
|
||||
auto line = raw[0 .. bytesPerLine];
|
||||
raw = raw[bytesPerLine .. $];
|
||||
|
||||
auto unfiltered = unfilter(filter, line, previousLine, bpp);
|
||||
previousLine = line;
|
||||
|
||||
convertPngData(parent.header.type, parent.header.depth, unfiltered, width, idata, idataIdx);
|
||||
}
|
||||
|
||||
this.data = idata;
|
||||
}
|
||||
|
||||
// then need to uncompress it
|
||||
// and unfilter it...
|
||||
// and then convert it to the right format.
|
||||
|
||||
MemoryImage frameData;
|
||||
}
|
||||
|
||||
class ApngAnimation {
|
||||
PngHeader header;
|
||||
AnimationControlChunk acc;
|
||||
Color[] palette;
|
||||
ApngFrame[] frames;
|
||||
// default image? tho i can just load it as a png for that too.
|
||||
|
||||
MemoryImage render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
enum APNG_DISPOSE_OP : byte {
|
||||
NONE = 0,
|
||||
BACKGROUND = 1,
|
||||
PREVIOUS = 2
|
||||
}
|
||||
|
||||
enum APNG_BLEND_OP : byte {
|
||||
SOURCE = 0,
|
||||
OVER = 1
|
||||
}
|
||||
|
||||
void readApng(in ubyte[] data) {
|
||||
auto png = readPng(data);
|
||||
auto header = PngHeader.fromChunk(png.chunks[0]);
|
||||
Color[] palette;
|
||||
if(header.type == 3) {
|
||||
palette = fetchPalette(png);
|
||||
}
|
||||
|
||||
auto obj = new ApngAnimation();
|
||||
|
||||
bool seenIdat = false;
|
||||
bool seenFctl = false;
|
||||
|
||||
int frameNumber;
|
||||
int expectedSequenceNumber = 0;
|
||||
|
||||
foreach(chunk; png.chunks) {
|
||||
switch(chunk.stype) {
|
||||
case "IDAT":
|
||||
seenIdat = true;
|
||||
// all I care about here are animation frames,
|
||||
// so if this isn't after a control chunk, I'm
|
||||
// just going to ignore it. Read the file with
|
||||
// readPng if you want that.
|
||||
if(!seenFctl)
|
||||
continue;
|
||||
|
||||
assert(obj.frames[0]);
|
||||
|
||||
obj.frames[0].compressedDatastream ~= chunk.payload;
|
||||
break;
|
||||
case "acTL":
|
||||
AnimationControlChunk c;
|
||||
int offset = 0;
|
||||
c.num_frames |= chunk.payload[offset++] << 24;
|
||||
c.num_frames |= chunk.payload[offset++] << 16;
|
||||
c.num_frames |= chunk.payload[offset++] << 8;
|
||||
c.num_frames |= chunk.payload[offset++] << 0;
|
||||
|
||||
c.num_plays |= chunk.payload[offset++] << 24;
|
||||
c.num_plays |= chunk.payload[offset++] << 16;
|
||||
c.num_plays |= chunk.payload[offset++] << 8;
|
||||
c.num_plays |= chunk.payload[offset++] << 0;
|
||||
|
||||
assert(offset == chunk.payload.length);
|
||||
|
||||
obj.acc = c;
|
||||
obj.frames = new ApngFrame[](c.num_frames);
|
||||
break;
|
||||
case "fcTL":
|
||||
FrameControlChunk c;
|
||||
int offset = 0;
|
||||
|
||||
seenFctl = true;
|
||||
|
||||
c.sequence_number |= chunk.payload[offset++] << 24;
|
||||
c.sequence_number |= chunk.payload[offset++] << 16;
|
||||
c.sequence_number |= chunk.payload[offset++] << 8;
|
||||
c.sequence_number |= chunk.payload[offset++] << 0;
|
||||
|
||||
c.width |= chunk.payload[offset++] << 24;
|
||||
c.width |= chunk.payload[offset++] << 16;
|
||||
c.width |= chunk.payload[offset++] << 8;
|
||||
c.width |= chunk.payload[offset++] << 0;
|
||||
|
||||
c.height |= chunk.payload[offset++] << 24;
|
||||
c.height |= chunk.payload[offset++] << 16;
|
||||
c.height |= chunk.payload[offset++] << 8;
|
||||
c.height |= chunk.payload[offset++] << 0;
|
||||
|
||||
c.x_offset |= chunk.payload[offset++] << 24;
|
||||
c.x_offset |= chunk.payload[offset++] << 16;
|
||||
c.x_offset |= chunk.payload[offset++] << 8;
|
||||
c.x_offset |= chunk.payload[offset++] << 0;
|
||||
|
||||
c.y_offset |= chunk.payload[offset++] << 24;
|
||||
c.y_offset |= chunk.payload[offset++] << 16;
|
||||
c.y_offset |= chunk.payload[offset++] << 8;
|
||||
c.y_offset |= chunk.payload[offset++] << 0;
|
||||
|
||||
c.delay_num |= chunk.payload[offset++] << 8;
|
||||
c.delay_num |= chunk.payload[offset++] << 0;
|
||||
|
||||
c.delay_den |= chunk.payload[offset++] << 8;
|
||||
c.delay_den |= chunk.payload[offset++] << 0;
|
||||
|
||||
c.dispose_op = cast(APNG_DISPOSE_OP) chunk.payload[offset++];
|
||||
c.blend_op = cast(APNG_BLEND_OP) chunk.payload[offset++];
|
||||
|
||||
assert(offset == chunk.payload.length);
|
||||
|
||||
if(expectedSequenceNumber != c.sequence_number)
|
||||
throw new Exception("malformed apng file");
|
||||
|
||||
expectedSequenceNumber++;
|
||||
|
||||
|
||||
if(obj.frames[frameNumber] is null)
|
||||
obj.frames[frameNumber] = new ApngFrame(obj);
|
||||
obj.frames[frameNumber].frameControlChunk = c;
|
||||
|
||||
frameNumber++;
|
||||
break;
|
||||
case "fdAT":
|
||||
uint sequence_number;
|
||||
int offset;
|
||||
|
||||
sequence_number |= chunk.payload[offset++] << 24;
|
||||
sequence_number |= chunk.payload[offset++] << 16;
|
||||
sequence_number |= chunk.payload[offset++] << 8;
|
||||
sequence_number |= chunk.payload[offset++] << 0;
|
||||
|
||||
if(expectedSequenceNumber != sequence_number)
|
||||
throw new Exception("malformed apng file");
|
||||
|
||||
expectedSequenceNumber++;
|
||||
|
||||
// and the rest of it is a datastream...
|
||||
obj.frames[frameNumber - 1].compressedDatastream ~= chunk.payload;
|
||||
break;
|
||||
default:
|
||||
// ignore
|
||||
}
|
||||
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue