-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcanvas_resize.js
165 lines (136 loc) · 5.84 KB
/
canvas_resize.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
function canvasResize(original, target, callback) {
var w1 = original.width,
h1 = original.height,
w2 = target.width,
h2 = target.height,
img = original.getContext("2d").getImageData(0, 0, w1, h1),
img2 = target.getContext("2d").getImageData(0, 0, w2, h2);
// If scaling up, use browser canvas drawImage
if (w2 > w1 || h2 > h1) {
target.getContext("2d").drawImage(original, 0, 0, w2, h2);
return callback();
}
var data = img.data;
// it's an _ because we don't use it much, as working with doubles isn't great
var _data2 = img2.data;
// Instead, we enforce float type for every entity in the array
// this prevents weird faded lines when things get rounded off
var data2 = Array(_data2.length);
for (var i = 0; i < _data2.length; i++) {
data2[i] = 0.0;
}
// We track alphas, since we need to use alphas to correct colors later on
var alphas = Array(_data2.length >> 2);
for (var i = 0; i < _data2.length >> 2; i++) {
alphas[i] = 1;
}
// this will always be between 0 and 1
var xScale = w2 / w1;
var yScale = h2 / h1;
// We process 1 row at a time ( and then let the process rest for 0ms [async] )
var nextY = function(y1) {
for (var x1 = 0; x1 < w1; x1++) {
var // the original pixel is split between two pixels in the output, we do an extra step
extraX = false,
extraY = false,
// the output pixel
targetX = Math.floor(x1 * xScale),
targetY = Math.floor(y1 * yScale),
// The percentage of this pixel going to the output pixel (this gets modified)
xFactor = xScale,
yFactor = yScale,
// The percentage of this pixel going to the right neighbor or bottom neighbor
bottomFactor = 0,
rightFactor = 0,
// positions of pixels in the array
offset = (y1 * w1 + x1) * 4,
targetOffset = (targetY * w2 + targetX) * 4;
// Right side goes into another pixel
if (targetX < Math.floor((x1 + 1) * xScale)) {
rightFactor = ((x1 + 1) * xScale) % 1;
xFactor -= rightFactor;
extraX = true;
}
// Bottom side goes into another pixel
if (targetY < Math.floor((y1 + 1) * yScale)) {
bottomFactor = ((y1 + 1) * yScale) % 1;
yFactor -= bottomFactor;
extraY = true;
}
var a;
a = data[offset + 3] / 255;
var alphaOffset = targetOffset / 4;
if (extraX) {
// Since we're not adding the color of invisible pixels, we multiply by a
data2[targetOffset + 4] += data[offset] * rightFactor * yFactor * a;
data2[targetOffset + 5] += data[offset + 1] * rightFactor * yFactor * a;
data2[targetOffset + 6] += data[offset + 2] * rightFactor * yFactor * a;
data2[targetOffset + 7] += data[offset + 3] * rightFactor * yFactor;
// if we left out the color of invisible pixels(fully or partly)
// the entire average we end up with will no longer be out of 255
// so we subtract the percentage from the alpha ( originally 1 )
// so that we can reverse this effect by dividing by the amount.
// ( if one pixel is black and invisible, and the other is white and visible,
// the white pixel will weight itself at 50% because it does not know the other pixel is invisible
// so the total(color) for the new pixel would be 128(gray), but it should be all white.
// the alpha will be the correct 128, combinging alphas, but we need to preserve the color
// of the visible pixels )
alphas[alphaOffset + 1] -= (1 - a) * rightFactor * yFactor;
}
if (extraY) {
data2[targetOffset + w2 * 4] +=
data[offset] * xFactor * bottomFactor * a;
data2[targetOffset + w2 * 4 + 1] +=
data[offset + 1] * xFactor * bottomFactor * a;
data2[targetOffset + w2 * 4 + 2] +=
data[offset + 2] * xFactor * bottomFactor * a;
data2[targetOffset + w2 * 4 + 3] +=
data[offset + 3] * xFactor * bottomFactor;
alphas[alphaOffset + w2] -= (1 - a) * xFactor * bottomFactor;
}
if (extraX && extraY) {
data2[targetOffset + w2 * 4 + 4] +=
data[offset] * rightFactor * bottomFactor * a;
data2[targetOffset + w2 * 4 + 5] +=
data[offset + 1] * rightFactor * bottomFactor * a;
data2[targetOffset + w2 * 4 + 6] +=
data[offset + 2] * rightFactor * bottomFactor * a;
data2[targetOffset + w2 * 4 + 7] +=
data[offset + 3] * rightFactor * bottomFactor;
alphas[alphaOffset + w2 + 1] -= (1 - a) * rightFactor * bottomFactor;
}
data2[targetOffset] += data[offset] * xFactor * yFactor * a;
data2[targetOffset + 1] += data[offset + 1] * xFactor * yFactor * a;
data2[targetOffset + 2] += data[offset + 2] * xFactor * yFactor * a;
data2[targetOffset + 3] += data[offset + 3] * xFactor * yFactor;
alphas[alphaOffset] -= (1 - a) * xFactor * yFactor;
}
if (y1++ < h1) {
// Big images shouldn't block for a long time.
// This breaks up the process and allows other processes to tick
setTimeout(function() {
nextY(y1);
}, 0);
} else done();
};
var done = function() {
// fully distribute the color of pixels that are partially full because their neighbor is transparent
// (i.e. undo the invisible pixels are averaged with visible ones)
for (var i = 0; i < _data2.length >> 2; i++) {
if (alphas[i] && alphas[i] < 1) {
data2[i << 2] /= alphas[i]; // r
data2[(i << 2) + 1] /= alphas[i]; // g
data2[(i << 2) + 2] /= alphas[i]; // b
}
}
// re populate the actual imgData
for (var i = 0; i < data2.length; i++) {
_data2[i] = Math.round(data2[i]);
}
var context = target.getContext("2d");
context.putImageData(img2, 0, 0);
callback();
};
// Start processing the image at row 0
nextY(0);
}