function createPalette(RGB6Bit) { "use strict"; var RGBAColours = RGB6Bit.map((RGB6Bit) => { return new Uint8Array( [ RGB6Bit[0] << 2 | RGB6Bit[0] >> 4, RGB6Bit[1] << 2 | RGB6Bit[1] >> 4, RGB6Bit[2] << 2 | RGB6Bit[2] >> 4, 255 ] ); }); var foreground = 7; var background = 0; function getRGBAColour(index) { return RGBAColours[index]; } function getForegroundColour() { return foreground; } function getBackgroundColour() { return background; } function setForegroundColour(newForeground) { foreground = newForeground; document.dispatchEvent(new CustomEvent("onForegroundChange", {"detail": foreground})); } function setBackgroundColour(newBackground) { background = newBackground; document.dispatchEvent(new CustomEvent("onBackgroundChange", {"detail": background})); } return { "getRGBAColour": getRGBAColour, "getForegroundColour": getForegroundColour, "getBackgroundColour": getBackgroundColour, "setForegroundColour": setForegroundColour, "setBackgroundColour": setBackgroundColour }; } function createDefaultPalette() { "use strict"; return createPalette([ [0, 0, 0], [0, 0, 42], [0, 42, 0], [0, 42, 42], [42, 0, 0], [42, 0, 42], [42, 21, 0], [42, 42, 42], [21, 21, 21], [21, 21, 63], [21, 63, 21], [21, 63, 63], [63, 21, 21], [63, 21, 63], [63, 63, 21], [63, 63, 63] ]); } function createPalettePreview(canvas) { "use strict"; var imageData; function updatePreview() { var colour; var foreground = palette.getRGBAColour(palette.getForegroundColour()); var background = palette.getRGBAColour(palette.getBackgroundColour()); for (var y = 0, i = 0; y < canvas.height; y++) { for (var x = 0; x < canvas.width; x++, i += 4) { if (y >= 10 && y < canvas.height - 10 && x > 10 && x < canvas.width - 10) { colour = foreground; } else { colour = background; } imageData.data.set(colour, i); } } canvas.getContext("2d").putImageData(imageData, 0, 0); } imageData = canvas.getContext("2d").createImageData(canvas.width, canvas.height); updatePreview(); document.addEventListener("onForegroundChange", updatePreview); document.addEventListener("onBackgroundChange", updatePreview); return { "setForegroundColour": updatePreview, "setBackgroundColour": updatePreview }; } function createPalettePicker(canvas) { "use strict"; var imageData = []; function updateColor(index) { var colour = palette.getRGBAColour(index); for (var y = 0, i = 0; y < imageData[index].height; y++) { for (var x = 0; x < imageData[index].width; x++, i += 4) { imageData[index].data.set(colour, i); } } canvas.getContext("2d").putImageData(imageData[index], (index > 7) ? (canvas.width / 2) : 0, (index % 8) * imageData[index].height); } function updatePalette() { for (var i = 0; i < 16; i++) { updateColor(i); } } function touchStart(evt) { var rect = canvas.getBoundingClientRect(); var x = Math.floor((evt.touches[0].pageX - rect.left) / (canvas.width / 2)); var y = Math.floor((evt.touches[0].pageY - rect.top) / (canvas.height / 8)); var colourIndex = y + ((x === 0) ? 0 : 8); // @todo fix meta keys if (evt.ctrlKey === false && evt.which != 3) { palette.setForegroundColour(colourIndex); } else { palette.setBackgroundColour(colourIndex); } } function mouseDown(evt) { var rect = canvas.getBoundingClientRect(); var x = Math.floor((evt.clientX - rect.left) / (canvas.width / 2)); var y = Math.floor((evt.clientY - rect.top) / (canvas.height / 8)); var colourIndex = y + ((x === 0) ? 0 : 8); if (evt.ctrlKey === false && evt.which != 3) { palette.setForegroundColour(colourIndex); } else { palette.setBackgroundColour(colourIndex); } } for (var i = 0; i < 16; i++) { imageData[i] = canvas.getContext("2d").createImageData(canvas.width / 2, canvas.height / 8); } function keydown(evt) { var keyCode = (evt.keyCode || evt.which); if (keyCode >= 48 && keyCode <= 55) { var num = keyCode - 48; if (evt.ctrlKey === true) { evt.preventDefault(); if (palette.getForegroundColour() === num) { palette.setForegroundColour(num + 8); } else { palette.setForegroundColour(num); } } else if (evt.altKey) { evt.preventDefault(); if (palette.getBackgroundColour() === num) { palette.setBackgroundColour(num + 8); } else { palette.setBackgroundColour(num); } } } else if (keyCode >= 37 && keyCode <= 40 && evt.ctrlKey === true){ evt.preventDefault(); switch(keyCode) { case 37: var colour = palette.getBackgroundColour(); colour = (colour === 0) ? 15 : (colour - 1); palette.setBackgroundColour(colour); break; case 38: var colour = palette.getForegroundColour(); colour = (colour === 0) ? 15 : (colour - 1); palette.setForegroundColour(colour); break; case 39: var colour = palette.getBackgroundColour(); colour = (colour === 15) ? 0 : (colour + 1); palette.setBackgroundColour(colour); break; case 40: var colour = palette.getForegroundColour(); colour = (colour === 15) ? 0 : (colour + 1); palette.setForegroundColour(colour); break; default: break; } } } updatePalette(); canvas.addEventListener("touchstart", touchStart); canvas.addEventListener("mousedown", mouseDown); canvas.addEventListener("contextmenu", (evt) => { evt.preventDefault(); }); document.addEventListener("keydown", keydown); } function loadImageAndGetImageData(url, callback) { "use strict"; var imgElement = new Image(); imgElement.addEventListener("load", () => { var canvas = createCanvas(imgElement.width, imgElement.height); var ctx = canvas.getContext("2d"); ctx.drawImage(imgElement, 0, 0); var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); callback(imageData); }); imgElement.addEventListener("error", () => { callback(undefined); }); imgElement.src = url; } function loadFontFromImage(fontName, letterSpacing, palette, callback) { "use strict"; var fontData = {}; var fontGlyphs; var alphaGlyphs; var letterSpacingImageData; function parseFontData(imageData) { var fontWidth = imageData.width / 16; var fontHeight = imageData.height / 16; if ((fontWidth === 8) && (imageData.height % 16 === 0) && (fontHeight >= 1 && fontHeight <= 32)) { var data = new Uint8Array(fontWidth * fontHeight * 256 / 8); var k = 0; for (var value = 0; value < 256; value += 1) { var x = (value % 16) * fontWidth; var y = Math.floor(value / 16) * fontHeight; var pos = (y * imageData.width + x) * 4; var i = 0; while (i < fontWidth * fontHeight) { data[k] = data[k] << 1; if (imageData.data[pos] > 127) { data[k] += 1; } if ((i += 1) % fontWidth === 0) { pos += (imageData.width - 8) * 4; } if (i % 8 === 0) { k += 1; } pos += 4; } } return { "width": fontWidth, "height": fontHeight, "data": data }; } return undefined; } function generateNewFontGlyphs() { var canvas = createCanvas(fontData.width, fontData.height); var ctx = canvas.getContext("2d"); var bits = new Uint8Array(fontData.width * fontData.height * 256); for (var i = 0, k = 0; i < fontData.width * fontData.height * 256 / 8; i += 1) { for (var j = 7; j >= 0; j -= 1, k += 1) { bits[k] = (fontData.data[i] >> j) & 1; } } fontGlyphs = new Array(16); for (var foreground = 0; foreground < 16; foreground++) { fontGlyphs[foreground] = new Array(16); for (var background = 0; background < 16; background++) { fontGlyphs[foreground][background] = new Array(256); for (var charCode = 0; charCode < 256; charCode++) { fontGlyphs[foreground][background][charCode] = ctx.createImageData(fontData.width, fontData.height); for (var i = 0, j = charCode * fontData.width * fontData.height; i < fontData.width * fontData.height; i += 1, j += 1) { var colour = palette.getRGBAColour((bits[j] === 1) ? foreground : background); fontGlyphs[foreground][background][charCode].data.set(colour, i * 4); } } } } alphaGlyphs = new Array(16); for (var foreground = 0; foreground < 16; foreground++) { alphaGlyphs[foreground] = new Array(256); for (var charCode = 0; charCode < 256; charCode++) { if (charCode === 220 || charCode === 223) { var imageData = ctx.createImageData(fontData.width, fontData.height); for (var i = 0, j = charCode * fontData.width * fontData.height; i < fontData.width * fontData.height; i += 1, j += 1) { if (bits[j] === 1) { imageData.data.set(palette.getRGBAColour(foreground), i * 4); } } var alphaCanvas = createCanvas(imageData.width, imageData.height); alphaCanvas.getContext("2d").putImageData(imageData, 0, 0); alphaGlyphs[foreground][charCode] = alphaCanvas; } } } letterSpacingImageData = new Array(16); for (var i = 0; i < 16; i++) { var canvas = createCanvas(1, fontData.height); var ctx = canvas.getContext("2d"); var imageData = ctx.getImageData(0, 0, 1, fontData.height); var colour = palette.getRGBAColour(i); for (var j = 0; j < fontData.height; j++) { imageData.data.set(colour, j * 4); } letterSpacingImageData[i] = imageData; } } function getWidth() { if (letterSpacing === true) { return fontData.width + 1; } return fontData.width; } function getHeight() { return fontData.height; } function setLetterSpacing(newLetterSpacing) { if (newLetterSpacing !== letterSpacing) { generateNewFontGlyphs(); letterSpacing = newLetterSpacing; document.dispatchEvent(new CustomEvent("onLetterSpacingChange", {"detail": letterSpacing})); } } function getLetterSpacing() { return letterSpacing; } loadImageAndGetImageData("fonts/" + fontName + ".png", (imageData) => { if (imageData === undefined) { callback(false); } else { var newFontData = parseFontData(imageData); if (newFontData === undefined) { callback(false); } else { fontData = newFontData; generateNewFontGlyphs(); callback(true); } } }); function draw(charCode, foreground, background, ctx, x, y) { if (letterSpacing === true) { ctx.putImageData(fontGlyphs[foreground][background][charCode], x * (fontData.width + 1), y * fontData.height); if (charCode >= 192 && charCode <= 223) { ctx.putImageData(fontGlyphs[foreground][background][charCode], x * (fontData.width + 1) + 1, y * fontData.height, fontData.width - 1, 0, 1, fontData.height); } else { ctx.putImageData(letterSpacingImageData[background], x * (fontData.width + 1) + 8, y * fontData.height); } } else { ctx.putImageData(fontGlyphs[foreground][background][charCode], x * fontData.width, y * fontData.height); } } function drawWithAlpha(charCode, foreground, ctx, x, y) { if (letterSpacing === true) { ctx.drawImage(alphaGlyphs[foreground][charCode], x * (fontData.width + 1), y * fontData.height); if (charCode >= 192 && charCode <= 223) { ctx.drawImage(alphaGlyphs[foreground][charCode], fontData.width - 1, 0, 1, fontData.height, x * (fontData.width + 1) + fontData.width, y * fontData.height, 1, fontData.height); } } else { ctx.drawImage(alphaGlyphs[foreground][charCode], x * fontData.width, y * fontData.height); } } return { "getWidth": getWidth, "getHeight": getHeight, "setLetterSpacing": setLetterSpacing, "getLetterSpacing": getLetterSpacing, "draw": draw, "drawWithAlpha": drawWithAlpha }; } function createTextArtCanvas(canvasContainer, callback) { "use strict"; var columns = 80, rows = 25, iceColours = false, imageData = new Uint16Array(columns * rows), canvases, ctxs, offBlinkCanvases, onBlinkCanvases, offBlinkCtxs, onBlinkCtxs, blinkTimer, blinkOn = false, mouseButton = false, currentUndo = [], undoBuffer = [], redoBuffer = [], drawHistory = []; function updateBeforeBlinkFlip(x, y) { var dataIndex = y * columns + x; var contextIndex = Math.floor(y / 25); var contextY = y % 25; var charCode = imageData[dataIndex] >> 8; var background = (imageData[dataIndex] >> 4) & 15; var foreground = imageData[dataIndex] & 15; var shifted = background >= 8; if (shifted === true) { background -= 8; } if (blinkOn === true && shifted) { font.draw(charCode, background, background, ctxs[contextIndex], x, contextY); } else { font.draw(charCode, foreground, background, ctxs[contextIndex], x, contextY); } } function redrawGlyph(index, x, y) { var contextIndex = Math.floor(y / 25); var contextY = y % 25; var charCode = imageData[index] >> 8; var background = (imageData[index] >> 4) & 15; var foreground = imageData[index] & 15; if (iceColours === true) { font.draw(charCode, foreground, background, ctxs[contextIndex], x, contextY); } else { if (background >= 8) { background -= 8; font.draw(charCode, foreground, background, offBlinkCtxs[contextIndex], x, contextY); font.draw(charCode, background, background, onBlinkCtxs[contextIndex], x, contextY); } else { font.draw(charCode, foreground, background, offBlinkCtxs[contextIndex], x, contextY); font.draw(charCode, foreground, background, onBlinkCtxs[contextIndex], x, contextY); } } } function redrawEntireImage() { for (var y = 0, i = 0; y < rows; y++) { for (var x = 0; x < columns; x++, i++) { redrawGlyph(i, x, y); } } } function blink() { if (blinkOn === false) { blinkOn = true; for (var i = 0; i < ctxs.length; i++) { ctxs[i].drawImage(onBlinkCanvases[i], 0, 0); } } else { blinkOn = false; for (var i = 0; i < ctxs.length; i++) { ctxs[i].drawImage(offBlinkCanvases[i], 0, 0); } } } function createCanvases() { if (canvases !== undefined) { canvases.forEach((canvas) => { canvasContainer.removeChild(canvas); }); } canvases = []; offBlinkCanvases = []; offBlinkCtxs = []; onBlinkCanvases = []; onBlinkCtxs = []; ctxs = []; var fontWidth = font.getWidth(); var fontHeight = font.getHeight(); var canvasWidth = fontWidth * columns; var canvasHeight = fontHeight * 25; for (var i = 0; i < Math.floor(rows / 25); i++) { var canvas = createCanvas(canvasWidth, canvasHeight); canvases.push(canvas); ctxs.push(canvas.getContext("2d")); var onBlinkCanvas = createCanvas(canvasWidth, canvasHeight); onBlinkCanvases.push(onBlinkCanvas); onBlinkCtxs.push(onBlinkCanvas.getContext("2d")); var offBlinkCanvas = createCanvas(canvasWidth, canvasHeight); offBlinkCanvases.push(offBlinkCanvas); offBlinkCtxs.push(offBlinkCanvas.getContext("2d")); } var canvasHeight = fontHeight * (rows % 25); if (rows % 25 !== 0) { var canvas = createCanvas(canvasWidth, canvasHeight); canvases.push(canvas); ctxs.push(canvas.getContext("2d")); var onBlinkCanvas = createCanvas(canvasWidth, canvasHeight); onBlinkCanvases.push(onBlinkCanvas); onBlinkCtxs.push(onBlinkCanvas.getContext("2d")); var offBlinkCanvas = createCanvas(canvasWidth, canvasHeight); offBlinkCanvases.push(offBlinkCanvas); offBlinkCtxs.push(offBlinkCanvas.getContext("2d")); } canvasContainer.style.width = canvasWidth + "px"; for (var i = 0; i < canvases.length; i++) { canvasContainer.appendChild(canvases[i]); } if (blinkTimer !== undefined) { clearInterval(blinkTimer); blinkOn = false; } redrawEntireImage(); if (iceColours === false) { blinkTimer = setInterval(blink, 250); } } function updateTimer() { if (blinkTimer !== undefined) { clearInterval(blinkTimer); } if (iceColours === false) { blinkOn = false; blinkTimer = setInterval(blink, 500); } } function setFont(fontName, callback) { font = loadFontFromImage(fontName, font.getLetterSpacing(), palette, (success) => { createCanvases(); redrawEntireImage(); document.dispatchEvent(new CustomEvent("onFontChange", {"detail": fontName})); callback(); }); } function resize(newColumnValue, newRowValue) { if ((newColumnValue !== columns || newRowValue !== rows) && (newColumnValue > 0 && newRowValue > 0)) { clearUndos(); var maxColumn = (columns > newColumnValue) ? newColumnValue : columns; var maxRow = (rows > newRowValue) ? newRowValue : rows; var newImageData = new Uint16Array(newColumnValue * newRowValue); for (var y = 0; y < maxRow; y++) { for (var x = 0; x < maxColumn; x++) { newImageData[y * newColumnValue + x] = imageData[y * columns + x]; } } imageData = newImageData; columns = newColumnValue; rows = newRowValue; createCanvases(); document.dispatchEvent(new CustomEvent("onTextCanvasSizeChange", {"detail": {"columns": columns, "rows": rows}})); } } function getIceColours() { return iceColours; } function setIceColours(newIceColours) { if (iceColours !== newIceColours) { iceColours = newIceColours; updateTimer(); redrawEntireImage(); } } function onLetterSpacingChange(letterSpacing) { createCanvases(); } function getImage() { var completeCanvas = createCanvas(font.getWidth() * columns, font.getHeight() * rows); var y = 0; var ctx = completeCanvas.getContext("2d"); ((iceColours === true) ? canvases : offBlinkCanvases).forEach((canvas) => { ctx.drawImage(canvas, 0, y); y += canvas.height; }); return completeCanvas; } function getImageData() { return imageData; } function setImageData(newColumnValue, newRowValue, newImageData, newIceColours) { clearUndos(); columns = newColumnValue; rows = newRowValue; imageData = newImageData; if (iceColours !== newIceColours) { iceColours = newIceColours; updateTimer(); } createCanvases(); redrawEntireImage(); document.dispatchEvent(new CustomEvent("onOpenedFile")); } function getColumns() { return columns; } function getRows() { return rows; } function clearUndos() { currentUndo = []; undoBuffer = []; redoBuffer = []; } function clear() { title.reset(); clearUndos(); imageData = new Uint16Array(columns * rows); redrawEntireImage(); } palette = createDefaultPalette(); font = loadFontFromImage("CP437 8x16", false, palette, (success) => { createCanvases(); updateTimer(); callback(); }); function draw(index, charCode, foreground, background, x, y) { currentUndo.push([index, imageData[index], x, y]); imageData[index] = (charCode << 8) + (background << 4) + foreground; drawHistory.push((index << 16) + imageData[index]); } function getBlock(x, y) { var index = y * columns + x; var charCode = imageData[index] >> 8; var foregroundColour = imageData[index] & 15; var backgroundColour = (imageData[index] >> 4) & 15; return { "x": x, "y": y, "charCode": charCode, "foregroundColour": foregroundColour, "backgroundColour": backgroundColour }; } function getHalfBlock(x, y) { var textY = Math.floor(y / 2); var index = textY * columns + x; var foreground = imageData[index] & 15; var background = (imageData[index] >> 4) & 15; var upperBlockColour = 0; var lowerBlockColour = 0; var isBlocky = false; var isVerticalBlocky = false; var leftBlockColour; var rightBlockColour; switch (imageData[index] >> 8) { case 0: case 32: case 255: upperBlockColour = background; lowerBlockColour = background; isBlocky = true; break; case 220: upperBlockColour = background; lowerBlockColour = foreground; isBlocky = true; break; case 221: isVerticalBlocky = true; leftBlockColour = foreground; rightBlockColour = background; break; case 222: isVerticalBlocky = true; leftBlockColour = background; rightBlockColour = foreground; break; case 223: upperBlockColour = foreground; lowerBlockColour = background; isBlocky = true; break; case 219: upperBlockColour = foreground; lowerBlockColour = foreground; isBlocky = true; break; default: if (foreground === background) { isBlocky = true; upperBlockColour = foreground; lowerBlockColour = foreground; } else { isBlocky = false; } } return { "x": x, "y": y, "textY": textY, "isBlocky": isBlocky, "upperBlockColour": upperBlockColour, "lowerBlockColour": lowerBlockColour, "halfBlockY": y % 2, "isVerticalBlocky": isVerticalBlocky, "leftBlockColour": leftBlockColour, "rightBlockColour": rightBlockColour }; } function drawHalfBlock(index, foreground, x, y, textY) { var halfBlockY = y % 2; var charCode = imageData[index] >> 8; var currentForeground = imageData[index] & 15; var currentBackground = (imageData[index] >> 4) & 15; if (charCode === 219) { if (currentForeground !== foreground) { if (halfBlockY === 0) { draw(index, 223, foreground, currentForeground, x, textY); } else { draw(index, 220, foreground, currentForeground, x, textY); } } } else if (charCode !== 220 && charCode !== 223) { if (halfBlockY === 0) { draw(index, 223, foreground, currentBackground, x, textY); } else { draw(index, 220, foreground, currentBackground, x, textY); } } else { if (halfBlockY === 0) { if (charCode === 223) { if (currentBackground === foreground) { draw(index, 219, foreground, 0, x, textY); } else { draw(index, 223, foreground, currentBackground, x, textY); } } else if (currentForeground === foreground) { draw(index, 219, foreground, 0, x, textY); } else { draw(index, 223, foreground, currentForeground, x, textY); } } else { if (charCode === 220) { if (currentBackground === foreground) { draw(index, 219, foreground, 0, x, textY); } else { draw(index, 220, foreground, currentBackground, x, textY); } } else if (currentForeground === foreground) { draw(index, 219, foreground, 0, x, textY); } else { draw(index, 220, foreground, currentForeground, x, textY); } } } } document.addEventListener("onLetterSpacingChange", onLetterSpacingChange); function getXYCoords(clientX, clientY, callback) { var rect = canvasContainer.getBoundingClientRect(); var x = Math.floor((clientX - rect.left) / font.getWidth()); var y = Math.floor((clientY - rect.top) / font.getHeight()); var halfBlockY = Math.floor((clientY - rect.top) / font.getHeight() * 2); callback(x, y, halfBlockY); } canvasContainer.addEventListener("touchstart", (evt) => { mouseButton = true; getXYCoords(evt.touches[0].pageX, evt.touches[0].pageY, (x, y, halfBlockY) => { if (evt.altKey === true) { sampleTool.sample(x, halfBlockY); } else { document.dispatchEvent(new CustomEvent("onTextCanvasDown", {"detail": {"x": x, "y": y, "halfBlockY": halfBlockY, "leftMouseButton": (evt.button === 0 && evt.ctrlKey !== true), "rightMouseButton": (evt.button === 2 || evt.ctrlKey === true)}})); } }); }); canvasContainer.addEventListener("mousedown", (evt) => { mouseButton = true; getXYCoords(evt.clientX, evt.clientY, (x, y, halfBlockY) => { if (evt.altKey === true) { sampleTool.sample(x, halfBlockY); } else { document.dispatchEvent(new CustomEvent("onTextCanvasDown", {"detail": {"x": x, "y": y, "halfBlockY": halfBlockY, "leftMouseButton": (evt.button === 0 && evt.ctrlKey !== true), "rightMouseButton": (evt.button === 2 || evt.ctrlKey === true)}})); } }); }); canvasContainer.addEventListener("contextmenu", (evt) => { evt.preventDefault(); }); canvasContainer.addEventListener("touchmove", (evt) => { evt.preventDefault(); getXYCoords(evt.touches[0].pageX, evt.touches[0].pageY, (x, y, halfBlockY) => { document.dispatchEvent(new CustomEvent("onTextCanvasDrag", {"detail": {"x": x, "y": y, "halfBlockY": halfBlockY, "leftMouseButton": (evt.button === 0 && evt.ctrlKey !== true), "rightMouseButton": (evt.button === 2 || evt.ctrlKey === true)}})); }); }); canvasContainer.addEventListener("mousemove", (evt) => { evt.preventDefault(); if (mouseButton === true) { getXYCoords(evt.clientX, evt.clientY, (x, y, halfBlockY) => { document.dispatchEvent(new CustomEvent("onTextCanvasDrag", {"detail": {"x": x, "y": y, "halfBlockY": halfBlockY, "leftMouseButton": (evt.button === 0 && evt.ctrlKey !== true), "rightMouseButton": (evt.button === 2 || evt.ctrlKey === true)}})); }); } }); canvasContainer.addEventListener("touchend", (evt) => { evt.preventDefault(); mouseButton = false; document.dispatchEvent(new CustomEvent("onTextCanvasUp", {})); }); canvasContainer.addEventListener("mouseup", (evt) => { evt.preventDefault(); if (mouseButton === true) { mouseButton = false; document.dispatchEvent(new CustomEvent("onTextCanvasUp", {})); } }); canvasContainer.addEventListener("touchenter", (evt) => { evt.preventDefault(); document.dispatchEvent(new CustomEvent("onTextCanvasUp", {})); }); canvasContainer.addEventListener("mouseenter", (evt) => { evt.preventDefault(); if (mouseButton === true && (evt.which === 0 || evt.buttons === 0)) { mouseButton = false; document.dispatchEvent(new CustomEvent("onTextCanvasUp", {})); } }); function sendDrawHistory() { worker.draw(drawHistory); drawHistory = []; } function undo() { if (currentUndo.length > 0) { undoBuffer.push(currentUndo); currentUndo = []; } if (undoBuffer.length > 0) { var currentRedo = []; var undoChunk = undoBuffer.pop(); for (var i = undoChunk.length - 1; i >= 0; i--) { var undo = undoChunk.pop(); if (undo[0] < imageData.length) { currentRedo.push([undo[0], imageData[undo[0]], undo[2], undo[3]]); imageData[undo[0]] = undo[1]; drawHistory.push((undo[0] << 16) + undo[1]); if (iceColours === false) { updateBeforeBlinkFlip(undo[2], undo[3]); } redrawGlyph(undo[0], undo[2], undo[3]); } } redoBuffer.push(currentRedo); sendDrawHistory(); } } function redo() { if (redoBuffer.length > 0) { var redoChunk = redoBuffer.pop(); for (var i = redoChunk.length - 1; i >= 0; i--) { var redo = redoChunk.pop(); if (redo[0] < imageData.length) { currentUndo.push([redo[0], imageData[redo[0]], redo[2], redo[3]]); imageData[redo[0]] = redo[1]; drawHistory.push((redo[0] << 16) + redo[1]); if (iceColours === false) { updateBeforeBlinkFlip(redo[2], redo[3]); } redrawGlyph(redo[0], redo[2], redo[3]); } } undoBuffer.push(currentUndo); currentUndo = []; sendDrawHistory(); } } function startUndo() { if (currentUndo.length > 0) { undoBuffer.push(currentUndo); currentUndo = []; } redoBuffer = []; } function optimiseBlocks(blocks) { blocks.forEach((block) => { var index = block[0]; var attribute = imageData[index]; var background = (attribute >> 4) & 15; if (background >= 8) { switch (attribute >> 8) { case 0: case 32: case 255: draw(index, 219, background, 0, block[1], block[2]); break; case 219: draw(index, 219, (attribute & 15), 0, block[1], block[2]); break; case 221: var foreground = (attribute & 15); if (foreground < 8) { draw(index, 222, background, foreground, block[1], block[2]); } break; case 222: var foreground = (attribute & 15); if (foreground < 8) { draw(index, 221, background, foreground, block[1], block[2]); } break; case 223: var foreground = (attribute & 15); if (foreground < 8) { draw(index, 220, background, foreground, block[1], block[2]); } break; case 220: var foreground = (attribute & 15); if (foreground < 8) { draw(index, 223, background, foreground, block[1], block[2]); } break; default: break; } } }); } function drawBlocks(blocks) { blocks.forEach((block) => { if (iceColours === false) { updateBeforeBlinkFlip(block[1], block[2]); } redrawGlyph(block[0], block[1], block[2]); }); } function undoWithoutSending() { for (var i = currentUndo.length - 1; i >= 0; i--) { var undo = currentUndo.pop(); imageData[undo[0]] = undo[1]; } drawHistory = []; } function drawEntryPoint(callback, optimise) { var blocks = []; callback(function (charCode, foreground, background, x, y) { var index = y * columns + x; blocks.push([index, x, y]); draw(index, charCode, foreground, background, x, y); }); if (optimise) { optimiseBlocks(blocks); } drawBlocks(blocks); sendDrawHistory(); } function drawHalfBlockEntryPoint(callback) { var blocks = []; callback(function (foreground, x, y) { var textY = Math.floor(y / 2); var index = textY * columns + x; blocks.push([index, x, textY]); drawHalfBlock(index, foreground, x, y, textY); }); optimiseBlocks(blocks); drawBlocks(blocks); sendDrawHistory(); } function deleteArea(x, y, width, height, background) { var maxWidth = x + width; var maxHeight = y + height; drawEntryPoint(function (draw) { for (var dy = y; dy < maxHeight; dy++) { for (var dx = x; dx < maxWidth; dx++) { draw(0, 0, background, dx, dy); } } }); } function getArea(x, y, width, height) { var data = new Uint16Array(width * height); for (var dy = 0, j = 0; dy < height; dy++) { for (var dx = 0; dx < width; dx++, j++) { var i = (y + dy) * columns + (x + dx); data[j] = imageData[i]; } } return { "data": data, "width": width, "height": height }; } function setArea(area, x, y) { var maxWidth = Math.min(area.width, columns - x); var maxHeight = Math.min(area.height, rows - y); drawEntryPoint(function (draw) { for (var py = 0; py < maxHeight; py++) { for (var px = 0; px < maxWidth; px++) { var attrib = area.data[py * area.width + px]; draw(attrib >> 8, attrib & 15, (attrib >> 4) & 15, x + px, y + py); } } }); } function quickDraw(blocks) { blocks.forEach((block) => { if (imageData[block[0]] !== block[1]) { imageData[block[0]] = block[1]; if (iceColours === false) { updateBeforeBlinkFlip(block[2], block[3]); } redrawGlyph(block[0], block[2], block[3]); } }); } return { "resize": resize, "redrawEntireImage": redrawEntireImage, "setFont": setFont, "getIceColours": getIceColours, "setIceColours": setIceColours, "getImage": getImage, "getImageData": getImageData, "setImageData": setImageData, "getColumns": getColumns, "getRows": getRows, "clear": clear, "draw": drawEntryPoint, "getBlock": getBlock, "getHalfBlock": getHalfBlock, "drawHalfBlock": drawHalfBlockEntryPoint, "startUndo": startUndo, "undo": undo, "redo": redo, "deleteArea": deleteArea, "getArea": getArea, "setArea": setArea, "quickDraw": quickDraw }; }