scratch-sb1-converter
Version:
Scratch 1 (.sb) to Scratch 2 (.sb2) conversion library for Scratch 3.0
201 lines (179 loc) • 8.29 kB
JavaScript
import {BytePrimitive, Uint8, Uint32BE} from './byte-primitives';
import {ByteStream} from './byte-stream';
const defaultColorMap = [
0x00000000, 0xFF000000, 0xFFFFFFFF, 0xFF808080, 0xFFFF0000, 0xFF00FF00, 0xFF0000FF, 0xFF00FFFF,
0xFFFFFF00, 0xFFFF00FF, 0xFF202020, 0xFF404040, 0xFF606060, 0xFF9F9F9F, 0xFFBFBFBF, 0xFFDFDFDF,
0xFF080808, 0xFF101010, 0xFF181818, 0xFF282828, 0xFF303030, 0xFF383838, 0xFF484848, 0xFF505050,
0xFF585858, 0xFF686868, 0xFF707070, 0xFF787878, 0xFF878787, 0xFF8F8F8F, 0xFF979797, 0xFFA7A7A7,
0xFFAFAFAF, 0xFFB7B7B7, 0xFFC7C7C7, 0xFFCFCFCF, 0xFFD7D7D7, 0xFFE7E7E7, 0xFFEFEFEF, 0xFFF7F7F7,
0xFF000000, 0xFF003300, 0xFF006600, 0xFF009900, 0xFF00CC00, 0xFF00FF00, 0xFF000033, 0xFF003333,
0xFF006633, 0xFF009933, 0xFF00CC33, 0xFF00FF33, 0xFF000066, 0xFF003366, 0xFF006666, 0xFF009966,
0xFF00CC66, 0xFF00FF66, 0xFF000099, 0xFF003399, 0xFF006699, 0xFF009999, 0xFF00CC99, 0xFF00FF99,
0xFF0000CC, 0xFF0033CC, 0xFF0066CC, 0xFF0099CC, 0xFF00CCCC, 0xFF00FFCC, 0xFF0000FF, 0xFF0033FF,
0xFF0066FF, 0xFF0099FF, 0xFF00CCFF, 0xFF00FFFF, 0xFF330000, 0xFF333300, 0xFF336600, 0xFF339900,
0xFF33CC00, 0xFF33FF00, 0xFF330033, 0xFF333333, 0xFF336633, 0xFF339933, 0xFF33CC33, 0xFF33FF33,
0xFF330066, 0xFF333366, 0xFF336666, 0xFF339966, 0xFF33CC66, 0xFF33FF66, 0xFF330099, 0xFF333399,
0xFF336699, 0xFF339999, 0xFF33CC99, 0xFF33FF99, 0xFF3300CC, 0xFF3333CC, 0xFF3366CC, 0xFF3399CC,
0xFF33CCCC, 0xFF33FFCC, 0xFF3300FF, 0xFF3333FF, 0xFF3366FF, 0xFF3399FF, 0xFF33CCFF, 0xFF33FFFF,
0xFF660000, 0xFF663300, 0xFF666600, 0xFF669900, 0xFF66CC00, 0xFF66FF00, 0xFF660033, 0xFF663333,
0xFF666633, 0xFF669933, 0xFF66CC33, 0xFF66FF33, 0xFF660066, 0xFF663366, 0xFF666666, 0xFF669966,
0xFF66CC66, 0xFF66FF66, 0xFF660099, 0xFF663399, 0xFF666699, 0xFF669999, 0xFF66CC99, 0xFF66FF99,
0xFF6600CC, 0xFF6633CC, 0xFF6666CC, 0xFF6699CC, 0xFF66CCCC, 0xFF66FFCC, 0xFF6600FF, 0xFF6633FF,
0xFF6666FF, 0xFF6699FF, 0xFF66CCFF, 0xFF66FFFF, 0xFF990000, 0xFF993300, 0xFF996600, 0xFF999900,
0xFF99CC00, 0xFF99FF00, 0xFF990033, 0xFF993333, 0xFF996633, 0xFF999933, 0xFF99CC33, 0xFF99FF33,
0xFF990066, 0xFF993366, 0xFF996666, 0xFF999966, 0xFF99CC66, 0xFF99FF66, 0xFF990099, 0xFF993399,
0xFF996699, 0xFF999999, 0xFF99CC99, 0xFF99FF99, 0xFF9900CC, 0xFF9933CC, 0xFF9966CC, 0xFF9999CC,
0xFF99CCCC, 0xFF99FFCC, 0xFF9900FF, 0xFF9933FF, 0xFF9966FF, 0xFF9999FF, 0xFF99CCFF, 0xFF99FFFF,
0xFFCC0000, 0xFFCC3300, 0xFFCC6600, 0xFFCC9900, 0xFFCCCC00, 0xFFCCFF00, 0xFFCC0033, 0xFFCC3333,
0xFFCC6633, 0xFFCC9933, 0xFFCCCC33, 0xFFCCFF33, 0xFFCC0066, 0xFFCC3366, 0xFFCC6666, 0xFFCC9966,
0xFFCCCC66, 0xFFCCFF66, 0xFFCC0099, 0xFFCC3399, 0xFFCC6699, 0xFFCC9999, 0xFFCCCC99, 0xFFCCFF99,
0xFFCC00CC, 0xFFCC33CC, 0xFFCC66CC, 0xFFCC99CC, 0xFFCCCCCC, 0xFFCCFFCC, 0xFFCC00FF, 0xFFCC33FF,
0xFFCC66FF, 0xFFCC99FF, 0xFFCCCCFF, 0xFFCCFFFF, 0xFFFF0000, 0xFFFF3300, 0xFFFF6600, 0xFFFF9900,
0xFFFFCC00, 0xFFFFFF00, 0xFFFF0033, 0xFFFF3333, 0xFFFF6633, 0xFFFF9933, 0xFFFFCC33, 0xFFFFFF33,
0xFFFF0066, 0xFFFF3366, 0xFFFF6666, 0xFFFF9966, 0xFFFFCC66, 0xFFFFFF66, 0xFFFF0099, 0xFFFF3399,
0xFFFF6699, 0xFFFF9999, 0xFFFFCC99, 0xFFFFFF99, 0xFFFF00CC, 0xFFFF33CC, 0xFFFF66CC, 0xFFFF99CC,
0xFFFFCCCC, 0xFFFFFFCC, 0xFFFF00FF, 0xFFFF33FF, 0xFFFF66FF, 0xFFFF99FF, 0xFFFFCCFF, 0xFFFFFFFF];
const defaultOneBitColorMap = [0xFFFFFFFF, 0xFF000000];
const VariableIntBE = new BytePrimitive({
sizeOf (uint8a, position) {
const count = uint8a[position];
if (count <= 223) return 1;
if (count <= 254) return 2;
return 5;
},
read (uint8a, position) {
const count = uint8a[position];
if (count <= 223) return count;
if (count <= 254) return ((count - 224) * 256) + uint8a[position + 1];
return Uint32BE.read(uint8a, position + 1);
}
});
class SqueakImage {
decode (width, height, depth, bytes, colormap) {
const pixels = this.decodePixels(bytes, depth === 32);
if (depth <= 8) {
if (!colormap) {
colormap = depth === 1 ? defaultOneBitColorMap : defaultColorMap;
}
return this.unpackPixels(pixels, width, height, depth, colormap);
} else if (depth === 16) {
return this.raster16To32(pixels, width, height);
} else if (depth === 32) {
return pixels;
}
throw new Error('Unhandled Squeak Image depth.');
}
decodePixels (bytes, withAlpha) {
let result;
// Already decompressed
if (Array.isArray(bytes) || bytes instanceof Uint32Array) {
result = new Uint32Array(bytes);
if (withAlpha) {
for (let i = 0; i < result.length; i++) {
if (result[i] !== 0) {
result[i] = 0xff000000 | result[i];
}
}
}
return result;
}
const stream = new ByteStream(bytes.buffer, bytes.byteOffset);
const pixelsOut = stream.read(VariableIntBE);
result = new Uint32Array(pixelsOut);
let i = 0;
while (i < pixelsOut) {
const runLengthAndCode = stream.read(VariableIntBE);
const runLength = runLengthAndCode >> 2;
const code = runLengthAndCode & 0b11;
let w;
switch (code) {
case 0:
i += runLength;
break;
case 1:
w = stream.read(Uint8);
w = (w << 24) | (w << 16) | (w << 8) | w;
if (withAlpha && w !== 0) {
w |= 0xff000000;
}
for (let j = 0; j < runLength; j++) {
result[i++] = w;
}
break;
case 2:
w = stream.read(Uint32BE);
if (withAlpha && w !== 0) {
w |= 0xff000000;
}
for (let j = 0; j < runLength; j++) {
result[i++] = w;
}
break;
case 3:
for (let j = 0; j < runLength; j++) {
w = stream.read(Uint32BE);
if (withAlpha && w !== 0) {
w |= 0xff000000;
}
result[i++] = w;
}
}
}
return result;
}
unpackPixels (words, width, height, depth, colormap) {
const result = new Uint32Array(width * height);
const mask = (1 << depth) - 1;
const pixelsPerWord = 32 / depth;
let dst = 0;
let src = 0;
for (let y = 0; y < height; y++) {
let word;
let shift = -1;
for (let x = 0; x < width; x++) {
if (shift < 0) {
shift = depth * (pixelsPerWord - 1);
word = words[src++];
}
result[dst++] = colormap[(word >> shift) & mask];
shift -= depth;
}
}
return result;
}
raster16To32 (words, width, height) {
const result = new Uint32Array(2 * words.length);
let shift;
let word;
let pix;
let src = 0;
let dst = 0;
for (let y = 0; y < height; y++) {
shift = -1;
for (let x = 0; x < width; x++) {
if (shift < 0) {
shift = 16;
word = words[src++];
}
pix = (word >> shift) & 0xffff;
if (pix !== 0) {
const red = (pix >> 7) & 0b11111000;
const green = (pix >> 2) & 0b11111000;
const blue = (pix << 3) & 0b11111000;
pix = 0xff000000 | (red << 16) | (green << 8) | blue;
}
result[dst++] = pix;
shift -= 16;
}
}
return result;
}
buildCustomColormap (depth, colors, table) {
const result = new Uint32Array(1 << depth);
for (let i = 0; i < colors.length; i++) {
result[i] = table[colors[i].index - 1];
}
return result;
}
}
export {SqueakImage};