diff --git a/index.html b/index.html index 69ad34c..9ed6b53 100644 --- a/index.html +++ b/index.html @@ -22,18 +22,14 @@

- Currently just converts to grayscale and counts up unique blocks. - Next step: decimate if > 128 unique blocks per image, and combine - the most similar blocks in the output. + Currently converts to grayscale and reuses existing similar blocks as it goes, + increasing a similarity threshold (from 0) until the set fits in 128 chars. + Next step: correctly handle inverse video similarities + Further step: don't end up stuck between 64 and 128 blocks :D

-

Source video

-
- -

Work canvas

-
@@ -73,8 +69,50 @@ return Array.from(block).map((n) => n.toString(16)).join(''); } - function inverse(block) { - return block.map((n) => ~n & 0xf); + function inverse(pixel) { + return ~pixel & 0xf; + } + + function matchBlocks(a, b, threshold) { + for (let i = 0; i < blockWidth * blockHeight; i++) { + if (Math.abs(a[i] - b[i]) > threshold) { + return false; + } + } + return true; + } + + function matchBlocksInverse(a, b, threshold) { + for (let i = 0; i < blockWidth * blockHeight; i++) { + if (Math.abs(a[i] - inverse(b[i])) > threshold) { + return false; + } + } + return true; + } + + function drawChar(imageData, cx, cy, char, charset) { + let invert = Boolean(char & 0x80); + char &= 0x7f; + if (char >= charset.length) { + return; + } + let block = charset[char]; + for (let y = 0; y < blockHeight; y++) { + for (let x = 0; x < blockWidth; x++) { + let i = y * blockWidth + x; + let ii = (y + cy * blockHeight) * imageData.width + (x + cx * blockWidth); + let gray16 = block[i]; + if (invert) { + gray16 = inverse(gray16); + } + let gray256 = Math.round(gray16 * 255 / 15); + imageData.data[ii * 4] = gray256; + imageData.data[ii * 4 + 1] = gray256; + imageData.data[ii * 4 + 2] = gray256; + imageData.data[ii * 4 + 3] = 255; + } + } } function update() { @@ -121,35 +159,12 @@ // // First pass: uniques extraction // Convert the 4bpp pixel indices into hex strings - let blockMap = {}; let uniques = []; - /* - for (let i = 0; i < chars.length; i++) { - let char = chars[i]; - let block = blocks[char]; - let key = hexify(blocks[i]); - let keyInverse = hexify(inverse(block)); - if (blockMap[key]) { - char = blockMap[key]; - } else if (blockMap[keyInverse]) { - char = blockMap[keyInverse]; - } else { - char = uniques.push(block) - 1; - blockMap[key] = char; - blockMap[keyInverse] = char; - } - chars[i] = char; - } - */ + for (let threshold = 0; threshold < 16; threshold++) { charIter: - for (let i = 0; i < chars.length; i++) { - let char = chars[i]; - let block = blocks[char]; - if (!block) { - debugger - throw new Error('missing block'); - } + for (let i = 0; i < blocks.length; i++) { + let block = blocks[i]; fontMatch: for (let j = 0; j < uniques.length; j++) { @@ -158,14 +173,16 @@ debugger throw new Error('missing other'); } - for (let k = 0; k < blockWidth * blockHeight; k++) { - if (Math.abs(block[k] - other[k]) > threshold) { - continue fontMatch; - } + if (matchBlocks(block, other, threshold)) { + // we're close enough to reuse a character + chars[i] = j; + continue charIter; + } else if (matchBlocksInverse(block, other, threshold)) { + chars[i] = j | 0x8000; + continue charIter; + } else { + continue fontMatch; } - // we're close enough to reuse a character - chars[i] = j; - continue charIter; } // add a new char chars[i] = uniques.push(block) - 1; @@ -174,55 +191,34 @@ break; } // We need to decimate further - blocks = uniques; uniques = []; } let span = document.querySelector('#block-count'); span.textContent = `${uniques.length}`; - // Font (currently wrong! :D) + // Font let fontCtx = document.querySelector('#font').getContext('2d'); let font = fontCtx.createImageData(16 * blockWidth, 16 * blockHeight); for (let hi = 0; hi < 16; hi++) { for (let lo = 0; lo < 16; lo++) { let char = (hi << 4) | lo; - let invert = Boolean(char & 0x80); - char &= 0x7f; - if (char >= uniques.length) { - continue; - } - let block = uniques[char]; - for (let y = 0; y < blockHeight; y++) { - for (let x = 0; x < blockWidth; x++) { - let i = y * blockWidth + x; - let ii = (y + hi * blockHeight) * 16 * blockWidth + (x + lo * blockWidth); - if (block.length < i) { - debugger; - } - let gray16 = block[i]; - if (invert) { - gray16 = ~gray16 & 0x0f; - } - let gray256 = Math.round(gray16 * 255 / 15); - font.data[ii * 4] = gray256; - font.data[ii * 4 + 1] = gray256; - font.data[ii * 4 + 2] = gray256; - font.data[ii * 4 + 3] = 255; - } - } + drawChar(font, lo, hi, char, uniques); } } fontCtx.putImageData(font, 0, 0); // Redraw the blocks - for (let y = 0; y < height; y++) { - for (let x = 0; x < width; x++) { - let i = y * width + x; - let gray16 = pixels[i]; - let gray256 = Math.round(gray16 * 255 / 15); - data[i * 4] = gray256; - data[i * 4 + 1] = gray256; - data[i * 4 + 2] = gray256; + + for (let cy = 0; cy < heightBlocks; cy++) { + for (let cx = 0; cx < widthBlocks; cx++) { + let i = cy * widthBlocks + cx; + let char = chars[i]; + if (char & 0x8000) { + // we use a bigger bit during earlier stages + char &= 0x7f; + char |= 0x80; + } + drawChar(bits, cx, cy, char, uniques); } } ctx.putImageData(bits, 0, 0); @@ -231,7 +227,10 @@ let timer = null; source.addEventListener('playing', () => { if (!timer) { - timer = setInterval(update, 1000 / 10); + // target 8 fps + // not sure we can get any faster + // downloads over sio + timer = setInterval(update, 1000 / 8); } update(); });