Created
November 20, 2017 12:12
-
-
Save akabekobeko/3358ca68150c0318d53095ae622e2f0c to your computer and use it in GitHub Desktop.
RLE for ICNS.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* @file rle-icns.js | |
* @author akabeko | |
* | |
* This program is free software: you can redistribute it and/or modify | |
* it under the terms of the GNU General Public License as published by | |
* the Free Software Foundation, either version 3 of the License, or | |
* (at your option) any later version. | |
* | |
* This program is distributed in the hope that it will be useful, | |
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
* GNU General Public License for more details. | |
* | |
* You should have received a copy of the GNU General Public License | |
* along with this program. If not, see <http://www.gnu.org/licenses/>. | |
* | |
*/ | |
export default class RunLengthEncodingICNS { | |
/** | |
* Compress binary with ICNS RLE. | |
* I transplanted https://github.com/pornel/libicns/blob/master/src/icns_rle24.c to Node. | |
* | |
* @param {Array.<Number>} src Source binary (RGBA). | |
* | |
* @return {Array.<Number>} Compressed binary. | |
* | |
* @see http://icns.sourceforge.net/ | |
* @see https://github.com/pornel/libicns/ | |
*/ | |
static pack (src) { | |
// Assumptions of what icns rle data is all about: | |
// A) Each channel is encoded indepenent of the next. | |
// B) An encoded channel looks like this: | |
// 0xRL 0xCV 0xCV 0xRL 0xCV - RL is run-length and CV is color value. | |
// C) There are two types of runs | |
// 1) Run of same value - high bit of RL is set | |
// 2) Run of differing values - high bit of RL is NOT set | |
// D) 0xRL also has two ranges | |
// 1) for set high bit RL, 3 to 130 | |
// 2) for clr high bit RL, 1 to 128 | |
// E) 0xRL byte is therefore set as follows: | |
// 1) for same values, RL = RL - 1 | |
// 2) different values, RL = RL + 125 | |
// 3) both methods will automatically set the high bit appropriately | |
// F) 0xCV byte are set accordingly | |
// 1) for differing values, run of all differing values | |
// 2) for same values, only one byte of that values | |
// Estimations put the absolute worst case scenario as the | |
// final compressed data being slightly LARGER. So we need to be | |
// careful about allocating memory. (Did I miss something?) | |
// tests seem to indicate it will never be larger than the original | |
const dataTemp = (new Array(src.length + (src.length / 4))).fill(0) | |
const dataRun = (new Array(140)).fill(0) | |
const dataInChanSize = src.length / 4 | |
let dataTempCount = 65536 <= src.length ? 4 : 0 | |
// Data is stored in red run, green run,blue run | |
// So we compress from pixel format RGBA | |
// RED: byte[0], byte[4], byte[8] ... | |
// GREEN: byte[1], byte[5], byte[9] ... | |
// BLUE: byte[2], byte[6], byte[10] ... | |
// ALPHA: byte[3], byte[7], byte[11] do nothing with these bytes | |
for (let colorOffset = 0; colorOffset < 3; colorOffset++) { | |
dataRun[0] = src[colorOffset] | |
// Start with a runlength of 1 for the first byte | |
let runLength = 1 | |
// Assume that the run will be different for now... We can change this later | |
// 0 for low bit (different), 1 for high bit (same) | |
let runType = 0 | |
// Start one byte ahead | |
for (let dataInCount = 1; dataInCount < dataInChanSize; dataInCount++) { | |
const dataByte = src[colorOffset + (dataInCount * 4)] | |
if (runLength < 2) { | |
// Simply append to the current run | |
dataRun[runLength++] = dataByte | |
} else if (runLength === 2) { | |
// Decide here if the run should be same values or different values | |
// If the last three values were the same, we can change to a same-type run | |
if ((dataByte === dataRun[runLength - 1]) && (dataByte === dataRun[runLength - 2])) { | |
runType = 1 | |
} else { | |
runType = 0 | |
} | |
dataRun[runLength++] = dataByte | |
} else { | |
// Greater than or equal to 2 | |
if (runType === 0 && runLength < 128) { | |
// Different type run | |
// If the new value matches both of the last two values, we have a new | |
// same-type run starting with the previous two bytes | |
if ((dataByte === dataRun[runLength - 1]) && (dataByte === dataRun[runLength - 2])) { | |
// Set the RL byte | |
dataTemp[dataTempCount] = runLength - 3 | |
dataTempCount++ | |
// Copy 0 to runLength-2 bytes to the RLE data here | |
PackBits._arrayCopy(dataRun, 0, dataTemp, dataTempCount, runLength - 2) | |
dataTempCount = dataTempCount + (runLength - 2) | |
// Set up the new same-type run | |
dataRun[0] = dataRun[runLength - 2] | |
dataRun[1] = dataRun[runLength - 1] | |
dataRun[2] = dataByte | |
runLength = 3 | |
runType = 1 | |
} else { | |
// They don't match, so we can proceed | |
dataRun[runLength++] = dataByte | |
} | |
} else if (runType === 1 && runLength < 130) { | |
// Same type run | |
// If the new value matches both of the last two values, we | |
// can safely continue | |
if ((dataByte === dataRun[runLength - 1]) && (dataByte === dataRun[runLength - 2])) { | |
dataRun[runLength++] = dataByte | |
} else { | |
// They don't match, so we need to start a new run | |
// Set the RL byte | |
dataTemp[dataTempCount] = runLength + 125 | |
dataTempCount++ | |
// Only copy the first byte, since all the remaining values are identical | |
dataTemp[dataTempCount] = dataRun[0] | |
dataTempCount++ | |
// Copy 0 to runLength bytes to the RLE data here | |
dataRun[0] = dataByte | |
runLength = 1 | |
runType = 0 | |
} | |
} else { | |
// Exceeded run limit, need to start a new one | |
if (runType === 0) { | |
// Set the RL byte low | |
dataTemp[dataTempCount] = runLength - 1 | |
dataTempCount++ | |
// Copy 0 to runLength bytes to the RLE data here | |
PackBits._arrayCopy(dataRun, 0, dataTemp, dataTempCount, runLength) | |
dataTempCount = dataTempCount + runLength | |
} else if (runType === 1) { | |
// Set the RL byte high | |
dataTemp[dataTempCount] = runLength + 125 | |
dataTempCount++ | |
// Only copy the first byte, since all the remaining values are identical | |
dataTemp[dataTempCount] = dataRun[0] | |
dataTempCount++ | |
} | |
// Copy 0 to runLength bytes to the RLE data here | |
dataRun[0] = dataByte | |
runLength = 1 | |
runType = 0 | |
} | |
} | |
} | |
// Copy the end of the last run | |
if (runLength > 0) { | |
if (runType === 0) { | |
// Set the RL byte low | |
dataTemp[dataTempCount] = runLength - 1 | |
dataTempCount++ | |
// Copy 0 to runLength bytes to the RLE data here | |
PackBits._arrayCopy(dataRun, 0, dataTemp, dataTempCount, runLength) | |
dataTempCount = dataTempCount + runLength | |
} else if (runType === 1) { | |
// Set the RL byte high | |
dataTemp[dataTempCount] = runLength + 125 | |
dataTempCount++ | |
// Only copy the first byte, since all the remaining values are identical | |
dataTemp[dataTempCount] = dataRun[0] | |
dataTempCount++ | |
} | |
} | |
} | |
const dest = new Array(dataTempCount) | |
PackBits._arrayCopy(dataTemp, 0, dest, 0, dataTempCount) | |
return dest | |
} | |
/** | |
* Copies the array to the target array at the specified position and size. | |
* | |
* @param {Array.<Number>} src Byte array of copy source. | |
* @param {Number} srcBegin Copying start position of source. | |
* @param {Array.<Number>} dest Bayte array of copy destination. | |
* @param {Number} destBegin Writing start position of destinnation. | |
* @param {Number} size Size of copy bytes. | |
*/ | |
static _arrayCopy (src, srcBegin, dest, destBegin, size) { | |
if (src.length <= srcBegin || src.length < size || dest.length <= destBegin || dest.length < size) { | |
return | |
} | |
for (let i = srcBegin, j = destBegin, k = 0; k < size; ++i, ++j, ++k) { | |
dest[j] = src[i] | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment