ansilove.js

A script to display ANSi and artscene related file formats on web pages
Log | Files | Refs | README | LICENSE

commit 5a32b7b0258226cd5459b7d59357ddbabab0d9bd
parent e88e049576327c5e591ca16f06587f2d09630df9
Author: Andy Herbert <andy.herbert@gmail.com>
Date:   Thu, 31 Oct 2013 22:32:47 +0000

Substantial changes to the font rendering routines to limit putImageData() calls to an absolute minimum.

Diffstat:
Mansilove.js | 130+++++++++++++++++++++++++++++++++++++++----------------------------------------
1 file changed, 64 insertions(+), 66 deletions(-)

diff --git a/ansilove.js b/ansilove.js @@ -245,13 +245,13 @@ var AnsiLove = (function () { }; function read(file, width, height, fontSize, amigaFont) { - var bits, fontBitWidth, canvas, imageData, fontBuffer; + var bits, fontBitWidth, imageData, fontBuffer, fontBuffer24Bit; bits = new Uint8Array(width * height * fontSize); fontBitWidth = width * height; - canvas = createCanvas(width, height); - imageData = canvas.getContext("2d").getImageData(0, 0, width, height); fontBuffer = []; + fontBuffer24Bit = new Uint8Array(width * height * 4); + imageData = createCanvas(width, height).getContext("2d").getImageData(0, 0, width, height); (function () { var i, j, k, v; @@ -263,7 +263,7 @@ var AnsiLove = (function () { } }()); - function draw(ctx, x, y, charCode, palette, fg, bg) { + function getData(charCode, palette, fg, bg) { var i, j, k, bufferIndex; bufferIndex = charCode + (fg << 8) + (bg << 12); @@ -281,46 +281,32 @@ var AnsiLove = (function () { } } } - imageData.data.set(fontBuffer[bufferIndex], 0); - ctx.putImageData(imageData, x * width, y * height, 0, 0, width, height); + return fontBuffer[bufferIndex]; } - function draw24Bit(ctx, x, y, charCode, fg, bg) { + function get24BitData(charCode, fg, bg) { var i, j, k; for (i = 0, j = charCode * fontBitWidth, k = 0; i < fontBitWidth; ++i, ++j, k += 4) { if (bits[j]) { - imageData.data.set(fg, k); + fontBuffer24Bit.set(fg, k); } else { if (amigaFont && (fg > 7) && (i > 0) && bits[j - 1]) { - imageData.data.set(fg, k); + fontBuffer24Bit.set(fg, k); } else { - imageData.data.set(bg, k); + fontBuffer24Bit.set(bg, k); } } } - ctx.putImageData(imageData, x * width, y * height, 0, 0, width, height); - } - - function getHeight() { - return height; - } - - function getWidth() { - return width; - } - - function setWidth(newWidth) { - width = newWidth; + return fontBuffer24Bit; } return { - "draw": draw, - "draw24Bit": draw24Bit, + "getData": getData, + "get24BitData": get24BitData, "fontSize": fontSize, - "getHeight": getHeight, - "getWidth" : getWidth, - "setWidth": setWidth + "height": height, + "width" : width }; } @@ -569,39 +555,47 @@ var AnsiLove = (function () { } function display(raw, start, length, altFont, options) { - var canvas, font, end, ctx, i, j, x, y, fg, bg, chunky; + var canvas, font, end, ctx, i, j, k, l, x, chunky, fontBitWidth, fontDisplayWidth, displayFontBitWidth, imageData, fontData, rowOffset, screenOffset, fontOffset; font = raw.font || altFont || Font.preset("80x25"); - if (font.getWidth() === 9 && (options.bits !== "9" || options.thumbnail)) { - font.setWidth(8); - } + fontDisplayWidth = (font.width === 9 && (options.bits !== "9" || options.thumbnail)) ? 8 : font.width; + + fontBitWidth = font.width * 4; + displayFontBitWidth = fontDisplayWidth * 4; end = Math.min(start + length, raw.imageData.length); - canvas = createCanvas(raw.width * font.getWidth(), (end - start) / raw.rowLength * font.getHeight()); + canvas = createCanvas(raw.width * fontDisplayWidth, (end - start) / raw.rowLength * font.height); ctx = canvas.getContext("2d"); + imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); + rowOffset = canvas.width * 4; + if (raw.palette) { - for (i = start, j = x = y = 0; i < end; i += 2) { - fg = raw.imageData[i + 1] & 15; - bg = raw.imageData[i + 1] >> 4; - font.draw(ctx, x++, y, raw.imageData[i], raw.palette, fg, bg); - if (x % raw.width === 0) { - x = 0; - ++y; + for (i = start, screenOffset = 0, j = x = 0; i < end; i += 2, screenOffset += displayFontBitWidth) { + fontData = font.getData(raw.imageData[i], raw.palette, raw.imageData[i + 1] & 15, raw.imageData[i + 1] >> 4); + for (fontOffset = screenOffset, k = l = 0; k < font.height; ++k, fontOffset += rowOffset, l += fontBitWidth) { + imageData.data.set(fontData.subarray(l, l + displayFontBitWidth), fontOffset); + } + if (++x % raw.width === 0) { + screenOffset += (font.height - 1) * rowOffset; } } } else { - for (i = start, j = x = y = 0; i < end; i += 9) { - font.draw24Bit(ctx, x++, y, raw.imageData[i], raw.imageData.subarray(i + 1, i + 5), raw.imageData.subarray(i + 5, i + 9)); - if (x % raw.width === 0) { - x = 0; - ++y; + for (i = start, screenOffset = 0, j = x = 0; i < end; i += 9, screenOffset += displayFontBitWidth) { + fontData = font.get24BitData(raw.imageData[i], raw.imageData.subarray(i + 1, i + 5), raw.imageData.subarray(i + 5, i + 9)); + for (fontOffset = screenOffset, k = l = 0; k < font.height; ++k, fontOffset += rowOffset, l += fontBitWidth) { + imageData.data.set(fontData.subarray(l, l + displayFontBitWidth), fontOffset); + } + if (++x % raw.width === 0) { + screenOffset += (font.height - 1) * rowOffset; } } } + ctx.putImageData(imageData, 0, 0); + if (options.thumbnail) { chunky = Math.pow(2, 4 - options.thumbnail); canvas = scaleCanvas(canvas, chunky, chunky); @@ -1293,11 +1287,11 @@ var AnsiLove = (function () { } function Ansimation(bytes, options) { - var timer, interval, file, font, icecolors, bits, palette, columns, rows, screenClear, canvas, ctx, blinkCanvas, buffer, bufferCtx, blinkCtx, escaped, escapeCode, j, code, values, x, y, savedX, savedY, foreground, background, drawForeground, drawBackground, bold, inverse, blink, characterWidth, characterHeight; + var timer, interval, file, font, fontDisplayWidth, icecolors, bits, palette, columns, rows, screenClear, canvas, ctx, blinkCanvas, buffer, bufferCtx, blinkCtx, escaped, escapeCode, j, code, values, x, y, savedX, savedY, foreground, background, drawForeground, drawBackground, bold, inverse, blink, fontImageData; file = new File(bytes); icecolors = (options.icecolors === undefined) ? false : (options.icecolors === 1); - bits = options.bits || 8; + bits = options.bits || "8"; screenClear = (options["2J"] === undefined) ? true : (options["2J"] === 1); switch (bits) { @@ -1323,15 +1317,11 @@ var AnsiLove = (function () { font = Font.has(options.font) ? Font.preset(options.font) : Font.preset("80x25"); - if (font.getWidth() === 9 && bits !== "9") { - font.setWidth(8); - } - - characterWidth = font.getWidth(); - characterHeight = font.getHeight(); + fontDisplayWidth = (font.width === 9 && bits !== "9") ? 8 : font.width; - canvas = createCanvas(columns * font.getWidth(), rows * font.getHeight()); + canvas = createCanvas(columns * fontDisplayWidth, rows * font.height); ctx = canvas.getContext("2d"); + fontImageData = ctx.createImageData(font.width, font.height); blinkCanvas = [createCanvas(canvas.width, canvas.height), createCanvas(canvas.width, canvas.height)]; buffer = createCanvas(canvas.width, canvas.height); @@ -1357,25 +1347,25 @@ var AnsiLove = (function () { function clearBlinkChar(charX, charY) { var sx, sy; - sx = charX * characterWidth; - sy = charY * characterHeight; - blinkCtx[0].clearRect(sx, sy, characterWidth, characterHeight); - blinkCtx[1].clearRect(sx, sy, characterWidth, characterHeight); + sx = charX * fontDisplayWidth; + sy = charY * font.height; + blinkCtx[0].clearRect(sx, sy, fontDisplayWidth, font.height); + blinkCtx[1].clearRect(sx, sy, fontDisplayWidth, font.height); } function newLine() { x = 1; if (y === rows - 1) { - ctx.drawImage(canvas, 0, characterHeight, canvas.width, canvas.height - characterHeight * 2, 0, 0, canvas.width, canvas.height - characterHeight * 2); + ctx.drawImage(canvas, 0, font.height, canvas.width, canvas.height - font.height * 2, 0, 0, canvas.width, canvas.height - font.height * 2); bufferCtx.clearRect(0, 0, canvas.width, canvas.height); - bufferCtx.drawImage(blinkCanvas[0], 0, characterHeight, canvas.width, canvas.height - characterHeight * 2, 0, 0, canvas.width, canvas.height - characterHeight * 2); + bufferCtx.drawImage(blinkCanvas[0], 0, font.height, canvas.width, canvas.height - font.height * 2, 0, 0, canvas.width, canvas.height - font.height * 2); blinkCtx[0].clearRect(0, 0, canvas.width, canvas.height); blinkCtx[0].drawImage(buffer, 0, 0); bufferCtx.clearRect(0, 0, canvas.width, canvas.height); - bufferCtx.drawImage(blinkCanvas[1], 0, characterHeight, canvas.width, canvas.height - characterHeight * 2, 0, 0, canvas.width, canvas.height - characterHeight * 2); + bufferCtx.drawImage(blinkCanvas[1], 0, font.height, canvas.width, canvas.height - font.height * 2, 0, 0, canvas.width, canvas.height - font.height * 2); blinkCtx[1].clearRect(0, 0, canvas.width, canvas.height); blinkCtx[1].drawImage(buffer, 0, 0); - clearScreen(0, canvas.height - characterHeight * 2, canvas.width, characterHeight); + clearScreen(0, canvas.height - font.height * 2, canvas.width, font.height); return true; } ++y; @@ -1452,7 +1442,7 @@ var AnsiLove = (function () { } break; case "K": - clearScreen((x - 1) * characterWidth, (y - 1) * characterHeight, canvas.width - (x - 1) * characterWidth, characterHeight); + clearScreen((x - 1) * fontDisplayWidth, (y - 1) * font.height, canvas.width - (x - 1) * fontDisplayWidth, font.height); break; case "m": for (j = 0; j < values.length; ++j) { @@ -1533,15 +1523,23 @@ var AnsiLove = (function () { drawForeground = foreground; drawBackground = background; } + if (bold) { + drawForeground += 8; + } + if (blink && icecolors) { + drawBackground += 8; + } + fontImageData.data.set(font.getData(code, palette, drawForeground, drawBackground), 0); + ctx.putImageData(fontImageData, (x - 1) * fontDisplayWidth, (y - 1) * font.height, 0, 0, fontDisplayWidth, font.height); if (!icecolors) { if (blink) { - font.draw(blinkCtx[0], x - 1, y - 1, code, palette, bold ? (drawForeground + 8) : drawForeground, drawBackground); - font.draw(blinkCtx[1], x - 1, y - 1, code, palette, drawBackground, drawBackground); + blinkCtx[0].putImageData(fontImageData, (x - 1) * fontDisplayWidth, (y - 1) * font.height, 0, 0, fontDisplayWidth, font.height); + fontImageData.data.set(font.getData(code, palette, drawBackground, drawBackground), 0); + blinkCtx[1].putImageData(fontImageData, (x - 1) * fontDisplayWidth, (y - 1) * font.height, 0, 0, fontDisplayWidth, font.height); } else { clearBlinkChar(x - 1, y - 1); } } - font.draw(ctx, x - 1, y - 1, code, palette, bold ? (drawForeground + 8) : drawForeground, (blink && icecolors) ? (drawBackground + 8) : drawBackground); if (++x === columns + 1) { if (newLine()) { return i + 1;