visjs-network
Version:
A dynamic, browser-based network visualization library.
167 lines (146 loc) • 4.28 kB
JavaScript
/**
* Associates a canvas to a given image, containing a number of renderings
* of the image at various sizes.
*
* This technique is known as 'mipmapping'.
*
* NOTE: Images can also be of type 'data:svg+xml`. This code also works
* for svg, but the mipmapping may not be necessary.
*
* @param {Image} image
*/
class CachedImage {
/**
* @ignore
*/
constructor() {
// eslint-disable-line no-unused-vars
this.NUM_ITERATIONS = 4 // Number of items in the coordinates array
this.image = new Image()
this.canvas = document.createElement('canvas')
}
/**
* Called when the image has been successfully loaded.
*/
init() {
if (this.initialized()) return
this.src = this.image.src // For same interface with Image
var w = this.image.width
var h = this.image.height
// Ease external access
this.width = w
this.height = h
var h2 = Math.floor(h / 2)
var h4 = Math.floor(h / 4)
var h8 = Math.floor(h / 8)
var h16 = Math.floor(h / 16)
var w2 = Math.floor(w / 2)
var w4 = Math.floor(w / 4)
var w8 = Math.floor(w / 8)
var w16 = Math.floor(w / 16)
// Make canvas as small as possible
this.canvas.width = 3 * w4
this.canvas.height = h2
// Coordinates and sizes of images contained in the canvas
// Values per row: [top x, left y, width, height]
this.coordinates = [
[0, 0, w2, h2],
[w2, 0, w4, h4],
[w2, h4, w8, h8],
[5 * w8, h4, w16, h16]
]
this._fillMipMap()
}
/**
* @return {Boolean} true if init() has been called, false otherwise.
*/
initialized() {
return this.coordinates !== undefined
}
/**
* Redraw main image in various sizes to the context.
*
* The rationale behind this is to reduce artefacts due to interpolation
* at differing zoom levels.
*
* Source: http://stackoverflow.com/q/18761404/1223531
*
* This methods takes the resizing out of the drawing loop, in order to
* reduce performance overhead.
*
* TODO: The code assumes that a 2D context can always be gotten. This is
* not necessarily true! OTOH, if not true then usage of this class
* is senseless.
*
* @private
*/
_fillMipMap() {
var ctx = this.canvas.getContext('2d')
// First zoom-level comes from the image
var to = this.coordinates[0]
ctx.drawImage(this.image, to[0], to[1], to[2], to[3])
// The rest are copy actions internal to the canvas/context
for (let iterations = 1; iterations < this.NUM_ITERATIONS; iterations++) {
let from = this.coordinates[iterations - 1]
let to = this.coordinates[iterations]
ctx.drawImage(
this.canvas,
from[0],
from[1],
from[2],
from[3],
to[0],
to[1],
to[2],
to[3]
)
}
}
/**
* Draw the image, using the mipmap if necessary.
*
* MipMap is only used if param factor > 2; otherwise, original bitmap
* is resized. This is also used to skip mipmap usage, e.g. by setting factor = 1
*
* Credits to 'Alex de Mulder' for original implementation.
*
* @param {CanvasRenderingContext2D} ctx context on which to draw zoomed image
* @param {Float} factor scale factor at which to draw
* @param {number} left
* @param {number} top
* @param {number} width
* @param {number} height
*/
drawImageAtPosition(ctx, factor, left, top, width, height) {
if (!this.initialized()) return //can't draw image yet not intialized
if (factor > 2) {
// Determine which zoomed image to use
factor *= 0.5
let iterations = 0
while (factor > 2 && iterations < this.NUM_ITERATIONS) {
factor *= 0.5
iterations += 1
}
if (iterations >= this.NUM_ITERATIONS) {
iterations = this.NUM_ITERATIONS - 1
}
//console.log("iterations: " + iterations);
let from = this.coordinates[iterations]
ctx.drawImage(
this.canvas,
from[0],
from[1],
from[2],
from[3],
left,
top,
width,
height
)
} else {
// Draw image directly
ctx.drawImage(this.image, left, top, width, height)
}
}
}
export default CachedImage