-
Notifications
You must be signed in to change notification settings - Fork 0
/
SimpleImage.js
416 lines (392 loc) · 15.9 KB
/
SimpleImage.js
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
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
// Copyright 2015 Owen Astrachan, Drew Hilton, Susan Rodger, Robert Duvall
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Created by Nick Parlante
// Updated by Robert Duvall
// Represents one pixel in a SimpleImage, supports rgb get/set.
SimplePixel = function (simple_image, x, y) {
__SimpleImageUtilities.funCheck('SimplePixel', 3, arguments.length);
this.container = simple_image;
this.x = x;
this.y = y;
};
SimplePixel.prototype = {
constructor: SimplePixel,
getX: function () {
__SimpleImageUtilities.funCheck('getX', 0, arguments.length);
return this.x;
},
getY: function () {
__SimpleImageUtilities.funCheck('getY', 0, arguments.length);
return this.y;
},
getRed: function () {
__SimpleImageUtilities.funCheck('getRed', 0, arguments.length);
return this.container.getRed(this.x, this.y);
},
getGreen: function () {
__SimpleImageUtilities.funCheck('getGreen', 0, arguments.length);
return this.container.getGreen(this.x, this.y);
},
getBlue: function () {
__SimpleImageUtilities.funCheck('getBlue', 0, arguments.length);
return this.container.getBlue(this.x, this.y);
},
getAlpha: function () {
__SimpleImageUtilities.funCheck('getAlpha', 0, arguments.length);
return this.container.getAlpha(this.x, this.y);
},
setRed: function (val) {
__SimpleImageUtilities.funCheck('setRed', 1, arguments.length);
this.container.setRed(this.x, this.y, val);
},
setGreen: function (val) {
__SimpleImageUtilities.funCheck('setGreen', 1, arguments.length);
this.container.setGreen(this.x, this.y, val);
},
setBlue: function (val) {
__SimpleImageUtilities.funCheck('setBlue', 1, arguments.length);
this.container.setBlue(this.x, this.y, val);
},
setAlpha: function (val) {
__SimpleImageUtilities.funCheck('setAlpha', 1, arguments.length);
this.container.setAlpha(this.x, this.y, val);
},
setAllFrom: function (pixel) {
__SimpleImageUtilities.funCheck('setAllFrom', 1, arguments.length);
this.setRed(pixel.getRed());
this.setGreen(pixel.getGreen());
this.setBlue(pixel.getBlue());
this.setAlpha(pixel.getAlpha());
},
toString: function () {
return 'r:' + this.getRed() + ' g:' + this.getGreen() + ' b:' + this.getBlue();
},
// Render pixel as string
getString: function () {
return this.toString();
}
};
// Note there is an Image built in, so don't use that name.
// A SimpleImage can be created with a url, size, or an existing htmlImage or canvas
SimpleImage = function () {
if (arguments.length < 0 || arguments.length > 2) {
__SimpleImageUtilities.funCheck('SimpleImage', 1, arguments.length);
return null;
}
// function map for to support "overloaded constructor"
var funMap = [
function () {
return __SimpleImageUtilities.EMPTY_IMAGE;
},
function (source) {
if (source instanceof HTMLImageElement) {
return source;
} else if (typeof source == 'string') {
return __SimpleImageUtilities.makeHTMLImageFromURL(source, this);
} else if (source instanceof HTMLInputElement && source.type == 'file') {
return __SimpleImageUtilities.makeHTMLImageFromInput(source.files[0], this);
} else if (source instanceof SimpleImage) {
return source.canvas;
} else if (source instanceof HTMLCanvasElement) {
return source;
} else {
__SimpleImageUtilities.throwError('Unrecognized value used to create a SimpleImage: ' + source);
}
},
function (width, height) {
if (width > 0 && height > 0) {
return __SimpleImageUtilities.makeHTMLImageFromSize(width, height);
} else {
__SimpleImageUtilities.throwError('Unable to create a SimpleImage with a negative width or height [' + width + 'x' + height + ']');
}
}
];
// call appropriate constructor
var htmlImage = funMap[arguments.length].apply(this, arguments);
// actual content is backed by an invisible canvas
this.canvas = __SimpleImageUtilities.makeHTMLCanvas('SimpleImageCanvas');
this.canvas.style.display = 'none';
this.context = this.canvas.getContext('2d');
// when image is loaded, it will fill this in
this.imageData = null;
// check to see if we can complete the constructor now instead of waiting
if (htmlImage != null && (htmlImage instanceof HTMLCanvasElement || htmlImage.complete)) {
this.__init(htmlImage);
}
this.ACCEPTED_FILES = 'image.*';
}
SimpleImage.prototype = {
constructor: SimpleImage,
complete: function () {
return this.imageData != null;
},
getWidth: function () {
__SimpleImageUtilities.funCheck('getWidth', 0, arguments.length);
return this.width;
},
getHeight: function () {
__SimpleImageUtilities.funCheck('getHeight', 0, arguments.length);
return this.height;
},
getRed: function (x, y) {
__SimpleImageUtilities.funCheck('getRed', 2, arguments.length);
return this.imageData.data[this.__getIndex('getRed', x, y)];
},
getGreen: function (x, y) {
__SimpleImageUtilities.funCheck('getGreen', 2, arguments.length);
return this.imageData.data[this.__getIndex('getGreen', x, y) + 1];
},
getBlue: function (x, y) {
__SimpleImageUtilities.funCheck('getBlue', 2, arguments.length);
return this.imageData.data[this.__getIndex('getBlue', x, y) + 2];
},
getAlpha: function (x, y) {
__SimpleImageUtilities.funCheck('getAlpha', 2, arguments.length);
return this.imageData.data[this.__getIndex('getAlpha', x, y) + 3];
},
// Changes to the pixel write back to the image.
getPixel: function (x, y) {
__SimpleImageUtilities.funCheck('getPixel', 2, arguments.length);
__SimpleImageUtilities.rangeCheck(x, 0, this.getWidth(), 'getPixel', 'x', 'wide');
__SimpleImageUtilities.rangeCheck(y, 0, this.getHeight(), 'getPixel', 'y', 'tall');
return new SimplePixel(this, x, y);
},
setRed: function (x, y, value) {
__SimpleImageUtilities.funCheck('setRed', 3, arguments.length);
this.imageData.data[this.__getIndex('getRed', x, y)] = __SimpleImageUtilities.clamp(value);
},
setGreen: function (x, y, value) {
__SimpleImageUtilities.funCheck('setGreen', 3, arguments.length);
this.imageData.data[this.__getIndex('getGreen', x, y) + 1] = __SimpleImageUtilities.clamp(value);
},
setBlue: function (x, y, value) {
__SimpleImageUtilities.funCheck('setBlue', 3, arguments.length);
this.imageData.data[this.__getIndex('getBlue', x, y) + 2] = __SimpleImageUtilities.clamp(value);
},
setAlpha: function (x, y, value) {
__SimpleImageUtilities.funCheck('setAlpha', 3, arguments.length);
this.imageData.data[this.__getIndex('getAlpha', x, y) + 3] = __SimpleImageUtilities.clamp(value);
},
setPixel: function (x, y, pixel) {
__SimpleImageUtilities.funCheck('setPixel', 3, arguments.length);
__SimpleImageUtilities.rangeCheck(x, 0, this.getWidth(), 'setPixel', 'x', 'wide');
__SimpleImageUtilities.rangeCheck(y, 0, this.getHeight(), 'setPixel', 'y', 'tall');
this.setRed(x, y, pixel.getRed());
this.setBlue(x, y, pixel.getBlue());
this.setGreen(x, y, pixel.getGreen());
this.setAlpha(x, y, pixel.getAlpha());
},
// Scales contents of SimpleIage to the given size
setSize: function (width, height) {
__SimpleImageUtilities.funCheck('setSize', 2, arguments.length);
width = Math.floor(width);
height = Math.floor(height);
if (width > 0 && height > 0) {
// make sure we have the most current changes
__SimpleImageUtilities.flush(this.context, this.imageData);
this.imageData = __SimpleImageUtilities.changeSize(this.canvas, width, height);
this.width = width;
this.height = height;
this.canvas.width = width;
this.canvas.height = height;
}
else {
__SimpleImageUtilities.throwError('You tried to set the size of a SimpleImage to a negative width or height [' + width + 'x' + height + ']');
}
},
// Draws to the given canvas, setting its size to match SimpleImage's size
drawTo: function (toCanvas) {
if (this.imageData != null) {
__SimpleImageUtilities.flush(this.context, this.imageData);
toCanvas.width = this.getWidth();
toCanvas.height = this.getHeight();
toCanvas.getContext('2d').drawImage(this.canvas, 0, 0, toCanvas.width, toCanvas.height);
}
else {
var myself = this;
setTimeout(function () {
myself.drawTo(toCanvas);
}, 100);
}
},
// Export an image as an linear array of pixels that can be iterated over
toArray: function () {
__SimpleImageUtilities.funCheck('toArray', 0, arguments.length);
var array = new Array();
// nip 2012-7
// 1. simple-way (this is as good or faster in various browser tests)
// var array = new Array(this.getWidth() * this.getHeight());
// 2. change to cache-friendly y/x ordering
// Non-firefox browsers may benefit
for (var y = 0; y < this.getHeight(); y++) {
for (var x = 0; x < this.getWidth(); x++) {
//array[i++] = new SimplePixel(this, x, y); // 2.
array.push(new SimplePixel(this, x, y)); // 1.
}
}
return array;
},
// Support iterator within for loops (eventually)
values: function () {
__SimpleImageUtilities.funCheck('values', 0, arguments.length);
return this.toArray();
},
// Better name than values if we have to use it
pixels: function () {
return this.values();
},
// Private methods: should not be called publicly, but it should not hurt if it is
// Completes the construction of this object once the htmlImage is loaded
__init: function (img) {
try {
this.id = img.id;
// this is a hack to make three different cases work together:
// - small empty image, thumbnail images, and canvases
this.width = ('naturalWidth' in img) ? Math.max(img.naturalWidth, img.width) : img.width;
this.height = ('naturalHeight' in img) ? Math.max(img.naturalHeight, img.height) : img.height;
this.canvas.width = this.width;
this.canvas.height = this.height;
// are we copying an already loaded image or drawing it fresh
if (img instanceof HTMLCanvasElement) {
var canvasData = img.getContext('2d').getImageData(0, 0, this.width, this.height);
this.context.putImageData(canvasData, 0, 0);
}
else {
this.context.drawImage(img, 0, 0, this.width, this.height);
}
this.imageData = this.context.getImageData(0, 0, this.width, this.height);
}
catch (err) {
console.log(err);
__SimpleImageUtilities.throwError('The name you used to create a SimpleImage was not correct: ' + img.id);
}
},
// computes index into 1-d array, and checks correctness of x,y values
__getIndex: function (funName, x, y) {
__SimpleImageUtilities.rangeCheck(x, 0, this.getWidth(), funName, 'x', 'wide');
__SimpleImageUtilities.rangeCheck(y, 0, this.getHeight(), funName, 'y', 'tall');
return (Math.floor(x) + Math.floor(y) * this.getWidth()) * 4;
}
};
// Private helper functions, add __ to reduce chance they will conflict with anyone else's method names
var __SimpleImageUtilities = (function () {
// private globals
// image needed to seed "sized" image
var EMPTY_IMAGE_DATA = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAQAAAAnZu5uAAAAAXNSR0IArs4c6QAAABVJREFUeJxiYPgPhyQwAQAAAP//AwCgshjoJhZxhgAAAABJRU5ErkJggg==';
// number of canvases created to hold images
var globalCanvasCount = 0;
// load image by wrapping it in an HTML element
function makeHTMLImage(url, name, simpleImage, loadFunc) {
if (loadFunc == null) {
loadFunc = function () {
simpleImage.__init(this);
console.log('loaded image: ' + simpleImage.id);
}
}
var img = new Image();
img.onload = loadFunc;
img.src = url;
img.id = name;
img.style.display = 'none';
return img;
}
// public utility functions
return {
// make a blank image so it is cached for future uses
EMPTY_IMAGE: makeHTMLImage(EMPTY_IMAGE_DATA, 'EMPTY', null, function () { }),
// create a canvas element
makeHTMLCanvas: function (prefix) {
var canvas = document.createElement('canvas');
canvas.id = prefix + globalCanvasCount;
canvas.style.display = 'none';
canvas.innerHTML = 'Your browser does not support HTML5.'
globalCanvasCount++;
return canvas;
},
// get image from uploaded file input
makeHTMLImageFromInput: function (file, simpleImage) {
console.log('creating image: ' + file.name);
var reader = new FileReader();
reader.onload = function () {
makeHTMLImage(this.result, file.name.substr(file.name.lastIndexOf('/') + 1), simpleImage);
}
reader.readAsDataURL(file);
return null;
},
// get image from a relative URL
makeHTMLImageFromURL: function (url, simpleImage) {
var name = url.substr(0, url.indexOf(';'));
console.log('creating image: ' + name);
if (url.substr(0, 4) != 'http') {
return makeHTMLImage(url, name, simpleImage);
}
else {
// does not work --- loading image from URL taints the canvas so we cannot use it :(
__SimpleImageUtilities.throwError('Unfortunately you cannot create a SimpleImage directly from a URL: ' + url);
}
},
// create an empty image of the given size
makeHTMLImageFromSize: function (width, height) {
console.log('creating image: ' + width + 'x' + height);
var img = __SimpleImageUtilities.EMPTY_IMAGE.cloneNode(true);
img.width = width;
img.height = height;
return img;
},
// set size of the image to the given values, scaling the pixels
changeSize: function (canvasOld, newWidth, newHeight) {
var canvasNew = __SimpleImageUtilities.makeHTMLCanvas('setSize_');
canvasNew.width = newWidth;
canvasNew.height = newHeight;
// draw old canvas to new canvas
var contextNew = canvasNew.getContext('2d');
contextNew.drawImage(canvasOld, 0, 0, newWidth, newHeight);
return contextNew.getImageData(0, 0, newWidth, newHeight);
},
// clamp values to be in the range 0..255
clamp: function (value) {
return Math.max(0, Math.min(Math.floor(value), 255));
},
// push accumulated local changes out to the screen
flush: function (context, imageData) {
if (imageData != null) {
context.putImageData(imageData, 0, 0, 0, 0, imageData.width, imageData.height);
}
},
// call this to abort with a message
throwError: function (message) {
throw new Error(message);
},
// called from user-facing functions to check number of arguments
funCheck: function (funcName, expectedLen, actualLen) {
if (expectedLen != actualLen) {
var s1 = (actualLen == 1) ? '' : 's'; // pluralize correctly
var s2 = (expectedLen == 1) ? '' : 's';
var message = 'You tried to call ' + funcName + ' with ' + actualLen + ' value' + s1 +
', but it expects ' + expectedLen + ' value' + s2 + '.';
// someday: think about "values" vs. "arguments" here
__SimpleImageUtilities.throwError(message);
}
},
// called from user-facing functions to check if given value is valid
rangeCheck: function (value, low, high, funName, coordName, size) {
if (value < low || value >= high) {
var message = 'You tried to call ' + funName + ' for a pixel with ' + coordName + '-coordinate of ' + value +
' in an image that is only ' + high + ' pixels ' + size +
' (valid ' + coordName + ' coordinates are ' + low + ' to ' + (high - 1) + ').';
__SimpleImageUtilities.throwError(message);
}
}
};
})();