Path: blob/main/src/vs/editor/browser/viewParts/minimap/minimapCharRendererFactory.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { MinimapCharRenderer } from './minimapCharRenderer.js';6import { allCharCodes, Constants } from './minimapCharSheet.js';7import { prebakedMiniMaps } from './minimapPreBaked.js';8import { toUint8 } from '../../../../base/common/uint.js';910/**11* Creates character renderers. It takes a 'scale' that determines how large12* characters should be drawn. Using this, it draws data into a canvas and13* then downsamples the characters as necessary for the current display.14* This makes rendering more efficient, rather than drawing a full (tiny)15* font, or downsampling in real-time.16*/17export class MinimapCharRendererFactory {18private static lastCreated?: MinimapCharRenderer;19private static lastFontFamily?: string;2021/**22* Creates a new character renderer factory with the given scale.23*/24public static create(scale: number, fontFamily: string) {25// renderers are immutable. By default we'll 'create' a new minimap26// character renderer whenever we switch editors, no need to do extra work.27if (this.lastCreated && scale === this.lastCreated.scale && fontFamily === this.lastFontFamily) {28return this.lastCreated;29}3031let factory: MinimapCharRenderer;32if (prebakedMiniMaps[scale]) {33factory = new MinimapCharRenderer(prebakedMiniMaps[scale](), scale);34} else {35factory = MinimapCharRendererFactory.createFromSampleData(36MinimapCharRendererFactory.createSampleData(fontFamily).data,37scale38);39}4041this.lastFontFamily = fontFamily;42this.lastCreated = factory;43return factory;44}4546/**47* Creates the font sample data, writing to a canvas.48*/49public static createSampleData(fontFamily: string): ImageData {50const canvas = document.createElement('canvas');51const ctx = canvas.getContext('2d')!;5253canvas.style.height = `${Constants.SAMPLED_CHAR_HEIGHT}px`;54canvas.height = Constants.SAMPLED_CHAR_HEIGHT;55canvas.width = Constants.CHAR_COUNT * Constants.SAMPLED_CHAR_WIDTH;56canvas.style.width = Constants.CHAR_COUNT * Constants.SAMPLED_CHAR_WIDTH + 'px';5758ctx.fillStyle = '#ffffff';59ctx.font = `bold ${Constants.SAMPLED_CHAR_HEIGHT}px ${fontFamily}`;60ctx.textBaseline = 'middle';6162let x = 0;63for (const code of allCharCodes) {64ctx.fillText(String.fromCharCode(code), x, Constants.SAMPLED_CHAR_HEIGHT / 2);65x += Constants.SAMPLED_CHAR_WIDTH;66}6768return ctx.getImageData(0, 0, Constants.CHAR_COUNT * Constants.SAMPLED_CHAR_WIDTH, Constants.SAMPLED_CHAR_HEIGHT);69}7071/**72* Creates a character renderer from the canvas sample data.73*/74public static createFromSampleData(source: Uint8ClampedArray, scale: number): MinimapCharRenderer {75const expectedLength =76Constants.SAMPLED_CHAR_HEIGHT * Constants.SAMPLED_CHAR_WIDTH * Constants.RGBA_CHANNELS_CNT * Constants.CHAR_COUNT;77if (source.length !== expectedLength) {78throw new Error('Unexpected source in MinimapCharRenderer');79}8081const charData = MinimapCharRendererFactory._downsample(source, scale);82return new MinimapCharRenderer(charData, scale);83}8485private static _downsampleChar(86source: Uint8ClampedArray,87sourceOffset: number,88dest: Uint8ClampedArray,89destOffset: number,90scale: number91): number {92const width = Constants.BASE_CHAR_WIDTH * scale;93const height = Constants.BASE_CHAR_HEIGHT * scale;9495let targetIndex = destOffset;96let brightest = 0;9798// This is essentially an ad-hoc rescaling algorithm. Standard approaches99// like bicubic interpolation are awesome for scaling between image sizes,100// but don't work so well when scaling to very small pixel values, we end101// up with blurry, indistinct forms.102//103// The approach taken here is simply mapping each source pixel to the target104// pixels, and taking the weighted values for all pixels in each, and then105// averaging them out. Finally we apply an intensity boost in _downsample,106// since when scaling to the smallest pixel sizes there's more black space107// which causes characters to be much less distinct.108for (let y = 0; y < height; y++) {109// 1. For this destination pixel, get the source pixels we're sampling110// from (x1, y1) to the next pixel (x2, y2)111const sourceY1 = (y / height) * Constants.SAMPLED_CHAR_HEIGHT;112const sourceY2 = ((y + 1) / height) * Constants.SAMPLED_CHAR_HEIGHT;113114for (let x = 0; x < width; x++) {115const sourceX1 = (x / width) * Constants.SAMPLED_CHAR_WIDTH;116const sourceX2 = ((x + 1) / width) * Constants.SAMPLED_CHAR_WIDTH;117118// 2. Sample all of them, summing them up and weighting them. Similar119// to bilinear interpolation.120let value = 0;121let samples = 0;122for (let sy = sourceY1; sy < sourceY2; sy++) {123const sourceRow = sourceOffset + Math.floor(sy) * Constants.RGBA_SAMPLED_ROW_WIDTH;124const yBalance = 1 - (sy - Math.floor(sy));125for (let sx = sourceX1; sx < sourceX2; sx++) {126const xBalance = 1 - (sx - Math.floor(sx));127const sourceIndex = sourceRow + Math.floor(sx) * Constants.RGBA_CHANNELS_CNT;128129const weight = xBalance * yBalance;130samples += weight;131value += ((source[sourceIndex] * source[sourceIndex + 3]) / 255) * weight;132}133}134135const final = value / samples;136brightest = Math.max(brightest, final);137dest[targetIndex++] = toUint8(final);138}139}140141return brightest;142}143144private static _downsample(data: Uint8ClampedArray, scale: number): Uint8ClampedArray {145const pixelsPerCharacter = Constants.BASE_CHAR_HEIGHT * scale * Constants.BASE_CHAR_WIDTH * scale;146const resultLen = pixelsPerCharacter * Constants.CHAR_COUNT;147const result = new Uint8ClampedArray(resultLen);148149let resultOffset = 0;150let sourceOffset = 0;151let brightest = 0;152for (let charIndex = 0; charIndex < Constants.CHAR_COUNT; charIndex++) {153brightest = Math.max(brightest, this._downsampleChar(data, sourceOffset, result, resultOffset, scale));154resultOffset += pixelsPerCharacter;155sourceOffset += Constants.SAMPLED_CHAR_WIDTH * Constants.RGBA_CHANNELS_CNT;156}157158if (brightest > 0) {159const adjust = 255 / brightest;160for (let i = 0; i < resultLen; i++) {161result[i] *= adjust;162}163}164165return result;166}167}168169170