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