/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ 'use strict'; import { ColorId, TokenizationRegistry } from 'vs/editor/common/modes'; import Event, { Emitter } from 'vs/base/common/event'; import { RGBA8 } from 'vs/editor/common/core/rgba'; export class MinimapTokensColorTracker { private static _INSTANCE: MinimapTokensColorTracker = null; public static getInstance(): MinimapTokensColorTracker { if (!this._INSTANCE) { this._INSTANCE = new MinimapTokensColorTracker(); } return this._INSTANCE; } private _colors: RGBA8[]; private _backgroundIsLight: boolean; private _onDidChange = new Emitter(); public readonly onDidChange: Event = this._onDidChange.event; private constructor() { this._updateColorMap(); TokenizationRegistry.onDidChange((e) => { if (e.changedColorMap) { this._updateColorMap(); } }); } private _updateColorMap(): void { const colorMap = TokenizationRegistry.getColorMap(); if (!colorMap) { this._colors = [null]; this._backgroundIsLight = true; return; } this._colors = [null]; for (let colorId = 1; colorId < colorMap.length; colorId++) { const source = colorMap[colorId].rgba; // Use a VM friendly data-type this._colors[colorId] = new RGBA8(source.r, source.g, source.b, Math.round(source.a * 255)); } let backgroundLuminosity = colorMap[ColorId.DefaultBackground].getRelativeLuminance(); this._backgroundIsLight = (backgroundLuminosity >= 0.5); this._onDidChange.fire(void 0); } public getColor(colorId: ColorId): RGBA8 { if (colorId < 1 || colorId >= this._colors.length) { // background color (basically invisible) colorId = ColorId.DefaultBackground; } return this._colors[colorId]; } public backgroundIsLight(): boolean { return this._backgroundIsLight; } } export const enum Constants { START_CH_CODE = 32, // Space END_CH_CODE = 126, // Tilde (~) CHAR_COUNT = END_CH_CODE - START_CH_CODE + 1, SAMPLED_CHAR_HEIGHT = 16, SAMPLED_CHAR_WIDTH = 10, SAMPLED_HALF_CHAR_WIDTH = SAMPLED_CHAR_WIDTH / 2, x2_CHAR_HEIGHT = 4, x2_CHAR_WIDTH = 2, x1_CHAR_HEIGHT = 2, x1_CHAR_WIDTH = 1, RGBA_CHANNELS_CNT = 4, } export class MinimapCharRenderer { _minimapCharRendererBrand: void; public readonly x2charData: Uint8ClampedArray; public readonly x1charData: Uint8ClampedArray; public readonly x2charDataLight: Uint8ClampedArray; public readonly x1charDataLight: Uint8ClampedArray; constructor(x2CharData: Uint8ClampedArray, x1CharData: Uint8ClampedArray) { const x2ExpectedLen = Constants.x2_CHAR_HEIGHT * Constants.x2_CHAR_WIDTH * Constants.CHAR_COUNT; if (x2CharData.length !== x2ExpectedLen) { throw new Error('Invalid x2CharData'); } const x1ExpectedLen = Constants.x1_CHAR_HEIGHT * Constants.x1_CHAR_WIDTH * Constants.CHAR_COUNT; if (x1CharData.length !== x1ExpectedLen) { throw new Error('Invalid x1CharData'); } this.x2charData = x2CharData; this.x1charData = x1CharData; this.x2charDataLight = MinimapCharRenderer.soften(x2CharData, 12 / 15); this.x1charDataLight = MinimapCharRenderer.soften(x1CharData, 50 / 60); } private static soften(input: Uint8ClampedArray, ratio: number): Uint8ClampedArray { let result = new Uint8ClampedArray(input.length); for (let i = 0, len = input.length; i < len; i++) { result[i] = input[i] * ratio; } return result; } private static _getChIndex(chCode: number): number { chCode -= Constants.START_CH_CODE; if (chCode < 0) { chCode += Constants.CHAR_COUNT; } return (chCode % Constants.CHAR_COUNT); } public x2RenderChar(target: ImageData, dx: number, dy: number, chCode: number, color: RGBA8, backgroundColor: RGBA8, useLighterFont: boolean): void { if (dx + Constants.x2_CHAR_WIDTH > target.width || dy + Constants.x2_CHAR_HEIGHT > target.height) { console.warn('bad render request outside image data'); return; } const x2CharData = useLighterFont ? this.x2charDataLight : this.x2charData; const chIndex = MinimapCharRenderer._getChIndex(chCode); const outWidth = target.width * Constants.RGBA_CHANNELS_CNT; const backgroundR = backgroundColor.r; const backgroundG = backgroundColor.g; const backgroundB = backgroundColor.b; const deltaR = color.r - backgroundR; const deltaG = color.g - backgroundG; const deltaB = color.b - backgroundB; const dest = target.data; const sourceOffset = chIndex * Constants.x2_CHAR_HEIGHT * Constants.x2_CHAR_WIDTH; let destOffset = dy * outWidth + dx * Constants.RGBA_CHANNELS_CNT; { const c = x2CharData[sourceOffset] / 255; dest[destOffset + 0] = backgroundR + deltaR * c; dest[destOffset + 1] = backgroundG + deltaG * c; dest[destOffset + 2] = backgroundB + deltaB * c; } { const c = x2CharData[sourceOffset + 1] / 255; dest[destOffset + 4] = backgroundR + deltaR * c; dest[destOffset + 5] = backgroundG + deltaG * c; dest[destOffset + 6] = backgroundB + deltaB * c; } destOffset += outWidth; { const c = x2CharData[sourceOffset + 2] / 255; dest[destOffset + 0] = backgroundR + deltaR * c; dest[destOffset + 1] = backgroundG + deltaG * c; dest[destOffset + 2] = backgroundB + deltaB * c; } { const c = x2CharData[sourceOffset + 3] / 255; dest[destOffset + 4] = backgroundR + deltaR * c; dest[destOffset + 5] = backgroundG + deltaG * c; dest[destOffset + 6] = backgroundB + deltaB * c; } destOffset += outWidth; { const c = x2CharData[sourceOffset + 4] / 255; dest[destOffset + 0] = backgroundR + deltaR * c; dest[destOffset + 1] = backgroundG + deltaG * c; dest[destOffset + 2] = backgroundB + deltaB * c; } { const c = x2CharData[sourceOffset + 5] / 255; dest[destOffset + 4] = backgroundR + deltaR * c; dest[destOffset + 5] = backgroundG + deltaG * c; dest[destOffset + 6] = backgroundB + deltaB * c; } destOffset += outWidth; { const c = x2CharData[sourceOffset + 6] / 255; dest[destOffset + 0] = backgroundR + deltaR * c; dest[destOffset + 1] = backgroundG + deltaG * c; dest[destOffset + 2] = backgroundB + deltaB * c; } { const c = x2CharData[sourceOffset + 7] / 255; dest[destOffset + 4] = backgroundR + deltaR * c; dest[destOffset + 5] = backgroundG + deltaG * c; dest[destOffset + 6] = backgroundB + deltaB * c; } } public x1RenderChar(target: ImageData, dx: number, dy: number, chCode: number, color: RGBA8, backgroundColor: RGBA8, useLighterFont: boolean): void { if (dx + Constants.x1_CHAR_WIDTH > target.width || dy + Constants.x1_CHAR_HEIGHT > target.height) { console.warn('bad render request outside image data'); return; } const x1CharData = useLighterFont ? this.x1charDataLight : this.x1charData; const chIndex = MinimapCharRenderer._getChIndex(chCode); const outWidth = target.width * Constants.RGBA_CHANNELS_CNT; const backgroundR = backgroundColor.r; const backgroundG = backgroundColor.g; const backgroundB = backgroundColor.b; const deltaR = color.r - backgroundR; const deltaG = color.g - backgroundG; const deltaB = color.b - backgroundB; const dest = target.data; const sourceOffset = chIndex * Constants.x1_CHAR_HEIGHT * Constants.x1_CHAR_WIDTH; let destOffset = dy * outWidth + dx * Constants.RGBA_CHANNELS_CNT; { const c = x1CharData[sourceOffset] / 255; dest[destOffset + 0] = backgroundR + deltaR * c; dest[destOffset + 1] = backgroundG + deltaG * c; dest[destOffset + 2] = backgroundB + deltaB * c; } destOffset += outWidth; { const c = x1CharData[sourceOffset + 1] / 255; dest[destOffset + 0] = backgroundR + deltaR * c; dest[destOffset + 1] = backgroundG + deltaG * c; dest[destOffset + 2] = backgroundB + deltaB * c; } } public x2BlockRenderChar(target: ImageData, dx: number, dy: number, color: RGBA8, backgroundColor: RGBA8, useLighterFont: boolean): void { if (dx + Constants.x2_CHAR_WIDTH > target.width || dy + Constants.x2_CHAR_HEIGHT > target.height) { console.warn('bad render request outside image data'); return; } const outWidth = target.width * Constants.RGBA_CHANNELS_CNT; const c = 0.5; const backgroundR = backgroundColor.r; const backgroundG = backgroundColor.g; const backgroundB = backgroundColor.b; const deltaR = color.r - backgroundR; const deltaG = color.g - backgroundG; const deltaB = color.b - backgroundB; const colorR = backgroundR + deltaR * c; const colorG = backgroundG + deltaG * c; const colorB = backgroundB + deltaB * c; const dest = target.data; let destOffset = dy * outWidth + dx * Constants.RGBA_CHANNELS_CNT; { dest[destOffset + 0] = colorR; dest[destOffset + 1] = colorG; dest[destOffset + 2] = colorB; } { dest[destOffset + 4] = colorR; dest[destOffset + 5] = colorG; dest[destOffset + 6] = colorB; } destOffset += outWidth; { dest[destOffset + 0] = colorR; dest[destOffset + 1] = colorG; dest[destOffset + 2] = colorB; } { dest[destOffset + 4] = colorR; dest[destOffset + 5] = colorG; dest[destOffset + 6] = colorB; } destOffset += outWidth; { dest[destOffset + 0] = colorR; dest[destOffset + 1] = colorG; dest[destOffset + 2] = colorB; } { dest[destOffset + 4] = colorR; dest[destOffset + 5] = colorG; dest[destOffset + 6] = colorB; } destOffset += outWidth; { dest[destOffset + 0] = colorR; dest[destOffset + 1] = colorG; dest[destOffset + 2] = colorB; } { dest[destOffset + 4] = colorR; dest[destOffset + 5] = colorG; dest[destOffset + 6] = colorB; } } public x1BlockRenderChar(target: ImageData, dx: number, dy: number, color: RGBA8, backgroundColor: RGBA8, useLighterFont: boolean): void { if (dx + Constants.x1_CHAR_WIDTH > target.width || dy + Constants.x1_CHAR_HEIGHT > target.height) { console.warn('bad render request outside image data'); return; } const outWidth = target.width * Constants.RGBA_CHANNELS_CNT; const c = 0.5; const backgroundR = backgroundColor.r; const backgroundG = backgroundColor.g; const backgroundB = backgroundColor.b; const deltaR = color.r - backgroundR; const deltaG = color.g - backgroundG; const deltaB = color.b - backgroundB; const colorR = backgroundR + deltaR * c; const colorG = backgroundG + deltaG * c; const colorB = backgroundB + deltaB * c; const dest = target.data; let destOffset = dy * outWidth + dx * Constants.RGBA_CHANNELS_CNT; { dest[destOffset + 0] = colorR; dest[destOffset + 1] = colorG; dest[destOffset + 2] = colorB; } destOffset += outWidth; { dest[destOffset + 0] = colorR; dest[destOffset + 1] = colorG; dest[destOffset + 2] = colorB; } } }