egra: agg mini clipping bugfix; some fixes in widget rendering (buttons, etc.)
[iv.d.git] / nanovega_demo / svgdraw.d
blobb856a64cb69a8f5aaacf6e0179e601b185fba8e4
1 //
2 // Copyright (c) 2013 Mikko Mononen memon@inside.org
3 //
4 // This software is provided 'as-is', without any express or implied
5 // warranty. In no event will the authors be held liable for any damages
6 // arising from the use of this software.
7 // Permission is granted to anyone to use this software for any purpose,
8 // including commercial applications, and to alter it and redistribute it
9 // freely, subject to the following restrictions:
10 // 1. The origin of this software must not be misrepresented; you must not
11 // claim that you wrote the original software. If you use this software
12 // in a product, an acknowledgment in the product documentation would be
13 // appreciated but is not required.
14 // 2. Altered source versions must be plainly marked as such, and must not be
15 // misrepresented as being the original software.
16 // 3. This notice may not be removed or altered from any source distribution.
18 module svgdraw;
20 import core.stdc.stdio;
21 import iv.nanovega;
22 import iv.nanovega.svg;
23 import iv.strex;
24 import iv.vfs;
25 import iv.vfs.util;
27 import arsd.simpledisplay;
28 import arsd.color;
29 import arsd.png;
30 import arsd.jpeg;
33 // ////////////////////////////////////////////////////////////////////////// //
34 __gshared int bezierCount = 0;
35 __gshared bool nativeGradients = true;
36 __gshared bool nativeFill = true;
37 __gshared bool nativeStroke = true;
38 __gshared bool nativeOnlyBeziers = false;
40 enum PathMode { Original, EvenOdd, Flipping, AllHoles, NoHoles }
42 void render (NVGContext nvg, const(NSVG)* image, PathMode pathMode=PathMode.Flipping) {
43 NVGColor xcolor (uint clr, float a) {
44 if (a <= 0 || (clr>>24) == 0) return NVGColor.transparent;
45 if (a > 1) a = 1;
46 float ca = (clr>>24)/255.0f;
47 a *= ca;
48 if (a <= 0) return NVGColor.transparent;
49 if (a > 1) a = 1;
50 uint aa = cast(uint)(0xff*a)<<24;
51 return NVGColor.fromUint((clr&0xffffff)|aa);
54 NVGPaint createLinearGradient (const(NSVG.Gradient)* gradient, float a) {
55 float sx, sy, ex, ey;
57 NVGMatrix inverse = NVGMatrix(gradient.xform).inverted;
58 inverse.point(&sx, &sy, 0, 0);
59 inverse.point(&ex, &ey, 0, 1);
61 return nvg.linearGradient(sx, sy, ex, ey,
62 xcolor(gradient.stops.ptr[0].color, a),
63 xcolor(gradient.stops.ptr[gradient.nstops-1].color, a));
66 NVGPaint createRadialGradient (const(NSVG.Gradient)* gradient, float a) {
67 float cx, cy, r1, r2;
69 NVGMatrix inverse = NVGMatrix(gradient.xform).inverted;
70 inverse.point(&cx, &cy, 0, 0);
71 inverse.point(&r1, &r2, 0, 1);
72 immutable float outr = r2-cy;
73 immutable float inr = (gradient.nstops == 3 ? gradient.stops.ptr[1].offset*outr : 0);
75 if (a < 0) a = 0; else if (a > 1) a = 1;
76 uint aa = cast(uint)(0xff*a)<<24;
77 return nvg.radialGradient(cx, cy, inr, outr,
78 xcolor(gradient.stops.ptr[0].color, a),
79 xcolor(gradient.stops.ptr[gradient.nstops-1].color, a));
82 nvg.save();
83 scope(exit) nvg.restore();
85 switch (pathMode) {
86 case PathMode.Original: break;
87 case PathMode.EvenOdd: nvg.evenOddFill(); break;
88 default: nvg.nonZeroFill(); break;
91 // iterate shapes
92 image.forEachShape((in ref NSVG.Shape shape) {
93 // skip invisible shape
94 if (!shape.visible) return;
96 if (shape.fill.type == NSVG.PaintType.None && shape.stroke.type == NSVG.PaintType.None) return;
97 if (shape.opacity <= 0) return;
99 if (pathMode == PathMode.Original) {
100 //{ import iv.vfs.io; writeln(shape.fillRule); }
101 final switch (shape.fillRule) {
102 case NSVG.FillRule.NonZero: nvg.nonZeroFill(); break;
103 case NSVG.FillRule.EvenOdd: nvg.evenOddFill(); break;
107 // draw paths
108 nvg.beginPath();
109 bool pathHole = false;
110 shape.forEachPath((in ref NSVG.Path path) {
111 if (nativeOnlyBeziers) {
112 // get 2-arg for `moveTo` and 6-arg for `bezierTo`
113 path.asCubics!true(delegate (const(float)[] args) nothrow @trusted @nogc {
114 assert(args.length == 2 || args.length == 6);
115 if (args.length == 2) {
116 nvg.moveTo(args);
117 } else {
118 ++bezierCount;
119 nvg.bezierTo(args);
122 if (path.closed) nvg.lineTo(path.startX, path.startY);
123 } else {
124 path.forEachCommand!true(delegate (NSVG.Command cmd, const(float)[] args) nothrow @trusted @nogc {
125 final switch (cmd) {
126 case NSVG.Command.MoveTo: nvg.moveTo(args); break;
127 case NSVG.Command.LineTo: nvg.lineTo(args); break;
128 case NSVG.Command.QuadTo: nvg.quadTo(args); ++bezierCount; break;
129 case NSVG.Command.BezierTo: nvg.bezierTo(args); ++bezierCount; break;
134 if (pathMode != PathMode.Original) {
135 if (pathMode != PathMode.EvenOdd && pathHole) nvg.pathWinding(NVGSolidity.Hole); else nvg.pathWinding(NVGSolidity.Solid);
136 final switch (pathMode) {
137 case PathMode.Original: break;
138 case PathMode.EvenOdd: break;
139 case PathMode.Flipping: pathHole = !pathHole; break;
140 case PathMode.AllHoles: pathHole = true; break;
141 case PathMode.NoHoles: break;
146 // fill
147 if (nativeFill) {
148 switch (shape.fill.type) {
149 case NSVG.PaintType.Color:
150 nvg.fillColor(xcolor(shape.fill.color, shape.opacity));
151 nvg.fill();
152 break;
153 case NSVG.PaintType.LinearGradient:
154 if (nativeGradients) {
155 nvg.fillPaint(createLinearGradient(shape.fill.gradient, shape.opacity));
156 nvg.fill();
158 break;
159 case NSVG.PaintType.RadialGradient:
160 if (nativeGradients) {
161 nvg.fillPaint(createRadialGradient(shape.fill.gradient, shape.opacity));
162 nvg.fill();
164 break;
165 default:
166 break;
170 // set stroke/line
171 NVGLineCap join;
172 switch (shape.strokeLineJoin) {
173 case NSVG.LineJoin.Round: join = NVGLineCap.Round; break;
174 case NSVG.LineJoin.Bevel: join = NVGLineCap.Bevel; break;
175 case NSVG.LineJoin.Miter: goto default;
176 default: join = NVGLineCap.Miter; break;
178 NVGLineCap cap;
179 switch (shape.strokeLineCap) {
180 case NSVG.LineCap.Butt: cap = NVGLineCap.Butt; break;
181 case NSVG.LineCap.Round: cap = NVGLineCap.Round; break;
182 case NSVG.LineCap.Square: cap = NVGLineCap.Square; break;
183 default: cap = NVGLineCap.Square; break;
186 nvg.lineJoin(join);
187 nvg.lineCap(cap);
188 nvg.strokeWidth(shape.strokeWidth);
190 // draw line
191 if (nativeStroke) {
192 switch (shape.stroke.type) {
193 case NSVG.PaintType.Color:
194 nvg.strokeColor(xcolor(shape.stroke.color, shape.opacity));
195 nvg.stroke();
196 break;
197 case NSVG.PaintType.LinearGradient:
198 if (nativeGradients) {
199 nvg.strokePaint(createLinearGradient(shape.stroke.gradient, shape.opacity));
200 nvg.stroke();
202 break;
203 case NSVG.PaintType.RadialGradient:
204 if (nativeGradients) {
205 nvg.strokePaint(createRadialGradient(shape.stroke.gradient, shape.opacity));
206 nvg.stroke();
208 break;
209 default:
210 break;
217 // ////////////////////////////////////////////////////////////////////////// //
218 __gshared int GWidth = 640;
219 __gshared int GHeight = 480;
220 __gshared bool useDirectRendering = true;
223 // ////////////////////////////////////////////////////////////////////////// //
224 void main (string[] args) {
225 import core.time;
227 NVGContext nvg = null;
229 int minw = 32, minh = 32;
230 int defw = 256, defh = 256;
231 int addw = 0, addh = 0;
232 bool stencilStrokes = true;
233 bool contextAA = true;
234 bool maxSize = false;
235 string fname;
236 float userscale = 1;
237 for (usize idx = 1; idx < args.length; ++idx) {
238 import std.conv : to;
239 string a = args[idx];
240 if (a == "-w" || a == "-h") {
241 char d = a[1];
242 a = a[2..$];
243 if (a.length == 0) {
244 ++idx;
245 if (idx >= args.length) assert(0, "out of args");
246 a = args[idx];
248 if (d == 'w') defw = minw = to!int(a); else defh = minh = to!int(a);
249 } else if (a.length > 2 && a[0] == '0' && (a[1] == 'w' || a[1] == 'h')) {
250 if (a[1] == 'w') defw = minw = to!int(a); else defh = minh = to!int(a);
251 } else if (a == "--scale") {
252 ++idx;
253 if (idx >= args.length) assert(0, "out of args");
254 userscale = to!float(args[idx]);
255 } else if (a == "--width") {
256 ++idx;
257 if (idx >= args.length) assert(0, "out of args");
258 defw = minw = to!int(args[idx]);
259 } else if (a == "--height") {
260 ++idx;
261 if (idx >= args.length) assert(0, "out of args");
262 defh = minh = to!int(args[idx]);
263 } else if (a == "--addw") {
264 ++idx;
265 if (idx >= args.length) assert(0, "out of args");
266 addw = to!int(args[idx]);
267 } else if (a == "--addh") {
268 ++idx;
269 if (idx >= args.length) assert(0, "out of args");
270 addh = to!int(args[idx]);
271 } else if (a == "--nvg" || a == "--native") {
272 useDirectRendering = true;
273 } else if (a == "--svg" || a == "--raster") {
274 useDirectRendering = false;
275 } else if (a == "--stencil") {
276 stencilStrokes = true;
277 } else if (a == "--fast") {
278 stencilStrokes = false;
279 } else if (a == "--aa") {
280 contextAA = true;
281 } else if (a == "--noaa" || a == "--sharp") {
282 contextAA = false;
283 } else if (a == "--max") {
284 maxSize = true;
285 } else if (a == "--normal") {
286 maxSize = false;
287 } else if (a == "--") {
288 ++idx;
289 if (idx >= args.length) assert(0, "out of args");
290 fname = args[idx];
291 break;
292 } else {
293 if (fname !is null) assert(0, "too many filenames");
294 fname = args[idx];
297 if (fname.length == 0) assert(0, "no filename");
299 static if (NanoSVGHasIVVFS) {
300 VFile infile;
302 if (fname.getExtension.strEquCI(".zip")) {
303 auto did = vfsAddPak!("normal", true)(fname, ":::");
304 vfsForEachFileInPak(did, delegate (in ref de) {
305 if (de.name.getExtension.strEquCI(".svg")) {
306 //{ import iv.vfs.io; writeln(de.name); }
307 infile = VFile(de.name);
308 return 1;
310 return 0;
312 } else {
313 infile = VFile(fname);
317 NSVG* svg;
318 scope(exit) svg.kill();
320 import std.stdio : writeln;
321 import core.time, std.datetime;
322 auto stt = MonoTime.currTime;
323 static if (NanoSVGHasIVVFS) {
324 svg = nsvgParseFromFile(infile, "px", 96, defw, defh);
325 } else {
326 svg = nsvgParseFromFile(fname, "px", 96, defw, defh);
328 if (svg is null) assert(0, "svg parsing error");
329 auto dur = (MonoTime.currTime-stt).total!"msecs";
330 writeln("loading took ", dur, " milliseconds (", dur/1000.0, " seconds)");
331 { import std.stdio; writeln(args.length > 1 ? args[1] : "data/svg/tiger.svg"); }
334 printf("size: %f x %f\n", cast(double)svg.width, cast(double)svg.height);
335 GWidth = cast(int)svg.width+addw;
336 GHeight = cast(int)svg.height+addh;
337 float scale = userscale;
339 enum MaxWidth = 1900;
340 enum MaxHeight = 1100;
342 if (GWidth > MaxWidth || GHeight > MaxHeight || maxSize) {
343 float sx = cast(float)(MaxWidth-4)/GWidth;
344 float sy = cast(float)(MaxHeight-4)/GHeight;
345 scale = (GWidth*sx <= MaxWidth && GHeight*sx < MaxHeight ? sx : sy);
348 if (scale != 1) {
349 GWidth = cast(int)(GWidth*scale);
350 GHeight = cast(int)(GHeight*scale);
351 printf("new size: %d x %d\n", GWidth, GHeight);
354 if (GWidth < minw) GWidth = minw;
355 if (GHeight < minh) GHeight = minh;
357 NVGImage vgimg;
359 bool doQuit = false;
360 bool drawFPS = false;
362 //setOpenGLContextVersion(3, 2); // up to GLSL 150
363 setOpenGLContextVersion(2, 0); // it's enough
364 //openGLContextCompatible = false;
366 auto sdwindow = new SimpleWindow(GWidth, GHeight, "NanoSVG", OpenGlOptions.yes, Resizability.fixedSize);
367 //sdwindow.hideCursor();
369 static if (is(typeof(&sdwindow.closeQuery))) {
370 sdwindow.closeQuery = delegate () { doQuit = true; };
373 sdwindow.onClosing = delegate () {
374 vgimg.clear();
375 nvg.kill();
378 void closeWindow () {
379 if (!sdwindow.closed) sdwindow.close();
382 auto stt = MonoTime.currTime;
383 auto prevt = MonoTime.currTime;
384 auto curt = prevt;
385 float dt = 0, secs = 0;
386 //int mxOld = -1, myOld = -1;
387 PathMode pathMode = PathMode.min;
388 bool svgAA = false;
389 bool help = true;
391 sdwindow.visibleForTheFirstTime = delegate () {
392 sdwindow.setAsCurrentOpenGlContext(); // make this window active
393 sdwindow.vsync = false;
395 nvg = nvgCreateContext(
396 (contextAA ? NVGContextFlag.Antialias : NVGContextFlag.None)|
397 (stencilStrokes ? NVGContextFlag.StencilStrokes : NVGContextFlag.None)|
398 NVGContextFlag.None
400 if (nvg is null) assert(0, "Could not init nanovg.");
402 nvg.createFont("sans", "Tahoma");
403 { import iv.vfs.io; writeln("bezier tesselator: ", nvg.tesselation); }
404 sdwindow.redrawOpenGlScene();
407 sdwindow.redrawOpenGlScene = delegate () {
408 // timers
409 prevt = curt;
410 curt = MonoTime.currTime;
411 secs = cast(double)((curt-stt).total!"msecs")/1000.0;
412 dt = cast(double)((curt-prevt).total!"msecs")/1000.0;
414 // Update and render
415 //glClearColor(0, 0, 0, 0);
416 glClearColor(0.3, 0.3, 0.3, 0);
417 glClear(glNVGClearFlags);
419 if (nvg !is null) {
420 nvg.beginFrame(GWidth, GHeight);
421 scope(exit) nvg.endFrame();
423 if (useDirectRendering) {
424 nvg.save();
425 scope(exit) nvg.restore();
426 nvg.shapeAntiAlias = svgAA;
427 //vg.translate(0.5, 0.5);
428 import std.stdio : writeln;
429 import core.time, std.datetime;
430 auto stt = MonoTime.currTime;
431 nvg.translate(addw, addh);
432 nvg.scale(scale, scale);
433 bezierCount = 0;
434 nvg.render(svg, pathMode);
435 auto dur = (MonoTime.currTime-stt).total!"msecs";
436 writeln("*** rendering took ", dur, " milliseconds (", dur/1000.0, " seconds), ", bezierCount, " beziers rendered.");
437 } else {
438 // draw image
439 if (!vgimg.valid) {
440 // image is not rasterized, do it now
441 ubyte[] svgraster;
442 scope(exit) svgraster.destroy;
444 import std.stdio : writeln;
445 auto rst = nsvgCreateRasterizer();
446 scope(exit) rst.kill();
447 svgraster = new ubyte[](GWidth*GHeight*4);
448 import core.time, std.datetime;
449 auto stt = MonoTime.currTime;
450 writeln("rasterizing...");
451 rst.rasterize(svg,
452 addw/2, addh/2, // ofs
453 scale, // scale
454 svgraster.ptr, GWidth, GHeight);
455 auto dur = (MonoTime.currTime-stt).total!"msecs";
456 writeln("rasterizing took ", dur, " milliseconds (", dur/1000.0, " seconds)");
458 vgimg = nvg.createImageRGBA(GWidth, GHeight, svgraster[]);
460 nvg.save();
461 scope(exit) nvg.restore();
462 nvg.beginPath();
463 nvg.rect(0, 0, GWidth, GHeight);
464 nvg.fillPaint(nvg.imagePattern(0, 0, GWidth, GHeight, 0, vgimg, 1));
465 nvg.fill();
468 //vg.endFrame(); // flush rendering
469 //vg.beginFrame(GWidth, GHeight); // restart frame
471 if (help) {
472 nvg.save();
473 scope(exit) nvg.restore();
475 nvg.fontFace = "sans";
476 nvg.fontSize = 14;
477 nvg.textAlign(NVGTextAlign(NVGTextAlign.H.Left, NVGTextAlign.V.Top));
479 auto tw = nvg__max(nvg.textWidth("Direct"), nvg.textWidth("Image"));
480 foreach (string mn; __traits(allMembers, PathMode)) tw = nvg__max(tw, nvg.textWidth(mn));
481 auto th = nvg.textFontHeight;
482 auto rw = tw+10;
483 auto rh = (th+3)*3+10;
485 nvg.translate(nvg.width-rw-2, nvg.height-rh-2);
488 nvg.newPath();
489 nvg.save();
490 scope(exit) nvg.restore();
491 //vg.globalCompositeBlendFunc(NVGBlendFactor.ZERO, NVGBlendFactor.SRC_ALPHA);
492 //vg.scissor(0, 0, tw+1, 71);
493 nvg.rect(0.5, 0.5, rw, rh);
494 nvg.fillColor(NVGColor("#8000"));
495 nvg.fill();
496 //printf("tw=%g\n", cast(double)tw);
499 nvg.fillColor(NVGColor.white);
500 nvg.text(5, 5+(th+3)*0, (useDirectRendering ? "Direct" : "Image"));
501 nvg.text(5, 5+(th+3)*1, (svgAA ? "AA" : "NO AA"));
502 import std.conv : to;
503 nvg.text(5, 5+(th+3)*2, pathMode.to!string);
508 sdwindow.eventLoop(0/*1000/30*/,
509 delegate () {
510 if (sdwindow.closed) return;
511 if (doQuit) { closeWindow(); return; }
512 sdwindow.redrawOpenGlSceneNow();
514 delegate (KeyEvent event) {
515 if (sdwindow.closed) return;
516 if (!event.pressed) return;
517 if (event == "Escape" || event == "C-Q") { sdwindow.close(); return; }
518 if (event == "D" || event == "V") { useDirectRendering = !useDirectRendering; sdwindow.redrawOpenGlSceneNow(); return; }
519 if (event == "A") { svgAA = !svgAA; if (useDirectRendering) sdwindow.redrawOpenGlSceneNow(); return; }
520 if (event == "M") {
521 if (pathMode == PathMode.max) pathMode = PathMode.min; else ++pathMode;
522 if (useDirectRendering) sdwindow.redrawOpenGlSceneNow();
523 return;
525 if (event == "G") { nativeGradients = !nativeGradients; sdwindow.redrawOpenGlSceneNow(); return; }
526 if (event == "F") { nativeFill = !nativeFill; sdwindow.redrawOpenGlSceneNow(); return; }
527 if (event == "S") { nativeStroke = !nativeStroke; sdwindow.redrawOpenGlSceneNow(); return; }
528 if (event == "B") { nativeOnlyBeziers = !nativeOnlyBeziers; sdwindow.redrawOpenGlSceneNow(); return; }
529 if (event == "T") {
530 nvg.tesselation = cast(NVGTesselation)(nvg.tesselation == NVGTesselation.max ? NVGTesselation.min : nvg.tesselation+1);
531 { import iv.vfs.io; writeln("bezier tesselator: ", nvg.tesselation); }
532 sdwindow.redrawOpenGlSceneNow();
533 return;
535 //if (event == "Space") { drawFPS = !drawFPS; return; }
536 if (event == "Space") { help = !help; sdwindow.redrawOpenGlSceneNow(); return; }
538 delegate (MouseEvent event) {
540 delegate (dchar ch) {
541 //if (ch == 'q') { doQuit = true; return; }
544 closeWindow();