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.2 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. class CachedImage {
  11. constructor(image) {
  12. this.NUM_ITERATIONS = 4; // Number of items in the coordinates array
  13. this.image = new Image();
  14. this.canvas = document.createElement('canvas');
  15. }
  16. /**
  17. * Called when the image has been succesfully loaded.
  18. */
  19. init() {
  20. if (this.initialized()) return;
  21. var w = this.image.width;
  22. var h = this.image.height;
  23. // Ease external access
  24. this.width = w;
  25. this.height = h;
  26. // Make canvas as small as possible
  27. this.canvas.width = 3*w/4;
  28. this.canvas.height = h/2;
  29. // Coordinates and sizes of images contained in the canvas
  30. // Values per row: [top x, left y, width, height]
  31. this.coordinates = [
  32. [ 0 , 0 , w/2 , h/2],
  33. [ w/2 , 0 , w/4 , h/4],
  34. [ w/2 , h/4, w/8 , h/8],
  35. [ 5*w/8, h/4, w/16, h/16]
  36. ];
  37. this._fillMipMap();
  38. }
  39. /**
  40. * @return {Boolean} true if init() has been called, false otherwise.
  41. */
  42. initialized() {
  43. return (this.coordinates !== undefined);
  44. }
  45. /**
  46. * Redraw main image in various sizes to the context.
  47. *
  48. * The rationale behind this is to reduce artefacts due to interpolation
  49. * at differing zoom levels.
  50. *
  51. * Source: http://stackoverflow.com/q/18761404/1223531
  52. *
  53. * This methods takes the resizing out of the drawing loop, in order to
  54. * reduce performance overhead.
  55. *
  56. * @private
  57. */
  58. _fillMipMap() {
  59. var ctx = this.canvas.getContext('2d');
  60. // First zoom-level comes from the image
  61. var to = this.coordinates[0];
  62. ctx.drawImage(this.image, to[0], to[1], to[2], to[3]);
  63. // The rest are copy actions internal to the canvas/context
  64. for (let iterations = 1; iterations < this.NUM_ITERATIONS; iterations++) {
  65. let from = this.coordinates[iterations - 1];
  66. let to = this.coordinates[iterations];
  67. ctx.drawImage(this.canvas,
  68. from[0], from[1], from[2], from[3],
  69. to[0], to[1], to[2], to[3]
  70. );
  71. }
  72. }
  73. /**
  74. * Draw the image, using the mipmap if necessary.
  75. *
  76. * MipMap is only used if param factor > 2; otherwise, original bitmap
  77. * is resized. This is also used to skip mipmap usage, e.g. by setting factor = 1
  78. *
  79. * Credits to 'Alex de Mulder' for original implementation.
  80. *
  81. * ctx {Context} context on which to draw zoomed image
  82. * factor {Float} scale factor at which to draw
  83. */
  84. drawImageAtPosition(ctx, factor, left, top, width, height) {
  85. if (factor > 2 && this.initialized()) {
  86. // Determine which zoomed image to use
  87. factor *= 0.5;
  88. let iterations = 0;
  89. while (factor > 2 && iterations < this.NUM_ITERATIONS) {
  90. factor *= 0.5;
  91. iterations += 1;
  92. }
  93. if (iterations >= this.NUM_ITERATIONS) {
  94. iterations = this.NUM_ITERATIONS - 1;
  95. }
  96. //console.log("iterations: " + iterations);
  97. let from = this.coordinates[iterations];
  98. ctx.drawImage(this.canvas,
  99. from[0], from[1], from[2], from[3],
  100. left, top, width, height
  101. );
  102. } else if (this._isImageOk()) {
  103. // Draw image directly
  104. ctx.drawImage(this.image, left, top, width, height);
  105. }
  106. }
  107. /**
  108. * Check if image is loaded
  109. *
  110. * Source: http://stackoverflow.com/a/1977898/1223531
  111. *
  112. * @private
  113. */
  114. _isImageOk(img) {
  115. var img = this.image;
  116. // During the onload event, IE correctly identifies any images that
  117. // weren’t downloaded as not complete. Others should too. Gecko-based
  118. // browsers act like NS4 in that they report this incorrectly.
  119. if (!img.complete) {
  120. return false;
  121. }
  122. // However, they do have two very useful properties: naturalWidth and
  123. // naturalHeight. These give the true size of the image. If it failed
  124. // to load, either of these should be zero.
  125. if (typeof img.naturalWidth !== "undefined" && img.naturalWidth === 0) {
  126. return false;
  127. }
  128. // No other way of checking: assume it’s ok.
  129. return true;
  130. }
  131. }
  132. export default CachedImage;