on bb2f028 help sign in
elk/runtime/Render/Collage.js
263 lines 7.9 kB history raw
2ElmRuntime.Render.Collage = function() {
3'use strict';
5var Render = ElmRuntime.use(ElmRuntime.Render.Element);
6var Matrix = Elm.Matrix2D({});
7var Utils = ElmRuntime.use(ElmRuntime.Render.Utils);
8var newElement = Utils.newElement, addTo = Utils.addTo,
9 extract = Utils.extract, fromList = Utils.fromList,
10 fromString = Utils.fromString, addTransform = Utils.addTransform;
12function trace(ctx, path) {
13 var points = fromList(path);
14 var i = points.length - 1;
15 if (i <= 0) return;
16 ctx.moveTo(points[i]._0, points[i]._1);
17 while (i--) { ctx.lineTo(points[i]._0, points[i]._1); }
18 if (path.closed) {
19 i = points.length - 1;
20 ctx.lineTo(points[i]._0, points[i]._1);
21 }
24function line(ctx,style,path) {
25 style.dashing.ctor === 'Nil' ? trace(ctx, path) : customLineHelp(ctx, style, path);
26 ctx.stroke();
29function customLineHelp(ctx, style, path) {
30 var points = fromList(path);
31 if (path.closed) points.push(points[0]);
32 var pattern = fromList(style.dashing);
33 var i = points.length - 1;
34 if (i <= 0) return;
35 var x0 = points[i]._0, y0 = points[i]._1;
36 var x1=0, y1=0, dx=0, dy=0, remaining=0, nx=0, ny=0;
37 var pindex = 0, plen = pattern.length;
38 var draw = true, segmentLength = pattern[0];
39 ctx.moveTo(x0,y0);
40 while (i--) {
41 x1 = points[i]._0; y1 = points[i]._1;
42 dx = x1 - x0; dy = y1 - y0;
43 remaining = Math.sqrt(dx * dx + dy * dy);
44 while (segmentLength <= remaining) {
45 x0 += dx * segmentLength / remaining;
46 y0 += dy * segmentLength / remaining;
47 ctx[draw ? 'lineTo' : 'moveTo'](x0, y0);
48 // update starting position
49 dx = x1 - x0; dy = y1 - y0;
50 remaining = Math.sqrt(dx * dx + dy * dy);
51 // update pattern
52 draw = !draw;
53 pindex = (pindex + 1) % plen;
54 segmentLength = pattern[pindex];
55 }
56 if (remaining > 0) {
57 ctx[draw ? 'lineTo' : 'moveTo'](x1, y1);
58 segmentLength -= remaining;
59 }
60 x0 = x1; y0 = y1;
61 }
64function drawLine(ctx, style, path) {
65 ctx.lineWidth = style.width;
66 ctx.lineCap = style.cap.ctor.toLowerCase();
67 var join = style.join.ctor;
68 ctx.lineJoin = join === 'Smooth' ? 'round' :
69 join === 'Sharp' ? 'miter' : 'bevel';
70 ctx.miterLimit = style.miterLimit;
71 ctx.strokeStyle = extract(style.color);
72 return line(ctx, style, path);
75function texture(redo, ctx, src) {
76 var img = new Image();
77 img.src = fromString(src);
78 img.onload = redo;
79 return ctx.createPattern(img, 'repeat');
82function gradient(ctx, grad) {
83 var g;
84 if (grad.ctor === 'Linear') {
85 var p1 = grad._1, p2 = grad._2;
86 g = ctx.createLinearGradient(p1._0, p1._1, p2._0, p2._1);
87 } else {
88 var p1 = grad._1, p2 = grad._3;
89 g = ctx.createRadialGradient(p1._0, p1._1, grad._2, p2._0, p2._1, grad._4);
90 }
91 var stops = fromList(grad._0);
92 for (var i = stops.length; i--; ) {
93 var stop = stops[i];
94 g.addColorStop(stop._0, extract(stop._1));
95 }
96 return g;
99function drawShape(redo, ctx, style, path) {
100 trace(ctx, path);
101 var sty = style.ctor;
102 ctx.fillStyle =
103 sty === 'Solid' ? extract(style._0) :
104 sty === 'Texture' ? texture(redo, ctx, style._0) : gradient(ctx, style._0);
105 ctx.fill();
108function drawImage(redo, ctx, form) {
109 var img = new Image();
110 img.onload = redo;
111 img.src = fromString(form._3);
112 var w = form._0, h = form._1, pos = form._2;
113 ctx.drawImage(img, pos._0, pos._1, w, h, -w/2, -h/2, w, h);
116function renderForm(redo, ctx, form) {
117 ctx.save();
118 var x = form.x, y = form.y, theta = form.theta, scale = form.scale;
119 if (x !== 0 || y !== 0) ctx.translate(x, -y);
120 if (theta !== 0) ctx.rotate(theta);
121 if (scale !== 1) ctx.scale(scale, scale);
122 ctx.beginPath();
123 var f = form.form;
124 switch(f.ctor) {
125 case 'FPath' : drawLine(ctx, f._0, f._1); break;
126 case 'FShape':
127 if (f._0.ctor === 'Left') {
128 f._1.closed = true;
129 drawLine(ctx, f._0._0, f._1);
130 } else {
131 drawShape(redo, ctx, f._0._0, f._1);
132 }
133 break;
134 case 'FImage': drawImage(redo, ctx, f); break;
135 }
136 ctx.restore();
139function makeTransform(form, matrix) {
140 var m = matrix;
141 var x = form.x, y = form.y, theta = form.theta, scale = form.scale;
142 if (x !== 0 || y !== 0) m = A3( Matrix.move, x, y, m );
143 if (theta !== 0) m = A2( Matrix.rotate, theta, m );
144 if (scale !== 1) m = Matrix.scale( scale );
145 return m;
148function collageElement(width, height, matrices, form) {
149 var elem = form.form._0,
150 e = Render.render(elem),
151 m = A6( Matrix.matrix, 1, 0, 0, 1,
152 (width - elem.props.width)/2,
153 (elem.props.height - height)/2 );
154 var len = matrices.length;
155 for (var i = 0; i < len; ++i) { m = A2( Matrix.multiply, m, matrices[i] ); }
156 m = makeTransform(form, m);
157 var mtrx = 'matrix(' + m[0] + ',' + m[3] + ',' + m[1] + ',' +
158 m[4] + ',' + m[2] + ',' + (-m[5]) + ')';
159 addTransform(e.style, mtrx);
160 var div = newElement('div');
161 addTo(div,e);
162 div.style.width = width + 'px';
163 div.style.height = height + 'px';
164 div.style.overflow = 'hidden';
165 div.style.position = 'absolute';
166 return div;
169function stepperHelp(group) {
170 var i = 0;
171 function hasNext() { return i < group.length; }
172 // assumes that there is a next element
173 function next() {
174 var out = group[i];
175 ++i;
176 return out;
177 }
178 return { hasNext:hasNext, next:next };
181function stepper(forms) {
182 var ps = [stepperHelp(fromList(forms))];
183 var matrices = [];
184 function hasNext() {
185 var len = ps.length;
186 for (var i = 0; i < len; ++i ) {
187 if (ps[i].hasNext()) return true;
188 }
189 return false;
190 }
191 // assumes that there is a next element
192 function next(ctx) {
193 while (!ps[0].hasNext()) { ps.shift(); matrices.pop(); ctx.restore(); }
194 var out = ps[0].next();
195 if (out.ctor === 'FGroup') {
196 ps.unshift(stepperHelp(out._1));
197 matrices.push(out._0);
198 ctx.save();
199 var m = f._0;
200 ctx.transform(m[0], m[3], m[1], m[4], m[2], -m[5]);
201 }
202 return out;
203 }
204 function transforms() { return matrices; }
205 return { hasNext:hasNext, next:next, transforms:transforms };
208function makeCanvas(w,h) {
209 var canvas = newElement('canvas');
210 canvas.style.width = w + 'px';
211 canvas.style.height = h + 'px';
212 canvas.style.display = "block";
213 canvas.style.position = "absolute";
214 canvas.width = w;
215 canvas.height = h;
216 return canvas;
219function render(model) {
220 var w = model.w,
221 h = model.h,
222 div = newElement('div'),
223 canvas = makeCanvas(w,h),
224 ctx = canvas.getContext('2d');
225 div.appendChild(canvas);
226 ctx.translate(w/2, h/2);
228 var stpr = stepper(model.forms);
229 while (stpr.hasNext()) {
230 var form = stpr.next(ctx);
231 if (form.form.ctor === 'FElement') {
232 div.appendChild(collageElement(w, h, stpr.transforms(), form));
233 if (!stpr.hasNext()) return div;
234 canvas = makeCanvas(w,h);
235 div.appendChild(canvas);
236 ctx = canvas.getContext('2d');
237 ctx.translate(w/2, h/2);
238 var transforms = stpr.transforms();
239 var len = transforms.length;
240 for (var i = 0; i < len; ++i) {
241 var m = transforms[i];
242 ctx.save();
243 ctx.transform(m[0], m[3], m[1], m[4], m[2], m[5]);
244 }
245 } else if (form.form.ctor !== 'FGroup') {
246 renderForm(function() { update(div, model, model); }, ctx, form);
247 }
248 }
249 return div;
252function update(node, oldModel, newModel) {
253 if (!node.getContext) {
254 return node.parentNode.replaceChild(render(newModel), node);
255 }
256 var ctx = node.getContext('2d');
257 var w = newModel.w, h = newModel.h;
258 ctx.clearRect(-w/2, -h/2, w, h);
261return { render:render, update:update };
263};