blob: 0a5ce275437afa8602f8bfa73bae536a04397de1 [file] [log] [blame]
<!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">&lt;canvas&gt; 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>