| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133 |
- /*---------------------------------------------------------------------------------------------
- * Copyright (c) Microsoft Corporation. All rights reserved.
- * Licensed under the MIT License. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
- import { MinimapCharRenderer } from './minimapCharRenderer.js';
- import { allCharCodes } from './minimapCharSheet.js';
- import { prebakedMiniMaps } from './minimapPreBaked.js';
- import { toUint8 } from '../../../../base/common/uint.js';
- /**
- * Creates character renderers. It takes a 'scale' that determines how large
- * characters should be drawn. Using this, it draws data into a canvas and
- * then downsamples the characters as necessary for the current display.
- * This makes rendering more efficient, rather than drawing a full (tiny)
- * font, or downsampling in real-time.
- */
- export class MinimapCharRendererFactory {
- /**
- * Creates a new character renderer factory with the given scale.
- */
- static create(scale, fontFamily) {
- // renderers are immutable. By default we'll 'create' a new minimap
- // character renderer whenever we switch editors, no need to do extra work.
- if (this.lastCreated && scale === this.lastCreated.scale && fontFamily === this.lastFontFamily) {
- return this.lastCreated;
- }
- let factory;
- if (prebakedMiniMaps[scale]) {
- factory = new MinimapCharRenderer(prebakedMiniMaps[scale](), scale);
- }
- else {
- factory = MinimapCharRendererFactory.createFromSampleData(MinimapCharRendererFactory.createSampleData(fontFamily).data, scale);
- }
- this.lastFontFamily = fontFamily;
- this.lastCreated = factory;
- return factory;
- }
- /**
- * Creates the font sample data, writing to a canvas.
- */
- static createSampleData(fontFamily) {
- const canvas = document.createElement('canvas');
- const ctx = canvas.getContext('2d');
- canvas.style.height = `${16 /* Constants.SAMPLED_CHAR_HEIGHT */}px`;
- canvas.height = 16 /* Constants.SAMPLED_CHAR_HEIGHT */;
- canvas.width = 96 /* Constants.CHAR_COUNT */ * 10 /* Constants.SAMPLED_CHAR_WIDTH */;
- canvas.style.width = 96 /* Constants.CHAR_COUNT */ * 10 /* Constants.SAMPLED_CHAR_WIDTH */ + 'px';
- ctx.fillStyle = '#ffffff';
- ctx.font = `bold ${16 /* Constants.SAMPLED_CHAR_HEIGHT */}px ${fontFamily}`;
- ctx.textBaseline = 'middle';
- let x = 0;
- for (const code of allCharCodes) {
- ctx.fillText(String.fromCharCode(code), x, 16 /* Constants.SAMPLED_CHAR_HEIGHT */ / 2);
- x += 10 /* Constants.SAMPLED_CHAR_WIDTH */;
- }
- return ctx.getImageData(0, 0, 96 /* Constants.CHAR_COUNT */ * 10 /* Constants.SAMPLED_CHAR_WIDTH */, 16 /* Constants.SAMPLED_CHAR_HEIGHT */);
- }
- /**
- * Creates a character renderer from the canvas sample data.
- */
- static createFromSampleData(source, scale) {
- const expectedLength = 16 /* Constants.SAMPLED_CHAR_HEIGHT */ * 10 /* Constants.SAMPLED_CHAR_WIDTH */ * 4 /* Constants.RGBA_CHANNELS_CNT */ * 96 /* Constants.CHAR_COUNT */;
- if (source.length !== expectedLength) {
- throw new Error('Unexpected source in MinimapCharRenderer');
- }
- const charData = MinimapCharRendererFactory._downsample(source, scale);
- return new MinimapCharRenderer(charData, scale);
- }
- static _downsampleChar(source, sourceOffset, dest, destOffset, scale) {
- const width = 1 /* Constants.BASE_CHAR_WIDTH */ * scale;
- const height = 2 /* Constants.BASE_CHAR_HEIGHT */ * scale;
- let targetIndex = destOffset;
- let brightest = 0;
- // This is essentially an ad-hoc rescaling algorithm. Standard approaches
- // like bicubic interpolation are awesome for scaling between image sizes,
- // but don't work so well when scaling to very small pixel values, we end
- // up with blurry, indistinct forms.
- //
- // The approach taken here is simply mapping each source pixel to the target
- // pixels, and taking the weighted values for all pixels in each, and then
- // averaging them out. Finally we apply an intensity boost in _downsample,
- // since when scaling to the smallest pixel sizes there's more black space
- // which causes characters to be much less distinct.
- for (let y = 0; y < height; y++) {
- // 1. For this destination pixel, get the source pixels we're sampling
- // from (x1, y1) to the next pixel (x2, y2)
- const sourceY1 = (y / height) * 16 /* Constants.SAMPLED_CHAR_HEIGHT */;
- const sourceY2 = ((y + 1) / height) * 16 /* Constants.SAMPLED_CHAR_HEIGHT */;
- for (let x = 0; x < width; x++) {
- const sourceX1 = (x / width) * 10 /* Constants.SAMPLED_CHAR_WIDTH */;
- const sourceX2 = ((x + 1) / width) * 10 /* Constants.SAMPLED_CHAR_WIDTH */;
- // 2. Sample all of them, summing them up and weighting them. Similar
- // to bilinear interpolation.
- let value = 0;
- let samples = 0;
- for (let sy = sourceY1; sy < sourceY2; sy++) {
- const sourceRow = sourceOffset + Math.floor(sy) * 3840 /* Constants.RGBA_SAMPLED_ROW_WIDTH */;
- const yBalance = 1 - (sy - Math.floor(sy));
- for (let sx = sourceX1; sx < sourceX2; sx++) {
- const xBalance = 1 - (sx - Math.floor(sx));
- const sourceIndex = sourceRow + Math.floor(sx) * 4 /* Constants.RGBA_CHANNELS_CNT */;
- const weight = xBalance * yBalance;
- samples += weight;
- value += ((source[sourceIndex] * source[sourceIndex + 3]) / 255) * weight;
- }
- }
- const final = value / samples;
- brightest = Math.max(brightest, final);
- dest[targetIndex++] = toUint8(final);
- }
- }
- return brightest;
- }
- static _downsample(data, scale) {
- const pixelsPerCharacter = 2 /* Constants.BASE_CHAR_HEIGHT */ * scale * 1 /* Constants.BASE_CHAR_WIDTH */ * scale;
- const resultLen = pixelsPerCharacter * 96 /* Constants.CHAR_COUNT */;
- const result = new Uint8ClampedArray(resultLen);
- let resultOffset = 0;
- let sourceOffset = 0;
- let brightest = 0;
- for (let charIndex = 0; charIndex < 96 /* Constants.CHAR_COUNT */; charIndex++) {
- brightest = Math.max(brightest, this._downsampleChar(data, sourceOffset, result, resultOffset, scale));
- resultOffset += pixelsPerCharacter;
- sourceOffset += 10 /* Constants.SAMPLED_CHAR_WIDTH */ * 4 /* Constants.RGBA_CHANNELS_CNT */;
- }
- if (brightest > 0) {
- const adjust = 255 / brightest;
- for (let i = 0; i < resultLen; i++) {
- result[i] *= adjust;
- }
- }
- return result;
- }
- }
|