252 lines
9.0 KiB
HTML
252 lines
9.0 KiB
HTML
<!DOCTYPE html>
|
|
<title>Spreadsheet Demo</title>
|
|
<meta charset="utf-8" />
|
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<script type="text/javascript" src="https://unpkg.com/canvaskit-wasm@0.33.0/bin/full/canvaskit.js"></script>
|
|
|
|
<style>
|
|
body {
|
|
background-color: black;
|
|
}
|
|
h1 {
|
|
color: white;
|
|
}
|
|
.hidden {
|
|
display: none;
|
|
}
|
|
</style>
|
|
|
|
<body>
|
|
<h1>Large canvas with many numbers, like a spreadsheet</h1>
|
|
<select id="numbers_impl">
|
|
<option value="fillText"><canvas> fillText</option>
|
|
<option value="drawGlyphs">CK drawGlyphs (tuned)</option>
|
|
<option value="drawText">CK drawText (naive)</option>
|
|
</select>
|
|
|
|
<canvas id=ck_canvas width=3840 height=2160 class="hidden"></canvas>
|
|
<canvas id=canvas_2d width=3840 height=2160></canvas>
|
|
</body>
|
|
|
|
<script type="text/javascript" charset="utf-8">
|
|
const ckLoaded = CanvasKitInit({ locateFile: (file) => 'https://unpkg.com/canvaskit-wasm@0.33.0/bin/full/' + file });
|
|
|
|
// This is the dimensions of a 4k screen.
|
|
const WIDTH = 3840, HEIGHT = 2160;
|
|
const ROWS = 144;
|
|
const ROW_HEIGHT = 15;
|
|
const COLS = 77;
|
|
const COL_WIDTH = 50;
|
|
const canvas2DEle = document.getElementById('canvas_2d');
|
|
const ckEle = document.getElementById('ck_canvas');
|
|
|
|
ckLoaded.then((CanvasKit) => {
|
|
const canvas2dCtx = canvas2DEle.getContext('2d');
|
|
const surface = CanvasKit.MakeCanvasSurface('ck_canvas');
|
|
if (!surface) {
|
|
throw 'Could not make surface';
|
|
}
|
|
|
|
const colorPaints = {
|
|
"grey": CanvasKit.Color(76, 76, 76),
|
|
"black": CanvasKit.BLACK,
|
|
"white": CanvasKit.WHITE,
|
|
"springGreen": CanvasKit.Color(0, 255, 127),
|
|
"tomato": CanvasKit.Color(255, 99, 71),
|
|
};
|
|
for (const name in colorPaints) {
|
|
const color = colorPaints[name];
|
|
const paint = new CanvasKit.Paint();
|
|
paint.setColor(color);
|
|
colorPaints[name] = paint;
|
|
}
|
|
|
|
const data = [];
|
|
for (let row = 0; row < ROWS; row++) {
|
|
data[row] = [];
|
|
for (let col = 0; col < COLS; col++) {
|
|
data[row][col] = Math.random() * Math.PI;
|
|
}
|
|
}
|
|
|
|
// Maybe use https://storage.googleapis.com/skia-cdn/google-web-fonts/NotoSans-Regular.ttf
|
|
const textFont = new CanvasKit.Font(null, 12);
|
|
|
|
const choice = document.getElementById("numbers_impl");
|
|
|
|
let frames = [];
|
|
const framesToMeasure = 10;
|
|
choice.addEventListener("change", () => {
|
|
frames = [];
|
|
if (choice.selectedIndex === 0) {
|
|
canvas2DEle.classList.remove('hidden');
|
|
ckEle.classList.add('hidden');
|
|
} else {
|
|
canvas2DEle.classList.add('hidden');
|
|
ckEle.classList.remove('hidden');
|
|
}
|
|
})
|
|
function drawFrame(canvas) {
|
|
if (frames && frames.length === framesToMeasure) {
|
|
// It is important to measure frame to frame time to account for the time spent by the
|
|
// GPU after our flush calls.
|
|
const deltas = [];
|
|
for (let i = 0; i< frames.length-1;i++) {
|
|
deltas.push(frames[i+1] - frames[i]);
|
|
}
|
|
console.log(`First ${framesToMeasure} frames`, deltas);
|
|
console.log((frames[framesToMeasure-1] - frames[0]) / framesToMeasure);
|
|
frames = null;
|
|
} else if (frames) {
|
|
frames.push(performance.now());
|
|
}
|
|
|
|
if (choice.selectedIndex === 2) {
|
|
canvas.clear(CanvasKit.BLACK);
|
|
drawTextImpl(canvas);
|
|
} else if (choice.selectedIndex === 1) {
|
|
canvas.clear(CanvasKit.BLACK);
|
|
drawGlyphsImpl(canvas);
|
|
} else {
|
|
fillTextImpl(canvas2dCtx);
|
|
}
|
|
|
|
surface.requestAnimationFrame(drawFrame)
|
|
}
|
|
|
|
function drawTextImpl(canvas) {
|
|
const timer = performance.now() / 10000;
|
|
for (let row = 0; row < ROWS; row++) {
|
|
if (row % 2) {
|
|
canvas.drawRect(CanvasKit.XYWHRect(0, row * ROW_HEIGHT + 2, WIDTH, ROW_HEIGHT), colorPaints["grey"]);
|
|
}
|
|
for (let col = 0; col < COLS; col++) {
|
|
let n = Math.abs(Math.sin(timer + data[row][col]));
|
|
let useWhiteFont = true;
|
|
if (n < 0.05) {
|
|
canvas.drawRect(CanvasKit.XYWHRect(col * COL_WIDTH - 2, (row - 1) * ROW_HEIGHT + 2, COL_WIDTH, ROW_HEIGHT), colorPaints["tomato"]);
|
|
useWhiteFont = false;
|
|
} else if (n > 0.95) {
|
|
canvas.drawRect(CanvasKit.XYWHRect(col * COL_WIDTH - 2, (row - 1) * ROW_HEIGHT + 2, COL_WIDTH, ROW_HEIGHT), colorPaints["springGreen"]);
|
|
useWhiteFont = false;
|
|
}
|
|
const str = n.toFixed(4);
|
|
canvas.drawText(str, col * COL_WIDTH, row * ROW_HEIGHT,
|
|
useWhiteFont ? colorPaints["white"] : colorPaints["black"], textFont);
|
|
}
|
|
}
|
|
}
|
|
|
|
//====================================================================================
|
|
const alphabet = "0.123456789 ";
|
|
const glyphIDs = textFont.getGlyphIDs(alphabet);
|
|
// These are all 7 with current font and size
|
|
const advances = textFont.getGlyphWidths(glyphIDs);
|
|
|
|
|
|
const charsPerDataPoint = 6; // leading 0, period, 4 decimal places
|
|
const glyphIDsMObj = CanvasKit.MallocGlyphIDs(ROWS * COLS * charsPerDataPoint);
|
|
let wasmGlyphIDArr = glyphIDsMObj.toTypedArray();
|
|
const glyphLocationsMObj = CanvasKit.Malloc(Float32Array, glyphIDsMObj.length * 2);
|
|
let wasmGlyphLocations = glyphLocationsMObj.toTypedArray();
|
|
|
|
function dataToGlyphs(n, outputBuffer, offset) {
|
|
const s = n.toFixed(4);
|
|
outputBuffer[offset] = glyphIDs[0]; // Always a leading 0
|
|
outputBuffer[offset+1] = glyphIDs[1]; // Always a decimal place
|
|
for (let i = 2; i< charsPerDataPoint; i++) {
|
|
outputBuffer[offset+i] = glyphIDs[alphabet.indexOf(s[i])];
|
|
}
|
|
}
|
|
const spaceIndex = alphabet.length - 1;
|
|
function blankOut(outputBuffer, offset) {
|
|
for (let i = 0; i< charsPerDataPoint; i++) {
|
|
outputBuffer[offset+i] = glyphIDs[spaceIndex];
|
|
}
|
|
}
|
|
|
|
for (let row = 0; row < ROWS; row++) {
|
|
for (let col = 0; col < COLS; col++) {
|
|
for (let i = 0; i < charsPerDataPoint; i++) {
|
|
const offset = (col + row * COLS) * charsPerDataPoint * 2;
|
|
wasmGlyphLocations[offset + i * 2] = col * COL_WIDTH + i * advances[0];
|
|
wasmGlyphLocations[offset + i * 2 + 1] = row * ROW_HEIGHT;
|
|
}
|
|
}
|
|
}
|
|
|
|
const greyGlyphIDsMObj = CanvasKit.MallocGlyphIDs(charsPerDataPoint);
|
|
|
|
function drawGlyphsImpl(canvas) {
|
|
wasmGlyphIDArr = glyphIDsMObj.toTypedArray();
|
|
let wasmGreyGlyphIDsArr = greyGlyphIDsMObj.toTypedArray();
|
|
|
|
const timer = performance.now() / 10000;
|
|
for (let row = 0; row < ROWS; row++) {
|
|
if (row % 2) {
|
|
canvas.drawRect(CanvasKit.XYWHRect(0, row * ROW_HEIGHT + 2, WIDTH, ROW_HEIGHT), colorPaints["grey"]);
|
|
}
|
|
for (let col = 0; col < COLS; col++) {
|
|
const n = Math.abs(Math.sin(timer + data[row][col]));
|
|
let useWhiteFont = true;
|
|
if (n < 0.05) {
|
|
canvas.drawRect(CanvasKit.XYWHRect(col * COL_WIDTH - 2, (row - 1) * ROW_HEIGHT + 2, COL_WIDTH, ROW_HEIGHT), colorPaints["tomato"]);
|
|
useWhiteFont = false;
|
|
} else if (n > 0.95) {
|
|
canvas.drawRect(CanvasKit.XYWHRect(col * COL_WIDTH - 2, (row - 1) * ROW_HEIGHT + 2, COL_WIDTH, ROW_HEIGHT), colorPaints["springGreen"]);
|
|
useWhiteFont = false;
|
|
}
|
|
|
|
const offset = (col + row * COLS) * charsPerDataPoint;
|
|
if (useWhiteFont) {
|
|
dataToGlyphs(n, wasmGlyphIDArr, offset);
|
|
} else {
|
|
blankOut(wasmGlyphIDArr, offset);
|
|
dataToGlyphs(n, wasmGreyGlyphIDsArr, 0);
|
|
canvas.drawGlyphs(wasmGreyGlyphIDsArr,
|
|
glyphLocationsMObj.subarray(offset*2, (offset + charsPerDataPoint) * 2),
|
|
0, 0, textFont, colorPaints["grey"]
|
|
);
|
|
}
|
|
}
|
|
}
|
|
canvas.drawGlyphs(wasmGlyphIDArr, glyphLocationsMObj, 0, 0, textFont, colorPaints["white"]);
|
|
}
|
|
|
|
function fillTextImpl(ctx) {
|
|
ctx.font = '12px monospace';
|
|
ctx.fillStyle = 'black';
|
|
ctx.fillRect(0, 0, WIDTH, HEIGHT);
|
|
const timer = performance.now() / 10000;
|
|
for (let row = 0; row < ROWS; row++) {
|
|
if (row % 2) {
|
|
ctx.fillStyle = 'rgb(76,76,76)';
|
|
ctx.fillRect(0, row * ROW_HEIGHT + 2, WIDTH, ROW_HEIGHT);
|
|
}
|
|
for (let col = 0; col < COLS; col++) {
|
|
let n = Math.abs(Math.sin(timer + data[row][col]));
|
|
let useWhiteFont = true;
|
|
if (n < 0.05) {
|
|
ctx.fillStyle = 'rgb(255, 99, 71)';
|
|
ctx.fillRect(col * COL_WIDTH - 2, (row - 1) * ROW_HEIGHT + 2, COL_WIDTH, ROW_HEIGHT);
|
|
useWhiteFont = false;
|
|
} else if (n > 0.95) {
|
|
ctx.fillStyle = 'rgb(0, 255, 127)';
|
|
ctx.fillRect(col * COL_WIDTH - 2, (row - 1) * ROW_HEIGHT + 2, COL_WIDTH, ROW_HEIGHT);
|
|
useWhiteFont = false;
|
|
}
|
|
const str = n.toFixed(4);
|
|
if (useWhiteFont) {
|
|
ctx.fillStyle = 'white';
|
|
} else {
|
|
ctx.fillStyle = 'black';
|
|
}
|
|
ctx.fillText(str, col * COL_WIDTH, row * ROW_HEIGHT);
|
|
}
|
|
}
|
|
}
|
|
|
|
surface.requestAnimationFrame(drawFrame);
|
|
});
|
|
</script> |