8eebc9a9ea8ec5791209180f8f0d056485123b8d16df4398f2d548797fa3d21f489466f1ec36987d6f322da5010937e9eb1826c45f4e41db0ad2ff06174fe8 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. /*---------------------------------------------------------------------------------------------
  2. * Copyright (c) Microsoft Corporation. All rights reserved.
  3. * Licensed under the MIT License. See License.txt in the project root for license information.
  4. *--------------------------------------------------------------------------------------------*/
  5. import { MinimapCharRenderer } from './minimapCharRenderer.js';
  6. import { allCharCodes } from './minimapCharSheet.js';
  7. import { prebakedMiniMaps } from './minimapPreBaked.js';
  8. import { toUint8 } from '../../../../base/common/uint.js';
  9. /**
  10. * Creates character renderers. It takes a 'scale' that determines how large
  11. * characters should be drawn. Using this, it draws data into a canvas and
  12. * then downsamples the characters as necessary for the current display.
  13. * This makes rendering more efficient, rather than drawing a full (tiny)
  14. * font, or downsampling in real-time.
  15. */
  16. export class MinimapCharRendererFactory {
  17. /**
  18. * Creates a new character renderer factory with the given scale.
  19. */
  20. static create(scale, fontFamily) {
  21. // renderers are immutable. By default we'll 'create' a new minimap
  22. // character renderer whenever we switch editors, no need to do extra work.
  23. if (this.lastCreated && scale === this.lastCreated.scale && fontFamily === this.lastFontFamily) {
  24. return this.lastCreated;
  25. }
  26. let factory;
  27. if (prebakedMiniMaps[scale]) {
  28. factory = new MinimapCharRenderer(prebakedMiniMaps[scale](), scale);
  29. }
  30. else {
  31. factory = MinimapCharRendererFactory.createFromSampleData(MinimapCharRendererFactory.createSampleData(fontFamily).data, scale);
  32. }
  33. this.lastFontFamily = fontFamily;
  34. this.lastCreated = factory;
  35. return factory;
  36. }
  37. /**
  38. * Creates the font sample data, writing to a canvas.
  39. */
  40. static createSampleData(fontFamily) {
  41. const canvas = document.createElement('canvas');
  42. const ctx = canvas.getContext('2d');
  43. canvas.style.height = `${16 /* Constants.SAMPLED_CHAR_HEIGHT */}px`;
  44. canvas.height = 16 /* Constants.SAMPLED_CHAR_HEIGHT */;
  45. canvas.width = 96 /* Constants.CHAR_COUNT */ * 10 /* Constants.SAMPLED_CHAR_WIDTH */;
  46. canvas.style.width = 96 /* Constants.CHAR_COUNT */ * 10 /* Constants.SAMPLED_CHAR_WIDTH */ + 'px';
  47. ctx.fillStyle = '#ffffff';
  48. ctx.font = `bold ${16 /* Constants.SAMPLED_CHAR_HEIGHT */}px ${fontFamily}`;
  49. ctx.textBaseline = 'middle';
  50. let x = 0;
  51. for (const code of allCharCodes) {
  52. ctx.fillText(String.fromCharCode(code), x, 16 /* Constants.SAMPLED_CHAR_HEIGHT */ / 2);
  53. x += 10 /* Constants.SAMPLED_CHAR_WIDTH */;
  54. }
  55. return ctx.getImageData(0, 0, 96 /* Constants.CHAR_COUNT */ * 10 /* Constants.SAMPLED_CHAR_WIDTH */, 16 /* Constants.SAMPLED_CHAR_HEIGHT */);
  56. }
  57. /**
  58. * Creates a character renderer from the canvas sample data.
  59. */
  60. static createFromSampleData(source, scale) {
  61. const expectedLength = 16 /* Constants.SAMPLED_CHAR_HEIGHT */ * 10 /* Constants.SAMPLED_CHAR_WIDTH */ * 4 /* Constants.RGBA_CHANNELS_CNT */ * 96 /* Constants.CHAR_COUNT */;
  62. if (source.length !== expectedLength) {
  63. throw new Error('Unexpected source in MinimapCharRenderer');
  64. }
  65. const charData = MinimapCharRendererFactory._downsample(source, scale);
  66. return new MinimapCharRenderer(charData, scale);
  67. }
  68. static _downsampleChar(source, sourceOffset, dest, destOffset, scale) {
  69. const width = 1 /* Constants.BASE_CHAR_WIDTH */ * scale;
  70. const height = 2 /* Constants.BASE_CHAR_HEIGHT */ * scale;
  71. let targetIndex = destOffset;
  72. let brightest = 0;
  73. // This is essentially an ad-hoc rescaling algorithm. Standard approaches
  74. // like bicubic interpolation are awesome for scaling between image sizes,
  75. // but don't work so well when scaling to very small pixel values, we end
  76. // up with blurry, indistinct forms.
  77. //
  78. // The approach taken here is simply mapping each source pixel to the target
  79. // pixels, and taking the weighted values for all pixels in each, and then
  80. // averaging them out. Finally we apply an intensity boost in _downsample,
  81. // since when scaling to the smallest pixel sizes there's more black space
  82. // which causes characters to be much less distinct.
  83. for (let y = 0; y < height; y++) {
  84. // 1. For this destination pixel, get the source pixels we're sampling
  85. // from (x1, y1) to the next pixel (x2, y2)
  86. const sourceY1 = (y / height) * 16 /* Constants.SAMPLED_CHAR_HEIGHT */;
  87. const sourceY2 = ((y + 1) / height) * 16 /* Constants.SAMPLED_CHAR_HEIGHT */;
  88. for (let x = 0; x < width; x++) {
  89. const sourceX1 = (x / width) * 10 /* Constants.SAMPLED_CHAR_WIDTH */;
  90. const sourceX2 = ((x + 1) / width) * 10 /* Constants.SAMPLED_CHAR_WIDTH */;
  91. // 2. Sample all of them, summing them up and weighting them. Similar
  92. // to bilinear interpolation.
  93. let value = 0;
  94. let samples = 0;
  95. for (let sy = sourceY1; sy < sourceY2; sy++) {
  96. const sourceRow = sourceOffset + Math.floor(sy) * 3840 /* Constants.RGBA_SAMPLED_ROW_WIDTH */;
  97. const yBalance = 1 - (sy - Math.floor(sy));
  98. for (let sx = sourceX1; sx < sourceX2; sx++) {
  99. const xBalance = 1 - (sx - Math.floor(sx));
  100. const sourceIndex = sourceRow + Math.floor(sx) * 4 /* Constants.RGBA_CHANNELS_CNT */;
  101. const weight = xBalance * yBalance;
  102. samples += weight;
  103. value += ((source[sourceIndex] * source[sourceIndex + 3]) / 255) * weight;
  104. }
  105. }
  106. const final = value / samples;
  107. brightest = Math.max(brightest, final);
  108. dest[targetIndex++] = toUint8(final);
  109. }
  110. }
  111. return brightest;
  112. }
  113. static _downsample(data, scale) {
  114. const pixelsPerCharacter = 2 /* Constants.BASE_CHAR_HEIGHT */ * scale * 1 /* Constants.BASE_CHAR_WIDTH */ * scale;
  115. const resultLen = pixelsPerCharacter * 96 /* Constants.CHAR_COUNT */;
  116. const result = new Uint8ClampedArray(resultLen);
  117. let resultOffset = 0;
  118. let sourceOffset = 0;
  119. let brightest = 0;
  120. for (let charIndex = 0; charIndex < 96 /* Constants.CHAR_COUNT */; charIndex++) {
  121. brightest = Math.max(brightest, this._downsampleChar(data, sourceOffset, result, resultOffset, scale));
  122. resultOffset += pixelsPerCharacter;
  123. sourceOffset += 10 /* Constants.SAMPLED_CHAR_WIDTH */ * 4 /* Constants.RGBA_CHANNELS_CNT */;
  124. }
  125. if (brightest > 0) {
  126. const adjust = 255 / brightest;
  127. for (let i = 0; i < resultLen; i++) {
  128. result[i] *= adjust;
  129. }
  130. }
  131. return result;
  132. }
  133. }