Skip to content

Commit 2a9e695

Browse files
committed
v.1.1.0, support gradient matrix transforms
1 parent 455a962 commit 2a9e695

File tree

4 files changed

+215
-39
lines changed

4 files changed

+215
-39
lines changed

README.md

+33-9
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
Class to create **linear, radial, elliptical and conic gradients** as bitmaps, even without canvas
44

5-
**version 1.0.0** (9 kB minified)
5+
**version 1.1.0** (11 kB minified)
66

77
Example:
88

@@ -56,19 +56,43 @@ function drawEllipticGradient(cx, cy, rx, ry, angle)
5656
{
5757
const canvas1 = document.getElementById('elliptic1');
5858
const canvas2 = document.getElementById('elliptic2');
59-
const ctx = canvas1.getContext("2d");
59+
const canvas3 = document.getElementById('elliptic3');
60+
const canvas4 = document.getElementById('elliptic4');
6061
const r = Math.max(rx, ry), sx = rx/r, sy = ry/r;
62+
const ctx1 = canvas1.getContext("2d");
63+
const ctx3 = canvas3.getContext("2d");
64+
// grad1 is a transformed radial gradient
65+
const grad1 = Gradient.createRadialGradient(cx, cy, 0, cx, cy, r);
66+
grad1.translate(-cx, -cy);
67+
grad1.scale(sx, sy);
68+
//grad1.rotate(-angle);
69+
grad1.translate(cx, cy);
70+
// grad2 is elliptic gradient
71+
const grad2 = Gradient.createEllipticGradient(cx, cy, rx, ry, angle);
72+
6173
applyGradient(
62-
canvas1.getContext("2d").createRadialGradient(cx/sx, cy/sy, 0, cx/sx, cy/sy, r),
74+
ctx1.createRadialGradient(cx/sx, cy/sy, 0, cx/sx, cy/sy, r),
6375
canvas1,
64-
Gradient.createEllipticGradient(cx, cy, rx, ry, /*angle*/0),
76+
grad1,
6577
canvas2
6678
);
67-
ctx.scale(sx, sy);
68-
/*ctx.translate(-cx/sx, -cy/sy);
69-
ctx.rotate(angle);
70-
ctx.translate(cx/sx, cy/sy);*/
71-
ctx.fillRect(0, 0, w/sx, h/sy);
79+
// transform radial gradient to become an unrotated elliptic
80+
ctx1.scale(sx, sy);
81+
ctx1.fillRect(0, 0, w/sx, h/sy);
82+
83+
84+
applyGradient(
85+
ctx3.createRadialGradient(cx, cy, 0, cx, cy, r),
86+
canvas3,
87+
grad2,
88+
canvas4
89+
);
90+
// transform radial gradient to become a rotated elliptic (does not produce expected result)
91+
ctx3.translate(-cx, -cy);
92+
ctx3.scale(sx, sy);
93+
ctx3.rotate(-angle);
94+
ctx3.translate(cx, cy);
95+
ctx3.fillRect(0, 0, w, h);
7296
}
7397
function drawConicGradient(angle, cx, cy)
7498
{

src/Gradient.js

+145-20
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* Gradient
33
* class to create linear/radial/elliptical/conic gradients as bitmaps even without canvas
44
*
5-
* @version 1.0.0
5+
* @version 1.1.0
66
* https://github.com/foo123/Gradient
77
*
88
**/
@@ -27,15 +27,42 @@ function Gradient(gradColorGetter)
2727
{
2828
var self = this;
2929
self._colorAt = gradColorGetter || null;
30+
self.resetTransform();
3031
self.addColorStop(0, 'transparent');
3132
self.addColorStop(1, 'transparent');
3233
}
33-
Gradient.VERSION = "1.0.0";
34+
Gradient.VERSION = "1.1.0";
3435
Gradient.prototype = {
3536
constructor: Gradient,
3637
stops: null,
3738
_stops: null,
3839
_colorAt: null,
40+
matrix: null,
41+
imatrix: null,
42+
resetTransform: function() {
43+
this.matrix = new Matrix();
44+
this.imatrix = new Matrix();
45+
},
46+
scale: function(sx, sy, ox, oy) {
47+
this.matrix = Matrix.scale(sx, sy, ox, oy).mul(this.matrix);
48+
this.imatrix = this.matrix.inv();
49+
},
50+
rotate: function(theta, ox, oy) {
51+
this.matrix = Matrix.rotate(theta, ox, oy).mul(this.matrix);
52+
this.imatrix = this.matrix.inv();
53+
},
54+
translate: function(tx, ty) {
55+
this.matrix = Matrix.translate(tx, ty).mul(this.matrix);
56+
this.imatrix = this.matrix.inv();
57+
},
58+
skewX: function(s) {
59+
this.matrix = Matrix.skewX(s).mul(this.matrix);
60+
this.imatrix = this.matrix.inv();
61+
},
62+
skewY: function(s) {
63+
this.matrix = Matrix.skewY(s).mul(this.matrix);
64+
this.imatrix = this.matrix.inv();
65+
},
3966
addColorStop: function(offset, color) {
4067
var self = this;
4168
if (null == self.stops) self.stops = {};
@@ -45,29 +72,27 @@ Gradient.prototype = {
4572
self._stops = o.map(function(o) {return self.stops[o];});
4673
},
4774
getColorAt: function(x, y) {
48-
var self = this, stops;
75+
var self = this, p;
4976
if (self._colorAt)
5077
{
51-
return self._colorAt(x, y, self._stops, new ImArray(4));
78+
p = self.imatrix.transform(x, y)
79+
return self._colorAt(p.x, p.y, self._stops, new ImArray(4), 0);
5280
}
5381
},
5482
getBitmap: function(w, h) {
55-
var self = this, color_at = self._colorAt,
56-
stops, i, x, y, size, bmp, c;
83+
var self = this, m = self.imatrix, color_at = self._colorAt,
84+
stops, i, x, y, p, size, bmp, c;
5785
if (color_at)
5886
{
59-
size = (w*h)<<2;
87+
size = (w*h) << 2;
6088
bmp = new ImArray(size);
6189
c = new ImArray(4);
6290
stops = self._stops;
6391
for (x=0,y=0,i=0; i<size; i+=4,++x)
6492
{
6593
if (x >= w) {x=0; ++y;}
66-
color_at(x, y, stops, c);
67-
bmp[i + 0] = c[0];
68-
bmp[i + 1] = c[1];
69-
bmp[i + 2] = c[2];
70-
bmp[i + 3] = c[3];
94+
p = m.transform(x, y)
95+
color_at(p.x, p.y, stops, bmp, i);
7196
}
7297
return bmp;
7398
}
@@ -82,7 +107,7 @@ Gradient.createLinearGradient = function(x1, y1, x2, y2) {
82107
vert = is_strictly_equal(dx, 0),
83108
hor = is_strictly_equal(dy, 0),
84109
f = 2*dx*dy;
85-
return new Gradient(function(x, y, stops, pixel) {
110+
return new Gradient(function(x, y, stops, pixel, i) {
86111
var t, px, py, stop1, stop2, sl = stops.length;
87112
px = x - x1; py = y - y1;
88113
t = hor && vert ? 0 : (vert ? py/dy : (hor ? px/dx : (px*dy + py*dx)/f));
@@ -102,7 +127,7 @@ Gradient.createLinearGradient = function(x1, y1, x2, y2) {
102127
stop1 = 0 === stop2 ? 0 : (stop2 - 1);
103128
}
104129
return interpolatePixel(
105-
pixel, 0,
130+
pixel, i || 0,
106131
stops[stop1][1], stops[stop2][1],
107132
// warp the value if needed, between stop ranges
108133
stops[stop2][0] > stops[stop1][0] ? (t - stops[stop1][0])/(stops[stop2][0] - stops[stop1][0]) : t
@@ -125,7 +150,7 @@ Gradient.createRadialGradient = function(x0, y0, r0, x1, y1, r1) {
125150
var a = r0*r0 - 2*r0*r1 + r1*r1 - x0*x0 + 2*x0*x1 - x1*x1 - y0*y0 + 2*y0*y1 - y1*y1,
126151
b = -2*r0*r0 + 2*r0*r1 + 2*x0*x0 - 2*x0*x1 + 2*y0*y0 - 2*y0*y1,
127152
c = -x0*x0 - y0*y0 + r0*r0;
128-
return new Gradient(function(x, y, stops, pixel) {
153+
return new Gradient(function(x, y, stops, pixel, i) {
129154
var t, px, py, pr, s, stop1, stop2, sl = stops.length;
130155
s = quadratic_roots(a, b - 2*x*x0 + 2*x*x1 - 2*y*y0 + 2*y*y1, c - x*x + 2*x*x0 - y*y + 2*y*y0);
131156
if (!s)
@@ -165,7 +190,7 @@ Gradient.createRadialGradient = function(x0, y0, r0, x1, y1, r1) {
165190
stop1 = 0 === stop2 ? 0 : (stop2 - 1);
166191
}
167192
return interpolatePixel(
168-
pixel, 0,
193+
pixel, i || 0,
169194
stops[stop1][1], stops[stop2][1],
170195
// warp the value if needed, between stop ranges
171196
stops[stop2][0] > stops[stop1][0] ? (t - stops[stop1][0])/(stops[stop2][0] - stops[stop1][0]) : t
@@ -176,7 +201,7 @@ Gradient.createConicGradient = function(angle, cx, cy) {
176201
angle = angle || 0;
177202
cx = cx || 0;
178203
cy = cy || 0;
179-
return new Gradient(function(x, y, stops, pixel) {
204+
return new Gradient(function(x, y, stops, pixel, i) {
180205
var t, stop1, stop2, sl = stops.length;
181206
t = stdMath.atan2(y - cy, x - cx) + HALF_PI - angle;
182207
if (0 > t) t += TWO_PI;
@@ -185,7 +210,7 @@ Gradient.createConicGradient = function(angle, cx, cy) {
185210
stop2 = binary_search(t, stops, sl);
186211
stop1 = 0 === stop2 ? 0 : (stop2 - 1);
187212
return interpolatePixel(
188-
pixel, 0,
213+
pixel, i || 0,
189214
stops[stop1][1], stops[stop2][1],
190215
// warp the value if needed, between stop ranges
191216
stops[stop2][0] > stops[stop1][0] ? (t - stops[stop1][0])/(stops[stop2][0] - stops[stop1][0]) : t
@@ -199,7 +224,7 @@ Gradient.createEllipticGradient = function(cx, cy, rx, ry, angle) {
199224
ry = ry || 0;
200225
angle = angle || 0;
201226
var cos = stdMath.cos(angle), sin = stdMath.sin(angle);
202-
return new Gradient(function(x, y, stops, pixel) {
227+
return new Gradient(function(x, y, stops, pixel, i) {
203228
var t, px, py, stop1, stop2, sl = stops.length;
204229
px = (cos*(x - cx) - sin*(y - cy))/rx;
205230
py = (sin*(x - cx) + cos*(y - cy))/ry;
@@ -215,14 +240,114 @@ Gradient.createEllipticGradient = function(cx, cy, rx, ry, angle) {
215240
stop1 = 0 === stop2 ? 0 : (stop2 - 1);
216241
}
217242
return interpolatePixel(
218-
pixel, 0,
243+
pixel, i || 0,
219244
stops[stop1][1], stops[stop2][1],
220245
// warp the value if needed, between stop ranges
221246
stops[stop2][0] > stops[stop1][0] ? (t - stops[stop1][0])/(stops[stop2][0] - stops[stop1][0]) : t
222247
);
223248
});
224249
};
225250

251+
function Matrix(m00, m01, m02, m10, m11, m12)
252+
{
253+
var self = this;
254+
if (arguments.length)
255+
{
256+
self.m00 = m00;
257+
self.m01 = m01;
258+
self.m02 = m02;
259+
self.m10 = m10;
260+
self.m11 = m11;
261+
self.m12 = m12;
262+
}
263+
else
264+
{
265+
self.m00 = 1;
266+
self.m01 = 0;
267+
self.m02 = 0;
268+
self.m10 = 0;
269+
self.m11 = 1;
270+
self.m12 = 0;
271+
}
272+
}
273+
Matrix.prototype = {
274+
constructor: Matrix,
275+
m00: 1,
276+
m01: 0,
277+
m02: 0,
278+
m10: 0,
279+
m11: 1,
280+
m12: 0,
281+
mul: function(other) {
282+
var self = this;
283+
return new Matrix(
284+
self.m00*other.m00 + self.m01*other.m10,
285+
self.m00*other.m01 + self.m01*other.m11,
286+
self.m00*other.m02 + self.m01*other.m12 + self.m02,
287+
self.m10*other.m00 + self.m11*other.m10,
288+
self.m10*other.m01 + self.m11*other.m11,
289+
self.m10*other.m02 + self.m11*other.m12 + self.m12
290+
);
291+
},
292+
inv: function() {
293+
var self = this,
294+
a00 = self.m00, a01 = self.m01, a02 = self.m02,
295+
a10 = self.m10, a11 = self.m11, a12 = self.m12,
296+
det2 = a00*a11 - a01*a10,
297+
i00 = 0, i01 = 0, i10 = 0, i11 = 0;
298+
299+
if (is_strictly_equal(det2, 0)) return null;
300+
i00 = a11/det2; i01 = -a01/det2;
301+
i10 = -a10/det2; i11 = a00/det2;
302+
return new Matrix(
303+
i00, i01, -i00*a02 - i01*a12,
304+
i10, i11, -i10*a02 - i11*a12
305+
);
306+
},
307+
transform: function(x, y) {
308+
var self = this;
309+
return {
310+
x: self.m00*x + self.m01*y + self.m02,
311+
y: self.m10*x + self.m11*y + self.m12
312+
};
313+
}
314+
};
315+
Matrix.translate = function(tx, ty) {
316+
return new Matrix(
317+
1, 0, tx,
318+
0, 1, ty
319+
);
320+
};
321+
Matrix.scale = function(sx, sy, ox, oy) {
322+
ox = ox || 0;
323+
oy = oy || 0;
324+
return new Matrix(
325+
sx, 0, -sx*ox + ox,
326+
0, sy, -sy*oy + oy
327+
);
328+
};
329+
Matrix.rotate = function(theta, ox, oy) {
330+
ox = ox || 0;
331+
oy = oy || 0;
332+
var cos = stdMath.cos(theta), sin = stdMath.sin(theta);
333+
return new Matrix(
334+
cos, -sin, ox - cos*ox + sin*oy,
335+
sin, cos, oy - cos*oy - sin*ox
336+
);
337+
};
338+
Matrix.skewX = function(s) {
339+
return new Matrix(
340+
1, s, 0,
341+
0, 1, 0
342+
);
343+
};
344+
Matrix.skewY = function(s) {
345+
return new Matrix(
346+
1, 0, 0,
347+
s, 1, 0
348+
);
349+
};
350+
226351
// utils
227352
function is_strictly_equal(a, b)
228353
{

0 commit comments

Comments
 (0)