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

/**
* 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;