-
Notifications
You must be signed in to change notification settings - Fork 19
Expand file tree
/
Copy pathCachedImage.js
More file actions
166 lines (146 loc) · 4.18 KB
/
CachedImage.js
File metadata and controls
166 lines (146 loc) · 4.18 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
/**
* 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