Skip to content

Commit

Permalink
optimized memory usage by maintaining encoded GIF data in a Uint8Arra…
Browse files Browse the repository at this point in the history
…y instead of a classical untyped Array

Since a Uint8Array uses only 1 byte per element and every number is likely a 64-bit floating point number, Uint8Array should be a much more compact way to store the data.
This change was tested by running encoding the same frames of a long gif animation that previously crashed the browser every time on the same laptop, same browser, same version of Windows...
  • Loading branch information
joshi1983 committed May 21, 2023
1 parent b46429c commit cf74fa5
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 7 deletions.
49 changes: 49 additions & 0 deletions DynamicByteArray.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/**
* This class represents an array of bytes in a compact, memory-efficient format.
* This was written to be used by GIFEncoder so it could encode larger GIF files than possible with a non-typed Array of number.
* @author Josh Greig
*/
function DynamicByteArray() {
var arr = new Uint8Array(1000);
var len = 0;

function expandCapacity() {
var newCapacity = arr.length * 2;
// If the capacity is huge, the risk of running out of memory is higher
// so we want to expand in 50% intervals instead of 100% intervals.
if (newCapacity > 50000000) {
newCapacity = arr.length * 1.5;
}
var newArr = new Uint8Array(newCapacity);
for (let i = 0; i < arr.length; i++) {
newArr[i] = arr[i];
}
arr = newArr;
}

DynamicByteArray.prototype.get = function(index) {
return arr[index];
};

DynamicByteArray.prototype.getLength = function() {
return len;
}

DynamicByteArray.prototype.toCompactUint8Array = function() {
if (arr.length !== len) {
const result = new Uint8Array(len);
for (let i = 0; i < len; i++) {
result[i] = arr[i];
}
arr = result;
}
return arr;
};

DynamicByteArray.prototype.writeByte = function(val) {
if (len >= arr.length) {
expandCapacity();
}
arr[len++] = val;
};
}
14 changes: 7 additions & 7 deletions GIFEncoder.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,27 +13,27 @@ GIFEncoder = function() {
chr[i] = String.fromCharCode(i);

function ByteArray() {
this.bin = [];
this.bin = new DynamicByteArray();
}

ByteArray.prototype.getData = function() {
for (var v = '', l = this.bin.length, i = 0; i < l; i++)
v += chr[this.bin[i]];
for (var v = '', l = this.bin.getLength(), i = 0; i < l; i++)
v += chr[this.bin.get(i)];
return v;
};

ByteArray.prototype.writeByte = function(val) {
this.bin.push(val);
this.bin.writeByte(val);
};

ByteArray.prototype.writeUTFBytes = function(string) {
for (var l = string.length, i = 0; i < l; i++)
this.writeByte(string.charCodeAt(i));
this.bin.writeByte(string.charCodeAt(i));
};

ByteArray.prototype.writeBytes = function(array, offset, length) {
for (var l = length || array.length, i = offset || 0; i < l; i++)
this.writeByte(array[i]);
this.bin.writeByte(array[i]);
};

var exports = {};
Expand Down Expand Up @@ -206,7 +206,7 @@ GIFEncoder = function() {
filename= filename !== undefined ? ( filename.endsWith(".gif")? filename: filename+".gif" ): "download.gif";
var templink = document.createElement("a");
templink.download=filename;
templink.href= URL.createObjectURL(new Blob([new Uint8Array(out.bin)], {type : "image/gif" } ));
templink.href= URL.createObjectURL(new Blob([out.bin.toCompactUint8Array()], {type : "image/gif" } ));
templink.click();
}
}
Expand Down

0 comments on commit cf74fa5

Please sign in to comment.