Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // Shared :: "Legacy/Graphics/Minimal Video Exchange.js"
- // Shared :: "Legacy/Graphics/Load TIL.js"
- // Function to load a TIL file with variable bit depth (GW-BASIC TILE format).
- function loadTIL(fileData, tileWidth, tileHeight, bitDepth) {
- const tileSize = (tileWidth * tileHeight * bitDepth) / 8;
- const tiles = [];
- const numTiles = fileData.length / tileSize;
- for (let i = 0; i < numTiles; i++) {
- const tile = new Uint8Array(tileWidth * tileHeight);
- const tileData = fileData.slice(i * tileSize, (i + 1) * tileSize);
- switch(bitDepth) {
- case 8:
- for (let j = 0; j < tileWidth * tileHeight; j++) {
- tile[j] = tileData[j];
- }
- break;
- case 4:
- for (let j = 0; j < tileWidth * tileHeight / 2; j++) {
- tile[j * 2] = (tileData[j] & 0xF0) >> 4;
- tile[j * 2 + 1] = tileData[j] & 0x0F;
- }
- break;
- case 2:
- for (let j = 0; j < tileWidth * tileHeight / 4; j++) {
- tile[j * 2 + 0] = (tileData[j] & 0xC0) >> 6;
- tile[j * 2 + 1] = (tileData[j] & 0x30) >> 4;
- tile[j * 2 + 2] = (tileData[j] & 0x0C) >> 2;
- tile[j * 2 + 3] = (tileData[j] & 0x03) >> 0;
- }
- break;
- case 1:
- for (let j = 0; j < tileData.length; j++) {
- for (let bit = 0; bit < 8; bit++) {
- tile[j * 8 + bit] = (tileData[j] >> (7 - bit)) & 0x01;
- }
- }
- break;
- default:
- throw new Error(`Unsupported bit depth: ${bitDepth}`);
- }
- tiles.push(tile);
- }
- return tiles;
- }
- // Shared :: "Legacy/Graphics/Save TIL.js"
- // Function to save a TIL file with variable bit depth (GW-BASIC TILE format).
- function saveTIL(tiles, tileWidth, tileHeight, bitDepth) {
- const tileSize = (tileWidth * tileHeight * bitDepth) / 8;
- const numTiles = tiles.length;
- const fileData = new Uint8Array(numTiles * tileSize);
- for (let i = 0; i < numTiles; i++) {
- const tile = tiles[i];
- switch (bitDepth){
- case 8:
- for (let j = 0; j < tileWidth * tileHeight; j++) {
- fileData[i * tileSize + j] = tile[j];
- }
- break;
- case 4:
- for (let j = 0; j < tileWidth * tileHeight / 2; j++) {
- const highNibble = tile[j * 2] << 4;
- const lowNibble = tile[j * 2 + 1] & 0x0F;
- fileData[i * tileSize + j] = highNibble | lowNibble;
- }
- break;
- case 2:
- for (let j = 0; j < tileWidth * tileHeight / 2; j++) {
- const bitC0 = ( tile[j * 4 + 0 ] << 6 ) & 0xC0;
- const bit30 = ( tile[j * 4 + 1 ] << 4 ) & 0x30;
- const bit0C = ( tile[j * 4 + 2 ] << 2 ) & 0x0C;
- const bit03 = ( tile[j * 4 + 3 ] << 0 ) & 0x03;
- fileData[i * tileSize + j] = bitC0 | bit30 | bit0C | bit03;
- }
- break;
- case 1:
- for (let j = 0; j < tileWidth * tileHeight / 8; j++) {
- let byte = 0;
- for (let bit = 0; bit < 8; bit++) {
- const pixelValue = tile[j * 8 + bit] & 0x01;
- byte |= (pixelValue << (7 - bit));
- }
- fileData[i * tileSize + j] = byte;
- }
- break;
- default:
- throw new Error(`Unsupported bit depth: ${bitDepth}`);
- }
- }
- return fileData;
- }
- // Shared :: "Legacy/Graphics/Load BSV.js"
- // Function to load a BSV file with variable bit depth (QBASIC BSAVE format).
- function loadBSV(fileData, bitDepth, tileWidth, tileHeight) {
- const headerSize = 7; // Standard BSAVE header size in bytes.
- const imageSize = calculateImageSize(bitDepth, tileWidth, tileHeight);
- const header = fileData.slice(0, headerSize); // First 7 bytes are the header.
- const pixelData = fileData.slice(headerSize, headerSize + imageSize); // The pixel data follows.
- const pixels = new Uint8Array(tileWidth * tileHeight);
- switch(bitDepth){
- case 8:
- for (let i = 0; i < pixelData.length; i++) {
- pixels[i] = pixelData[i]; // Direct mapping for 8-bit depth.
- }
- break;
- case 4:
- for (let i = 0; i < pixelData.length; i++) {
- pixels[i * 2 + 0] = (pixelData[i] & 0xF0) >> 4; // High nibble.
- pixels[i * 2 + 1] = pixelData[i] & 0x0F; // Low nibble.
- }
- break;
- case 2:
- for (let i = 0; i < pixelData.length; i++) {
- pixels[i * 4 + 0] = (pixelData[i] & 0xC0) >> 2; // High nibble.
- pixels[i * 4 + 1] = pixelData[i] & 0x30; // Low nibble.
- pixels[i * 4 + 2] = pixelData[i] & 0x0C; // Low nibble.
- pixels[i * 4 + 3] = pixelData[i] & 0x03; // Low nibble.
- }
- break;
- case 1:
- for (let i = 0; i < pixelData.length; i++) {
- for (let bit = 0; bit < 8; bit++) {
- const pixelValue = (pixelData[i] >> (7 - bit)) & 0x01;
- pixels[i * 8 + bit] = pixelValue; // Extract individual bits.
- }
- }
- break;
- default:
- throw new Error(`Unsupported bit depth: ${bitDepth}`);
- }
- return pixels;
- }
- // Shared :: "Legacy/Graphics/Save BSV.js"
- // Function to save a BSV file with variable bit depth (QBASIC BSAVE format).
- function saveBSV(pixels, bitDepth, tileWidth, tileHeight) {
- const headerSize = 7; // Standard BSAVE header size.
- const imageSize = calculateImageSize(bitDepth, tileWidth, tileHeight);
- const fileData = new Uint8Array(headerSize + imageSize);
- // Set up the BSAVE header.
- fileData[0] = 0xFD; // 'BSAVE' magic byte.
- fileData[1] = 0x00; // Offset (2 bytes), usually 0x0000 for mode 13h.
- fileData[2] = 0x00;
- fileData[3] = tileWidth & 0xFF; // Tile width.
- fileData[4] = (tileWidth >> 8) & 0xFF;
- fileData[5] = tileHeight & 0xFF; // Tile height.
- fileData[6] = (tileHeight >> 8) & 0xFF;
- switch(bitDepth){
- case 8:
- for (let i = 0; i < tileWidth * tileHeight; i++) {
- fileData[headerSize + i] = pixels[i]; // Direct mapping for 8-bit depth.
- }
- break;
- case 4:
- for (let i = 0; i < tileWidth * tileHeight / 2; i++) {
- const highNibble = (pixels[i * 2 + 0] << 4) & 0xF0;
- const lowNibble = (pixels[i * 2 + 1] << 0) & 0x0F;
- fileData[headerSize + i] = highNibble | lowNibble;
- }
- break;
- case 2:
- for (let i = 0; i < tileWidth * tileHeight / 4; i++) {
- const bitsC0 = (pixels[i * 4 + 0] << 6) & 0xC0;
- const bits30 = (pixels[i * 4 + 1] << 4) & 0x30;
- const bits0C = (pixels[i * 4 + 2] << 2) & 0x0C;
- const bits03 = (pixels[i * 4 + 3] << 0) & 0x03;
- fileData[headerSize + i] = bitsC0 | bits30 | bits0C | bits03;
- }
- break;
- case 1:
- for (let i = 0; i < tileWidth * tileHeight / 8; i++) {
- let byte = 0;
- for (let bit = 0; bit < 8; bit++) {
- const pixelValue = pixels[i * 8 + bit] & 0x01;
- byte |= (pixelValue << (7 - bit));
- }
- fileData[headerSize + i] = byte;
- }
- break;
- default:
- throw new Error(`Unsupported bit depth: ${bitDepth}`);
- }
- return fileData;
- }
- // Shared :: "Legacy/Graphics/Support.js"
- // Function to calculate image size based on bit depth.
- function calculateImageSize(bitDepth, tileWidth, tileHeight) {
- return (tileWidth * tileHeight * bitDepth) / 8;
- }
- // Shared :: "Legacy/Graphics/Rip MVX.js"
- // "Keal's Minimal Video Exchange Format"
- function Import(params = { type: `bsv`, fileData = [], bitDepth = 8 }) {
- let pixels = [];
- let tiles = [];
- switch (params.type.toLowerCase()) {
- case `bsv`:
- tiles[0] = loadBSV(params.fileData, params.bitDepth);
- break;
- case `til`:
- tiles = loadTIL(params.fileData, params.tileWidth, params.tileHeight, params.bitDepth);
- break;
- default:
- break;
- }
- }
- function eachAsHex(tiles, width, height) {
- let rips = [];
- let ripsUnique = [];
- let transformations = [];
- for (let i = 0; i < tiles.length; i++) {
- rips[i] = asHex(tiles[i], width, height);
- ripsUnique[i] = asUnique(rips[i], width, height);
- transformations[i] = generateTransformations(tiles[i], width, height);
- }
- return { ripsUnique, transformations };
- }
- // Function to generate flipped and rotated versions of the tile
- function generateTransformations(tile, width, height) {
- let transformed = {};
- transformed.original = asHex(tile, width, height);
- transformed.flippedHorizontal = asHex(flipHorizontal(tile, width, height), width, height);
- transformed.flippedVertical = asHex(flipVertical(tile, width, height), width, height);
- transformed.rotated90 = asHex(rotate90(tile, width, height), height, width);
- transformed.rotated180 = asHex(rotate180(tile, width, height), width, height);
- transformed.rotated270 = asHex(rotate270(tile, width, height), height, width);
- return transformed;
- }
- // Flip the tile horizontally
- function flipHorizontal(tile, width, height) {
- let flipped = new Uint8Array(tile.length);
- for (let y = 0; y < height; y++) {
- for (let x = 0; x < width; x++) {
- flipped[y * width + x] = tile[y * width + (width - x - 1)];
- }
- }
- return flipped;
- }
- // Flip the tile vertically
- function flipVertical(tile, width, height) {
- let flipped = new Uint8Array(tile.length);
- for (let y = 0; y < height; y++) {
- for (let x = 0; x < width; x++) {
- flipped[y * width + x] = tile[(height - y - 1) * width + x];
- }
- }
- return flipped;
- }
- // Rotate the tile by 90 degrees clockwise
- function rotate90(tile, width, height) {
- let rotated = new Uint8Array(tile.length);
- for (let y = 0; y < height; y++) {
- for (let x = 0; x < width; x++) {
- rotated[x * height + (height - y - 1)] = tile[y * width + x];
- }
- }
- return rotated;
- }
- // Rotate the tile by 180 degrees
- function rotate180(tile, width, height) {
- let rotated = new Uint8Array(tile.length);
- for (let y = 0; y < height; y++) {
- for (let x = 0; x < width; x++) {
- rotated[(height - y - 1) * width + (width - x - 1)] = tile[y * width + x];
- }
- }
- return rotated;
- }
- // Rotate the tile by 270 degrees clockwise
- function rotate270(tile, width, height) {
- let rotated = new Uint8Array(tile.length);
- for (let y = 0; y < height; y++) {
- for (let x = 0; x < width; x++) {
- rotated[(width - x - 1) * height + y] = tile[y * width + x];
- }
- }
- return rotated;
- }
- function asHex(tile, width, height) {
- const depth = 32; // Assuming each pixel is 32 bits (4 bytes)
- let hexData = ""; // Empty string to store hex values
- for (let y = 0; y < height; y++) {
- for (let x = 0; x < width; x++) {
- const pixelIndex = y * width + x; // Calculate the current pixel index
- const pixelValue = tile[pixelIndex]; // Get the value of the pixel
- const hexPixel = pixelValue.toString(16).padStart(depth / 4, '0'); // Convert pixel value to hex
- hexData += hexPixel;
- }
- }
- return hexData;
- }
- function asUnique(hexData, width, height) {
- const depth = 32;
- let palette = ""; // To store unique hex values (colors)
- let field = ""; // To store indices for the unique palette
- for (let y = 0; y < height; y++) {
- for (let x = 0; x < width; x++) {
- let startIndex = (y * width + x) * (depth / 4); // Calculate start index for the current pixel in hexData
- let pixel = hexData.slice(startIndex, startIndex + (depth / 4)); // Extract the hex value for the pixel
- let paletteIndex = palette.indexOf(pixel);
- if (paletteIndex === -1) {
- paletteIndex = palette.length / (depth / 4); // Calculate the new index
- palette += pixel; // Add the new pixel to the palette
- }
- field += paletteIndex.toString(16).padStart(2, '0'); // Assuming a 2-digit hex index
- }
- }
- return { palette, field };
- }
- function paletteUnique(ripsUnique) {
- let ripsMaster = { palette: "", field: "" };
- let allPalettes = ripsUnique.map(ru => ru.palette).join('');
- let allFields = ripsUnique.map(ru => ru.field).join('');
- for (let i = 0; i < ripsUnique.length; i++) {
- let current = ripsUnique[i];
- let paletteIndex = allPalettes.indexOf(current.palette);
- let fieldIndex = allFields.indexOf(current.field);
- if (paletteIndex !== -1 && paletteIndex <= i * current.palette.length) {
- ripsMaster.palette += paletteIndex.toString(16).padStart(2, '0');
- } else {
- ripsMaster.palette += i.toString(16).padStart(2, '0');
- }
- if (fieldIndex !== -1 && fieldIndex <= i * current.field.length) {
- ripsMaster.field += fieldIndex.toString(16).padStart(2, '0');
- } else {
- ripsMaster.field += i.toString(16).padStart(2, '0');
- }
- }
- return ripsMaster;
- }
- // Function to create a frequency table from the data
- function createFrequencyTable(data) {
- let freqTable = {};
- for (let i = 0; i < data.length; i++) {
- let char = data[i];
- freqTable[char] = (freqTable[char] || 0) + 1;
- }
- return freqTable;
- }
- // Function to create Huffman tree from the frequency table
- function createHuffmanTree(freqTable) {
- let nodes = Object.entries(freqTable).map(([char, freq]) => ({ char, freq }));
- while (nodes.length > 1) {
- nodes.sort((a, b) => a.freq - b.freq);
- let left = nodes.shift();
- let right = nodes.shift();
- let newNode = { char: null, freq: left.freq + right.freq, left, right };
- nodes.push(newNode);
- }
- return nodes[0];
- }
- // Function to generate Huffman codes from the tree
- function generateHuffmanCodes(tree, prefix = '', codes = {}) {
- if (tree.char !== null) {
- codes[tree.char] = prefix;
- } else {
- generateHuffmanCodes(tree.left, prefix + '0', codes);
- generateHuffmanCodes(tree.right, prefix + '1', codes);
- }
- return codes;
- }
- // Function to compress data using Huffman codes
- function huffmanCompress(data, huffmanCodes) {
- let binaryString = '';
- for (let i = 0; i < data.length; i++) {
- binaryString += huffmanCodes[data[i]];
- }
- let byteArray = [];
- for (let i = 0; i < binaryString.length; i += 8) {
- let byte = binaryString.slice(i, i + 8);
- byteArray.push(parseInt(byte.padEnd(8, '0'), 2)); // Pack into bytes
- }
- return new Uint8Array(byteArray);
- }
- // Function to unpack the compressed byte array into a binary string
- function unpackBits(byteArray) {
- let binaryString = '';
- for (let i = 0; i < byteArray.length; i++) {
- let byte = byteArray[i].toString(2).padStart(8, '0');
- binaryString += byte;
- }
- return binaryString;
- }
- // Function to rebuild the Huffman tree from the Huffman codes
- function rebuildHuffmanTree(huffmanCodes) {
- let root = {};
- for (let char in huffmanCodes) {
- let code = huffmanCodes[char];
- let node = root;
- for (let bit of code) {
- if (!node[bit]) node[bit] = {};
- node = node[bit];
- }
- node.char = char;
- }
- return root;
- }
- // Function to decode the compressed binary data using the Huffman tree
- function huffmanDecompress(binaryString, huffmanTree) {
- let originalData = '';
- let node = huffmanTree;
- for (let bit of binaryString) {
- node = node[bit]; // Traverse the tree
- if (node.char) {
- originalData += node.char;
- node = huffmanTree; // Reset to root
- }
- }
- return originalData;
- }
- // Rebuild images from the original data
- function rebuildImages(palette, field, width, height, depth = 32) {
- let tiles = [];
- let paletteEntries = [];
- for (let i = 0; i < palette.length; i += (depth / 4)) {
- paletteEntries.push(palette.slice(i, i + (depth / 4)));
- }
- for (let i = 0; i < field.length / (width * height); i++) {
- let tile = new Uint8Array(width * height);
- for (let j = 0; j < width * height; j++) {
- let paletteIndex = parseInt(field.slice((i * width * height + j) * 2, (i * width * height + j) * 2 + 2), 16);
- let color = parseInt(paletteEntries[paletteIndex], 16);
- tile[j] = color;
- }
- tiles.push(tile);
- }
- return tiles;
- }
- // Full reversal process to convert compressed data back into original images
- function decompressMasterData(compressedData, huffmanCodes, width, height, depth = 32) {
- let binaryString = unpackBits(compressedData);
- let huffmanTree = rebuildHuffmanTree(huffmanCodes);
- let originalData = huffmanDecompress(binaryString, huffmanTree);
- let paletteLength = originalData.indexOf("fieldStart");
- let palette = originalData.slice(0, paletteLength);
- let field = originalData.slice(paletteLength + "fieldStart".length);
- let originalTiles = rebuildImages(palette, field, width, height, depth);
- return originalTiles;
- }
- // Example Huffman codes (must be saved or generated along with compression)
- let huffmanCodes = {
- 'a': '00',
- 'b': '01',
- 'c': '10',
- 'd': '110',
- 'e': '1110',
- 'f': '1111'
- };
- // Function to decompress the Huffman data back to BSV format
- function decompressToBSV(compressedData, huffmanCodes, width, height, bitDepth) {
- let binaryString = unpackBits(compressedData);
- let huffmanTree = rebuildHuffmanTree(huffmanCodes);
- let originalData = huffmanDecompress(binaryString, huffmanTree);
- // Split palette and field data
- let paletteLength = originalData.indexOf("fieldStart");
- let palette = originalData.slice(0, paletteLength);
- let field = originalData.slice(paletteLength + "fieldStart".length);
- // Rebuild image tiles
- let originalTiles = rebuildImages(palette, field, width, height, bitDepth);
- // Convert image back into BSV format
- let bsvFile = saveBSV(originalTiles, bitDepth, width, height);
- return bsvFile;
- }
- // Function to decompress the Huffman data back to TIL format
- function decompressToTIL(compressedData, huffmanCodes, width, height, bitDepth, tileWidth, tileHeight) {
- let binaryString = unpackBits(compressedData);
- let huffmanTree = rebuildHuffmanTree(huffmanCodes);
- let originalData = huffmanDecompress(binaryString, huffmanTree);
- // Split palette and field data
- let paletteLength = originalData.indexOf("fieldStart");
- let palette = originalData.slice(0, paletteLength);
- let field = originalData.slice(paletteLength + "fieldStart".length);
- // Rebuild image tiles
- let originalTiles = rebuildImages(palette, field, tileWidth, tileHeight, bitDepth);
- // Convert image back into TIL format
- let tilFile = saveTIL(originalTiles, tileWidth, tileHeight, bitDepth);
- return tilFile;
- }
- // Example of compressing and decompressing the data
- let ripsUnique = eachAsHex(tiles, width, height);
- let masterData = paletteUnique(ripsUnique.ripsUnique);
- let compressedMasterData = huffmanCompress(masterData);
- // Decompress and save to BSV
- let originalBSVFile = decompressToBSV(compressedMasterData, huffmanCodes, width, height, bitDepth);
- console.log("Original BSV:", originalBSVFile);
- // Decompress and save to TIL
- let originalTILFile = decompressToTIL(compressedMasterData, huffmanCodes, width, height, bitDepth, tileWidth, tileHeight);
- console.log("Original TIL:", originalTILFile);
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement