UNPKG

4.24 kBJavaScriptView Raw
1
2/**
3 * Associates a canvas to a given image, containing a number of renderings
4 * of the image at various sizes.
5 *
6 * This technique is known as 'mipmapping'.
7 *
8 * NOTE: Images can also be of type 'data:svg+xml`. This code also works
9 * for svg, but the mipmapping may not be necessary.
10 *
11 * @param {Image} image
12 */
13class CachedImage {
14 /**
15 * @ignore
16 */
17 constructor() { // eslint-disable-line no-unused-vars
18 this.NUM_ITERATIONS = 4; // Number of items in the coordinates array
19
20 this.image = new Image();
21 this.canvas = document.createElement('canvas');
22 }
23
24
25 /**
26 * Called when the image has been successfully loaded.
27 */
28 init() {
29 if (this.initialized()) return;
30
31 this.src = this.image.src; // For same interface with Image
32 var w = this.image.width;
33 var h = this.image.height;
34
35 // Ease external access
36 this.width = w;
37 this.height = h;
38
39 var h2 = Math.floor(h/2);
40 var h4 = Math.floor(h/4);
41 var h8 = Math.floor(h/8);
42 var h16 = Math.floor(h/16);
43
44 var w2 = Math.floor(w/2);
45 var w4 = Math.floor(w/4);
46 var w8 = Math.floor(w/8);
47 var w16 = Math.floor(w/16);
48
49 // Make canvas as small as possible
50 this.canvas.width = 3*w4;
51 this.canvas.height = h2;
52
53 // Coordinates and sizes of images contained in the canvas
54 // Values per row: [top x, left y, width, height]
55
56 this.coordinates = [
57 [ 0 , 0 , w2 , h2],
58 [ w2 , 0 , w4 , h4],
59 [ w2 , h4, w8 , h8],
60 [ 5*w8, h4, w16, h16]
61 ];
62
63 this._fillMipMap();
64 }
65
66
67 /**
68 * @return {Boolean} true if init() has been called, false otherwise.
69 */
70 initialized() {
71 return (this.coordinates !== undefined);
72 }
73
74
75 /**
76 * Redraw main image in various sizes to the context.
77 *
78 * The rationale behind this is to reduce artefacts due to interpolation
79 * at differing zoom levels.
80 *
81 * Source: http://stackoverflow.com/q/18761404/1223531
82 *
83 * This methods takes the resizing out of the drawing loop, in order to
84 * reduce performance overhead.
85 *
86 * TODO: The code assumes that a 2D context can always be gotten. This is
87 * not necessarily true! OTOH, if not true then usage of this class
88 * is senseless.
89 *
90 * @private
91 */
92 _fillMipMap() {
93 var ctx = this.canvas.getContext('2d');
94
95 // First zoom-level comes from the image
96 var to = this.coordinates[0];
97 ctx.drawImage(this.image, to[0], to[1], to[2], to[3]);
98
99 // The rest are copy actions internal to the canvas/context
100 for (let iterations = 1; iterations < this.NUM_ITERATIONS; iterations++) {
101 let from = this.coordinates[iterations - 1];
102 let to = this.coordinates[iterations];
103
104 ctx.drawImage(this.canvas,
105 from[0], from[1], from[2], from[3],
106 to[0], to[1], to[2], to[3]
107 );
108 }
109 }
110
111
112 /**
113 * Draw the image, using the mipmap if necessary.
114 *
115 * MipMap is only used if param factor > 2; otherwise, original bitmap
116 * is resized. This is also used to skip mipmap usage, e.g. by setting factor = 1
117 *
118 * Credits to 'Alex de Mulder' for original implementation.
119 *
120 * @param {CanvasRenderingContext2D} ctx context on which to draw zoomed image
121 * @param {Float} factor scale factor at which to draw
122 * @param {number} left
123 * @param {number} top
124 * @param {number} width
125 * @param {number} height
126 */
127 drawImageAtPosition(ctx, factor, left, top, width, height) {
128
129 if(!this.initialized())
130 return; //can't draw image yet not intialized
131
132 if (factor > 2) {
133 // Determine which zoomed image to use
134 factor *= 0.5;
135 let iterations = 0;
136 while (factor > 2 && iterations < this.NUM_ITERATIONS) {
137 factor *= 0.5;
138 iterations += 1;
139 }
140
141 if (iterations >= this.NUM_ITERATIONS) {
142 iterations = this.NUM_ITERATIONS - 1;
143 }
144 //console.log("iterations: " + iterations);
145
146 let from = this.coordinates[iterations];
147 ctx.drawImage(this.canvas,
148 from[0], from[1], from[2], from[3],
149 left, top, width, height
150 );
151 } else {
152 // Draw image directly
153 ctx.drawImage(this.image, left, top, width, height);
154 }
155 }
156
157}
158
159
160export default CachedImage;