/* * Copyright (c) 2016-2017 Moddable Tech, Inc. * * This file is part of the Moddable SDK Tools. * * The Moddable SDK Tools 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. * * The Moddable SDK Tools 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 the Moddable SDK Tools. If not, see . * */ #include "mc.xs.h" typedef struct { uint16_t* begin; uint16_t* current; uint16_t* end; uint32_t delta; uint32_t length; uint16_t* from; uint16_t* to; uint16_t* first; uint16_t* last; int pass; int transparentIndex; } txRow; static int* bufferColorIndexes(int* prefixes, int* colorIndexes, int* buffer, int code, int clearCode) { while (code > clearCode) { *buffer++ = colorIndexes[code]; code = prefixes[code]; } *buffer = code; return buffer; } static uint16_t pad(uint16_t dimension, uint16_t multiple) { uint16_t overflow = dimension & (multiple - 1); if (overflow) dimension += multiple - overflow; return dimension; } static void poke(txRow* row, uint16_t* colorTable, int index) { if ((row->from <= row->current) && (row->current < row->to)) { if (index == row->transparentIndex) { row->current++; } else { *row->current++ = colorTable[index]; } if (row->current == row->end) { if (row->pass == 0) { row->begin += row->length; } else if (row->pass == 1) { row->begin += 8 * row->length; if (row->begin >= row->last) { row->begin = row->first + 4 * row->length; row->pass++; } } else if (row->pass == 2) { row->begin += 8 * row->length; if (row->begin >= row->last) { row->begin = row->first + 2 * row->length; row->pass++; } } else if (row->pass == 3) { row->begin += 4 * row->length; if (row->begin >= row->last) { row->begin = row->first + 1 * row->length; row->pass++; } } else if (row->pass == 4) { row->begin += 2 * row->length; } row->current = row->begin; row->end = row->begin + row->delta; } } } #define MAX_CODE 4096 #define BYTE ((bytes < bytesLimit) ? *bytes++ : (xsUnknownError("invalid GIF buffer"), 0)) void Tool_readGIF(xsMachine* the) { static uint8_t byteMasks[8] = { 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80 }; static int codeMasks[16] = { 0x0001, 0x0002, 0x0004, 0x0008, 0x0010, 0x0020, 0x0040, 0x0080, 0x0100, 0x0200, 0x0400, 0x0800, 0x1000, 0x2000, 0x4000, 0x8000 }; uint16_t globalColorTable[256]; uint16_t localColorTable[256]; uint8_t* bytesInitial; uint8_t* bytesLimit; uint8_t* bytes; uint32_t bufferSize; uint32_t bufferOffset; txRow row; uint8_t byte; uint16_t globalWidth; uint16_t globalHeight; uint8_t backgroundColorIndex; uint8_t count; uint16_t delay = 0; uint8_t disposalMethod = 0; uint8_t transparentColorFlag = 0; uint8_t transparentColorIndex = 0; uint16_t* screen; uint16_t* colorTable; xsVars(2); bufferSize = xsToInteger(xsGet(xsArg(0), xsID_byteLength)); if (xsIsInstanceOf(xsArg(0), xsArrayBufferPrototype)) bytesInitial = xsToArrayBuffer(xsArg(0)); else bytesInitial = xsGetHostData(xsArg(0)); bytesLimit = bytesInitial + bufferSize; bytes = bytesInitial; if (bufferSize < 6) xsUnknownError("invalid GIF buffer"); if ((bytes[0] != 'G') || (bytes[1] != 'I') ||(bytes[2] != 'F')) xsUnknownError("invalid GIF signature"); if ((bytes[3] != '8') || ((bytes[4] != '7') && (bytes[4] != '9')) || (bytes[5] != 'a')) xsUnknownError("invalid GIF version"); bytes += 6; globalWidth = BYTE; globalWidth += BYTE << 8; globalHeight = BYTE; globalHeight += BYTE << 8; globalWidth = pad(globalWidth, 4); globalHeight = pad(globalHeight, 4); byte = BYTE; backgroundColorIndex = BYTE; bytes++; if (byte & 0x80) { int c = 2 << (byte & 0x07), i; for (i = 0; i < c; i++) { uint16_t r = BYTE; uint16_t g = BYTE; uint16_t b = BYTE; globalColorTable[i] = (((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3)); } } bufferOffset = bytes - bytesInitial; xsResult = xsNewArray(0); xsDefine(xsResult, xsID_width, xsInteger((xsIntegerValue)globalWidth), xsDefault); xsDefine(xsResult, xsID_height, xsInteger((xsIntegerValue)globalHeight), xsDefault); if (xsIsInstanceOf(xsArg(0), xsArrayBufferPrototype)) bytesInitial = xsToArrayBuffer(xsArg(0)); else bytesInitial = xsGetHostData(xsArg(0)); bytesLimit = bytesInitial + bufferSize; bytes = bytesInitial + bufferOffset; for (;;) { byte = BYTE; if (byte == 0x21) { byte = BYTE; if (byte == 0xF9) { bytes++; byte = BYTE; disposalMethod = ((byte & 0x1C) >> 2); transparentColorFlag = byte & 0x01; delay = BYTE; delay += BYTE << 8; transparentColorIndex = BYTE; bytes++; } else if ((byte == 0x01) || (byte == 0xFE) || (byte == 0xFF)) { while ((count = BYTE)) bytes += count; } else { xsUnknownError("invalid GIF extension"); } } else if (byte== 0x2C) { int prefixes[MAX_CODE]; int colorIndexes[MAX_CODE]; int buffer[MAX_CODE]; int* first = buffer; int* last; uint16_t x, y, width, height; uint8_t interlaceFlag; int colorIndexBitCount, codeBitCount, codeBitIndex, byteBitIndex; int codeAvailable, clearCode, endCode, nextCode, newCode, oldCode, colorIndex; bufferOffset = bytes - bytesInitial; xsVar(0) = xsArrayBuffer(NULL, (globalWidth * globalHeight * 2)); xsDefine(xsVar(0), xsID_delay, xsInteger((xsIntegerValue)delay * 10), xsDefault); xsCall1(xsResult, xsID_push, xsVar(0)); if (xsIsInstanceOf(xsArg(0), xsArrayBufferPrototype)) bytesInitial = xsToArrayBuffer(xsArg(0)); else bytesInitial = xsGetHostData(xsArg(0)); bytesLimit = bytesInitial + bufferSize; bytes = bytesInitial + bufferOffset; screen = xsToArrayBuffer(xsVar(0)); if (xsTest(xsVar(1))) { uint16_t* former = xsToArrayBuffer(xsVar(1)); c_memcpy(screen, former, (globalWidth * globalHeight * 2)); } else c_memset(screen, 0xFF, (globalWidth * globalHeight * 2)); xsVar(1) = xsVar(0); x = BYTE; x += BYTE << 8; y = BYTE; y += BYTE << 8; width = BYTE; width += BYTE << 8; height = BYTE; height += BYTE << 8; byte = BYTE; interlaceFlag = (byte & 0x40) >> 6; if (byte & 0x80) { int c = 2 << (byte & 0x07), i; for (i = 0; i < c; i++) { uint16_t r = BYTE; uint16_t g = BYTE; uint16_t b = BYTE; localColorTable[i] = (((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3)); } colorTable = localColorTable; } else colorTable = globalColorTable; row.begin = screen + (((y * globalWidth) + x)); row.current = row.begin; row.end = row.begin + width; row.delta = width; row.length = globalWidth; row.from = screen; row.to = screen + (globalWidth * globalHeight); row.transparentIndex = (transparentColorFlag) ? transparentColorIndex : -1; if (interlaceFlag) { row.pass = 1; row.first = row.begin; row.last = row.begin + (height * row.length); } else row.pass = 0; colorIndexBitCount = BYTE; clearCode = 1 << colorIndexBitCount; endCode = clearCode + 1; codeAvailable = 1 << colorIndexBitCount; codeBitCount = colorIndexBitCount + 1; nextCode = clearCode + 2; oldCode = -1; newCode = 0; codeBitIndex = 0; while ((count = BYTE)) { while (count) { byte = BYTE; count--; for (byteBitIndex = 0; byteBitIndex < 8; byteBitIndex++) { if (byte & byteMasks[byteBitIndex]) newCode |= codeMasks[codeBitIndex]; codeBitIndex++; if (codeBitIndex == codeBitCount) { if (newCode == endCode) { bytes += count; count = 0; break; } else if (newCode == clearCode) { codeAvailable = (1 << colorIndexBitCount) - 1; codeBitCount = colorIndexBitCount + 1; nextCode = clearCode + 2; oldCode = -1; } else { codeAvailable--; if (oldCode == -1) { colorIndex = oldCode = newCode; poke(&row, colorTable, colorIndex); } else { if (newCode >= nextCode) { *first = colorIndex; last = bufferColorIndexes(prefixes, colorIndexes, first + 1, oldCode, clearCode); } else { last = bufferColorIndexes(prefixes, colorIndexes, first, newCode, clearCode); } colorIndex = *last; while (last >= first) poke(&row, colorTable, *last--); if (nextCode < MAX_CODE) { prefixes[nextCode] = oldCode; colorIndexes[nextCode] = colorIndex; nextCode++; } oldCode = newCode; } } if ((codeAvailable == 0) && (codeBitCount < 12)) { codeAvailable = 1 << codeBitCount; codeBitCount++; } codeBitIndex = 0; newCode = 0; } } } } } else if (byte == 0x3B) { break; } else xsUnknownError("invalid GIF extension"); } }