Skip to content

Commit 4c69efe

Browse files
committed
v.1.2.0, Pattern.createPattern
1 parent 87ad60c commit 4c69efe

File tree

4 files changed

+211
-77
lines changed

4 files changed

+211
-77
lines changed

README.md

Lines changed: 35 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# Gradient
22

3-
Class to create **linear, radial, conic and elliptic gradients** as bitmaps without canvas
3+
Class to create **linear, radial, conic and elliptic gradients** and **image patterns** as bitmaps without canvas
44

5-
**version 1.1.1** (10 kB minified)
5+
**version 1.2.0** (12 kB minified)
66

77
**API:**
88

@@ -25,6 +25,20 @@ grad.transform.reset();
2525

2626
grad.getColorAt(x, y);
2727
grad.getBitmap(width, height);
28+
29+
// create patterns
30+
const pat = Gradient.createPattern(imageData, repetition = 'repeat');
31+
32+
// methods
33+
pat.transform.scale(sx, sy);
34+
pat.transform.rotate(angle);
35+
pat.transform.translate(tx, ty);
36+
pat.transform.skewX(s);
37+
pat.transform.skewY(s);
38+
pat.transform.reset();
39+
40+
pat.getColorAt(x, y);
41+
pat.getBitmap(width, height);
2842
```
2943

3044
**Example:**
@@ -47,16 +61,16 @@ function drawLinearGradient(x1, y1, x2, y2)
4761
const canvas2 = document.getElementById('linear2');
4862
const ctx1 = canvas1.getContext("2d");
4963
const ctx2 = canvas2.getContext("2d");
50-
64+
5165
const gradient1 = ctx1.createLinearGradient(x1, y1, x2, y2);
5266
const gradient2 = Gradient.createLinearGradient(x1, y1, x2, y2);
53-
67+
5468
addColorStops(gradient1);
5569
addColorStops(gradient2);
56-
70+
5771
ctx1.fillStyle = gradient1;
5872
ctx1.fillRect(0, 0, w, h);
59-
73+
6074
const imData = ctx2.createImageData(w, h);
6175
imData.data.set(gradient2.getBitmap(w, h));
6276
ctx2.putImageData(imData, 0, 0);
@@ -70,13 +84,13 @@ function drawRadialGradient(x1, y1, r1, x2, y2, r2)
7084

7185
const gradient1 = ctx1.createRadialGradient(x1, y1, r1, x2, y2, r2);
7286
const gradient2 = Gradient.createRadialGradient(x1, y1, r1, x2, y2, r2);
73-
87+
7488
addColorStops(gradient1);
7589
addColorStops(gradient2);
76-
90+
7791
ctx1.fillStyle = gradient1;
7892
ctx1.fillRect(0, 0, w, h);
79-
93+
8094
const imData = ctx2.createImageData(w, h);
8195
imData.data.set(gradient2.getBitmap(w, h));
8296
ctx2.putImageData(imData, 0, 0);
@@ -85,66 +99,43 @@ function drawEllipticGradient(cx, cy, rx, ry, angle)
8599
{
86100
const canvas1 = document.getElementById('elliptic1');
87101
const canvas2 = document.getElementById('elliptic2');
88-
const canvas3 = document.getElementById('elliptic3');
89-
const canvas4 = document.getElementById('elliptic4');
90102
const ctx1 = canvas1.getContext("2d");
91103
const ctx2 = canvas2.getContext("2d");
92-
const ctx3 = canvas3.getContext("2d");
93-
const ctx4 = canvas4.getContext("2d");
94-
104+
95105
const r = Math.max(rx, ry), sx = rx/r, sy = ry/r;
96-
106+
97107
const gradient1 = ctx1.createRadialGradient(cx/sx, cy/sy, 0, cx/sx, cy/sy, r);
98-
const gradient2 = Gradient.createRadialGradient(cx, cy, 0, cx, cy, r);
99-
const gradient3 = ctx3.createRadialGradient(cx, cy, 0, cx, cy, r);
100-
const gradient4 = Gradient.createEllipticGradient(cx, cy, rx, ry, angle);
101-
108+
const gradient2 = Gradient.createEllipticGradient(cx, cy, rx, ry, 0/*angle*/);
109+
102110
addColorStops(gradient1);
103111
addColorStops(gradient2);
104-
addColorStops(gradient3);
105-
addColorStops(gradient4);
106-
107-
// transform radial gradient1 to become an unrotated elliptic
112+
113+
// scale radial gradient1 to become an unrotated elliptic
108114
ctx1.scale(sx, sy);
109115
ctx1.fillStyle = gradient1;
110116
ctx1.fillRect(0, 0, w/sx, h/sy);
111-
// gradient2 is a transformed radial gradient
112-
gradient2.transform.translate(-cx, -cy);
113-
gradient2.transform.scale(sx, sy);
114-
//gradient2.transform.rotate(-angle);
115-
gradient2.transform.translate(cx, cy);
117+
118+
// gradient2 is true elliptic gradient
116119
const imData2 = ctx2.createImageData(w, h);
117120
imData2.data.set(gradient2.getBitmap(w, h));
118121
ctx2.putImageData(imData2, 0, 0);
119-
120-
// transform radial gradient3 to become a rotated elliptic (does not produce expected result)
121-
ctx3.scale(sx, sy);
122-
ctx3.translate(-cx, -cy);
123-
ctx3.rotate(-angle);
124-
ctx3.translate(cx, cy);
125-
ctx3.fillStyle = gradient3;
126-
ctx3.fillRect(0, 0, w, h);
127-
// gradient4 is elliptic gradient
128-
const imData4 = ctx4.createImageData(w, h);
129-
imData4.data.set(gradient4.getBitmap(w, h));
130-
ctx4.putImageData(imData4, 0, 0);
131122
}
132123
function drawConicGradient(angle, cx, cy)
133124
{
134125
const canvas1 = document.getElementById('conic1');
135126
const canvas2 = document.getElementById('conic2');
136127
const ctx1 = canvas1.getContext("2d");
137128
const ctx2 = canvas2.getContext("2d");
138-
129+
139130
const gradient1 = ctx1.createConicGradient(angle, cx, cy);
140131
const gradient2 = Gradient.createConicGradient(angle, cx, cy);
141-
132+
142133
addColorStops(gradient1);
143134
addColorStops(gradient2);
144-
135+
145136
ctx1.fillStyle = gradient1;
146137
ctx1.fillRect(0, 0, w, h);
147-
138+
148139
const imData = ctx2.createImageData(w, h);
149140
imData.data.set(gradient2.getBitmap(w, h));
150141
ctx2.putImageData(imData, 0, 0);

src/Gradient.js

Lines changed: 144 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,16 @@
22
* Gradient
33
* class to create linear/radial/elliptical/conic gradients as bitmaps even without canvas
44
*
5-
* @version 1.1.1
5+
* @version 1.2.0
66
* https://github.com/foo123/Gradient
77
*
88
**/
99
!function(root, name, factory) {
1010
"use strict";
1111
if (('object' === typeof module) && module.exports) /* CommonJS */
12-
(module.$deps = module.$deps||{}) && (module.exports = module.$deps[name] = factory.call(root));
12+
module.exports = factory.call(root);
1313
else if (('function' === typeof define) && define.amd && ('function' === typeof require) && ('function' === typeof require.specified) && require.specified(name) /*&& !require.defined(name)*/) /* AMD */
14-
define(name, ['module'], function(module) {factory.moduleUri = module.uri; return factory.call(root);});
14+
define(name, ['module'], function(module) {return factory.call(root);});
1515
else if (!(name in root)) /* Browser/WebWorker/.. */
1616
(root[name] = factory.call(root)||1) && ('function' === typeof(define)) && define.amd && define(function() {return root[name];});
1717
}( /* current root */ 'undefined' !== typeof self ? self : this,
@@ -23,6 +23,7 @@ var HAS = Object.prototype.hasOwnProperty,
2323
stdMath = Math, PI = stdMath.PI, TWO_PI = 2*PI, HALF_PI = PI/2, EPS = 1e-6,
2424
ImArray = 'undefined' !== typeof Uint8ClampedArray ? Uint8ClampedArray : ('undefined' !== typeof Uint8Array ? Uint8Array : Array);
2525

26+
// Gradient Pattern
2627
function Gradient(grad_color_at)
2728
{
2829
if (
@@ -71,23 +72,25 @@ function Gradient(grad_color_at)
7172
self.addColorStop = function(offset, color) {
7273
_stops = null;
7374
stops[String(offset)] = [+offset, parseColor(color) || [0,0,0,0]];
74-
}
75+
};
7576
self.getColorAt = function(x, y) {
7677
var p = imatrix.transform(x, y);
7778
return grad_color_at(p.x, p.y, colorStops(), new ImArray(4), 0);
7879
};
79-
self.getBitmap = function(w, h) {
80-
var color_stops = colorStops(), i, x, y, p, size = (w*h) << 2, bmp = new ImArray(size);
80+
self.getBitmap = function(width, height) {
81+
width = stdMath.round(width);
82+
height = stdMath.round(height);
83+
var color_stops = colorStops(), i, x, y, p, size = (width*height) << 2, bmp = new ImArray(size);
8184
for (x=0,y=0,i=0; i<size; i+=4,++x)
8285
{
83-
if (x >= w) {x=0; ++y;}
86+
if (x >= width) {x=0; ++y;}
8487
p = imatrix.transform(x, y);
8588
grad_color_at(p.x, p.y, color_stops, bmp, i);
8689
}
8790
return bmp;
8891
};
8992
}
90-
Gradient.VERSION = "1.1.1";
93+
Gradient.VERSION = "1.2.0";
9194
Gradient.prototype = {
9295
constructor: Gradient,
9396
transform: null,
@@ -241,6 +244,138 @@ Gradient.createEllipticGradient = function(cx, cy, rx, ry, angle) {
241244
});
242245
};
243246

247+
// Image Pattern
248+
function Pattern(pat_color_at)
249+
{
250+
if (
251+
!(this instanceof Pattern) ||
252+
('function' !== typeof pat_color_at) ||
253+
(4 > pat_color_at.length)
254+
)
255+
{
256+
throw new Error('Pattern: invalid pattern');
257+
}
258+
259+
var self = this, imatrix = new Matrix();
260+
261+
self.transform = {
262+
reset: function() {
263+
imatrix = new Matrix();
264+
},
265+
scale: function(sx, sy, ox, oy) {
266+
imatrix = imatrix.mul(Matrix.scale(1/sx, 1/sy, ox, oy));
267+
},
268+
rotate: function(theta, ox, oy) {
269+
imatrix = imatrix.mul(Matrix.rotate(-theta, ox, oy));
270+
},
271+
translate: function(tx, ty) {
272+
imatrix = imatrix.mul(Matrix.translate(-tx, -ty));
273+
},
274+
skewX: function(s) {
275+
imatrix = imatrix.mul(Matrix.skewX(s).inv());
276+
},
277+
skewX: function(s) {
278+
imatrix = imatrix.mul(Matrix.skewY(s).inv());
279+
}
280+
};
281+
self.getColorAt = function(x, y) {
282+
var p = imatrix.transform(x, y);
283+
return pat_color_at(p.x, p.y, new ImArray(4), 0);
284+
};
285+
self.getBitmap = function(width, height) {
286+
width = stdMath.round(width);
287+
height = stdMath.round(height);
288+
var i, x, y, p, size = (width*height) << 2, bmp = new ImArray(size);
289+
for (x=0,y=0,i=0; i<size; i+=4,++x)
290+
{
291+
if (x >= width) {x=0; ++y;}
292+
p = imatrix.transform(x, y);
293+
pat_color_at(p.x, p.y, bmp, i);
294+
}
295+
return bmp;
296+
};
297+
}
298+
Pattern.prototype = {
299+
constructor: Pattern,
300+
transform: null,
301+
getColorAt: null,
302+
getBitmap: null
303+
};
304+
Pattern.createPattern = function(imageData, repetition) {
305+
if (imageData && imageData.data && imageData.width && imageData.height && (imageData.data.length === 4*imageData.width*imageData.height))
306+
{
307+
var width = imageData.width, height = imageData.height;
308+
switch (repetition)
309+
{
310+
case 'no-repeat':
311+
return new Pattern(function(x, y, pixel, i) {
312+
x = stdMath.round(x);
313+
y = stdMath.round(y);
314+
if (0 <= x && x < width && 0 <= y && y < height)
315+
{
316+
var j = (x + y*width) << 2;
317+
pixel[i + 0] = imageData.data[j + 0];
318+
pixel[i + 1] = imageData.data[j + 1];
319+
pixel[i + 2] = imageData.data[j + 2];
320+
pixel[i + 3] = imageData.data[j + 3];
321+
}
322+
});
323+
case 'repeat-x':
324+
return new Pattern(function(x, y, pixel, i) {
325+
x = stdMath.round(x);
326+
y = stdMath.round(y);
327+
if (0 <= y && y < height)
328+
{
329+
x = x % width;
330+
if (0 > x) x += width;
331+
var j = (x + y*width) << 2;
332+
pixel[i + 0] = imageData.data[j + 0];
333+
pixel[i + 1] = imageData.data[j + 1];
334+
pixel[i + 2] = imageData.data[j + 2];
335+
pixel[i + 3] = imageData.data[j + 3];
336+
}
337+
});
338+
case 'repeat-y':
339+
return new Pattern(function(x, y, pixel, i) {
340+
x = stdMath.round(x);
341+
y = stdMath.round(y);
342+
if (0 <= x && x < width)
343+
{
344+
y = y % height;
345+
if (0 > y) y += height;
346+
var j = (x + y*width) << 2;
347+
pixel[i + 0] = imageData.data[j + 0];
348+
pixel[i + 1] = imageData.data[j + 1];
349+
pixel[i + 2] = imageData.data[j + 2];
350+
pixel[i + 3] = imageData.data[j + 3];
351+
}
352+
});
353+
case 'repeat':
354+
default:
355+
return new Pattern(function(x, y, pixel, i) {
356+
x = stdMath.round(x);
357+
y = stdMath.round(y);
358+
x = x % width;
359+
if (0 > x) x += width;
360+
y = y % height;
361+
if (0 > y) y += height;
362+
var j = (x + y*width) << 2;
363+
pixel[i + 0] = imageData.data[j + 0];
364+
pixel[i + 1] = imageData.data[j + 1];
365+
pixel[i + 2] = imageData.data[j + 2];
366+
pixel[i + 3] = imageData.data[j + 3];
367+
});
368+
}
369+
}
370+
else
371+
{
372+
throw new Error('Pattern: invalid image data');
373+
}
374+
};
375+
Gradient.Pattern = Pattern;
376+
Gradient.createPattern = Pattern.createPattern;
377+
378+
// Homogeneous Transformation Matrix
244379
function Matrix(m00, m01, m02, m10, m11, m12)
245380
{
246381
var self = this;
@@ -376,6 +511,7 @@ function binary_search(x, a, n)
376511
}
377512
return l;
378513
}
514+
// color utilities
379515
function interpolatePixel(pixel, index, rgba0, rgba1, t)
380516
{
381517
pixel[index + 0] = clamp(stdMath.round(rgba0[0] + t*(rgba1[0] - rgba0[0])), 0, 255);

0 commit comments

Comments
 (0)