-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.html
88 lines (79 loc) · 3.22 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
<title>Assembles an APNG from individual PNGs</title>
<meta name="viewport" content="width=650">
Source Images:<br/>
<div></div><br/>
Output Image:<br/>
<img id="outputImage"/><br/>
<p style="width: 640px; font-family: monospace;">
Since <a href="https://wiki.mozilla.org/APNG_Specification#Structure">APNG</a>
is a simple extension of <a href="https://en.wikipedia.org/wiki/PNG">PNG</a>, we
can assemble animated images using individual PNGs encoded by
<a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob">canvas.toBlob()</a> and
<a href="https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas/convertToBlob">offscreenCanvas.convertToBlob()</a>.
<br/><br/>
A PNG contains a couple metadata chunks and then image data as one or more
`IDAT` chunks. APNG just adds a few new chunks: `acTL`, 'fcTL' and 'fdAT'. The
last of which is just an `IDAT` with a sequence number added.
<br/><br/>
This demo works by slicing the image data out of each image and applying a minor
transform to change it into the format expected by APNG.
<a href="https://en.wikipedia.org/wiki/APNG#File_format">Wikipedia</a> has an
excellent diagram of what this transform looks like.
<br/><br/>
<a href="https://github.com/dalecurtis/dalecurtis.github.io/tree/master/apng-assemble">Source Code</a><br/>
</p>
<script src="swirl.js"></script>
<script src="crc32.js"></script>
<script src="apng.js"></script>
<script>
const WIDTH = 640;
const HEIGHT = 480;
const FRAMES = 10;
const DELAY_NUMERATOR = 1;
const DELAY_DENOMINATOR = 10; // 10fps.
function displayImageData(img, imgData) {
let blob = new Blob([imgData], {type: 'application/octet-stream'});
img.src = URL.createObjectURL(blob);
}
document.addEventListener('DOMContentLoaded', async _ => {
let input_canvas = new OffscreenCanvas(WIDTH, HEIGHT);
let input_ctx = input_canvas.getContext("2d");
let particles = generateParticles(
101, input_canvas.width, input_canvas.height);
var have_frames;
let frame_encode_complete = new Promise((resolve, reject) => {
have_frames = resolve;
});
// Step animation and create png from each step.
let frames = new Array(FRAMES);
let frame_count = 0;
for (let i = 0; i < frames.length; ++i) {
anim(input_canvas, input_ctx, particles);
input_canvas.convertToBlob({type: 'image/png'}).then(blob => {
blob.arrayBuffer().then(arr => {
frames[i] = new Uint8Array(arr);
if (++frame_count == frames.length)
have_frames();
});
}, 'image/png');
}
await frame_encode_complete;
// Display source frames for demo purposes.
const PADDING = 2;
let div = document.querySelector('div');
div.style.width = (input_canvas.width + (frames.length / 2) * PADDING) + "px";
for (let i = 0; i < frames.length; ++i) {
let img = document.createElement('img');
img.width = input_canvas.width / 5;
img.height = input_canvas.height / 5;
img.style.paddingRight = PADDING + 'px';
img.style.paddingBottom = PADDING + 'px';
displayImageData(img, frames[i]);
div.appendChild(img);
}
let apng = createAnimatedPNG(
frames, input_canvas.width, input_canvas.height,
DELAY_NUMERATOR, DELAY_DENOMINATOR);
displayImageData(document.getElementById('outputImage'), apng);
});
</script>