vis.js is a dynamic, browser-based visualization library
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

160 lines
4.1 KiB

  1. /**
  2. * Associates a canvas to a given image, containing a number of renderings
  3. * of the image at various sizes.
  4. *
  5. * This technique is known as 'mipmapping'.
  6. *
  7. * NOTE: Images can also be of type 'data:svg+xml`. This code also works
  8. * for svg, but the mipmapping may not be necessary.
  9. *
  10. * @param {Image} image
  11. */
  12. class CachedImage {
  13. /**
  14. * @ignore
  15. */
  16. constructor() { // eslint-disable-line no-unused-vars
  17. this.NUM_ITERATIONS = 4; // Number of items in the coordinates array
  18. this.image = new Image();
  19. this.canvas = document.createElement('canvas');
  20. }
  21. /**
  22. * Called when the image has been successfully loaded.
  23. */
  24. init() {
  25. if (this.initialized()) return;
  26. this.src = this.image.src; // For same interface with Image
  27. var w = this.image.width;
  28. var h = this.image.height;
  29. // Ease external access
  30. this.width = w;
  31. this.height = h;
  32. var h2 = Math.floor(h/2);
  33. var h4 = Math.floor(h/4);
  34. var h8 = Math.floor(h/8);
  35. var h16 = Math.floor(h/16);
  36. var w2 = Math.floor(w/2);
  37. var w4 = Math.floor(w/4);
  38. var w8 = Math.floor(w/8);
  39. var w16 = Math.floor(w/16);
  40. // Make canvas as small as possible
  41. this.canvas.width = 3*w4;
  42. this.canvas.height = h2;
  43. // Coordinates and sizes of images contained in the canvas
  44. // Values per row: [top x, left y, width, height]
  45. this.coordinates = [
  46. [ 0 , 0 , w2 , h2],
  47. [ w2 , 0 , w4 , h4],
  48. [ w2 , h4, w8 , h8],
  49. [ 5*w8, h4, w16, h16]
  50. ];
  51. this._fillMipMap();
  52. }
  53. /**
  54. * @return {Boolean} true if init() has been called, false otherwise.
  55. */
  56. initialized() {
  57. return (this.coordinates !== undefined);
  58. }
  59. /**
  60. * Redraw main image in various sizes to the context.
  61. *
  62. * The rationale behind this is to reduce artefacts due to interpolation
  63. * at differing zoom levels.
  64. *
  65. * Source: http://stackoverflow.com/q/18761404/1223531
  66. *
  67. * This methods takes the resizing out of the drawing loop, in order to
  68. * reduce performance overhead.
  69. *
  70. * TODO: The code assumes that a 2D context can always be gotten. This is
  71. * not necessarily true! OTOH, if not true then usage of this class
  72. * is senseless.
  73. *
  74. * @private
  75. */
  76. _fillMipMap() {
  77. var ctx = this.canvas.getContext('2d');
  78. // First zoom-level comes from the image
  79. var to = this.coordinates[0];
  80. ctx.drawImage(this.image, to[0], to[1], to[2], to[3]);
  81. // The rest are copy actions internal to the canvas/context
  82. for (let iterations = 1; iterations < this.NUM_ITERATIONS; iterations++) {
  83. let from = this.coordinates[iterations - 1];
  84. let to = this.coordinates[iterations];
  85. ctx.drawImage(this.canvas,
  86. from[0], from[1], from[2], from[3],
  87. to[0], to[1], to[2], to[3]
  88. );
  89. }
  90. }
  91. /**
  92. * Draw the image, using the mipmap if necessary.
  93. *
  94. * MipMap is only used if param factor > 2; otherwise, original bitmap
  95. * is resized. This is also used to skip mipmap usage, e.g. by setting factor = 1
  96. *
  97. * Credits to 'Alex de Mulder' for original implementation.
  98. *
  99. * @param {CanvasRenderingContext2D} ctx context on which to draw zoomed image
  100. * @param {Float} factor scale factor at which to draw
  101. * @param {number} left
  102. * @param {number} top
  103. * @param {number} width
  104. * @param {number} height
  105. */
  106. drawImageAtPosition(ctx, factor, left, top, width, height) {
  107. if(!this.initialized())
  108. return; //can't draw image yet not intialized
  109. if (factor > 2) {
  110. // Determine which zoomed image to use
  111. factor *= 0.5;
  112. let iterations = 0;
  113. while (factor > 2 && iterations < this.NUM_ITERATIONS) {
  114. factor *= 0.5;
  115. iterations += 1;
  116. }
  117. if (iterations >= this.NUM_ITERATIONS) {
  118. iterations = this.NUM_ITERATIONS - 1;
  119. }
  120. //console.log("iterations: " + iterations);
  121. let from = this.coordinates[iterations];
  122. ctx.drawImage(this.canvas,
  123. from[0], from[1], from[2], from[3],
  124. left, top, width, height
  125. );
  126. } else {
  127. // Draw image directly
  128. ctx.drawImage(this.image, left, top, width, height);
  129. }
  130. }
  131. }
  132. export default CachedImage;