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.

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