2 // Copyright (c) 2013 Mikko Mononen memon@inside.org
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.
20 import core
.stdc
.stdio
;
22 import iv
.nanovega
.svg
;
27 import arsd
.simpledisplay
;
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
;
46 float ca
= (clr
>>24)/255.0f;
48 if (a
<= 0) return NVGColor
.transparent
;
50 uint aa
= cast(uint)(0xff*a
)<<24;
51 return NVGColor
.fromUint((clr
&0xffffff)|aa
);
54 NVGPaint
createLinearGradient (const(NSVG
.Gradient
)* gradient
, float a
) {
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
) {
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
));
83 scope(exit
) nvg
.restore();
86 case PathMode
.Original
: break;
87 case PathMode
.EvenOdd
: nvg
.evenOddFill(); break;
88 default: nvg
.nonZeroFill(); break;
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;
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) {
122 if (path
.closed
) nvg
.lineTo(path
.startX
, path
.startY
);
124 path
.forEachCommand
!true(delegate (NSVG
.Command cmd
, const(float)[] args
) nothrow @trusted @nogc {
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;
148 switch (shape
.fill
.type
) {
149 case NSVG
.PaintType
.Color
:
150 nvg
.fillColor(xcolor(shape
.fill
.color
, shape
.opacity
));
153 case NSVG
.PaintType
.LinearGradient
:
154 if (nativeGradients
) {
155 nvg
.fillPaint(createLinearGradient(shape
.fill
.gradient
, shape
.opacity
));
159 case NSVG
.PaintType
.RadialGradient
:
160 if (nativeGradients
) {
161 nvg
.fillPaint(createRadialGradient(shape
.fill
.gradient
, shape
.opacity
));
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;
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;
188 nvg
.strokeWidth(shape
.strokeWidth
);
192 switch (shape
.stroke
.type
) {
193 case NSVG
.PaintType
.Color
:
194 nvg
.strokeColor(xcolor(shape
.stroke
.color
, shape
.opacity
));
197 case NSVG
.PaintType
.LinearGradient
:
198 if (nativeGradients
) {
199 nvg
.strokePaint(createLinearGradient(shape
.stroke
.gradient
, shape
.opacity
));
203 case NSVG
.PaintType
.RadialGradient
:
204 if (nativeGradients
) {
205 nvg
.strokePaint(createRadialGradient(shape
.stroke
.gradient
, shape
.opacity
));
217 // ////////////////////////////////////////////////////////////////////////// //
218 __gshared
int GWidth
= 640;
219 __gshared
int GHeight
= 480;
220 __gshared
bool useDirectRendering
= true;
223 // ////////////////////////////////////////////////////////////////////////// //
224 void main (string
[] args
) {
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;
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") {
245 if (idx
>= args
.length
) assert(0, "out of args");
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") {
253 if (idx
>= args
.length
) assert(0, "out of args");
254 userscale
= to
!float(args
[idx
]);
255 } else if (a
== "--width") {
257 if (idx
>= args
.length
) assert(0, "out of args");
258 defw
= minw
= to
!int(args
[idx
]);
259 } else if (a
== "--height") {
261 if (idx
>= args
.length
) assert(0, "out of args");
262 defh
= minh
= to
!int(args
[idx
]);
263 } else if (a
== "--addw") {
265 if (idx
>= args
.length
) assert(0, "out of args");
266 addw
= to
!int(args
[idx
]);
267 } else if (a
== "--addh") {
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") {
281 } else if (a
== "--noaa" || a
== "--sharp") {
283 } else if (a
== "--max") {
285 } else if (a
== "--normal") {
287 } else if (a
== "--") {
289 if (idx
>= args
.length
) assert(0, "out of args");
293 if (fname
!is null) assert(0, "too many filenames");
297 if (fname
.length
== 0) assert(0, "no filename");
299 static if (NanoSVGHasIVVFS
) {
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
);
313 infile
= VFile(fname
);
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
);
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
);
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
;
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 () {
378 void closeWindow () {
379 if (!sdwindow
.closed
) sdwindow
.close();
382 auto stt
= MonoTime
.currTime
;
383 auto prevt
= MonoTime
.currTime
;
385 float dt = 0, secs
= 0;
386 //int mxOld = -1, myOld = -1;
387 PathMode pathMode
= PathMode
.min
;
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
)|
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 () {
410 curt
= MonoTime
.currTime
;
411 secs
= cast(double)((curt
-stt
).total
!"msecs")/1000.0;
412 dt = cast(double)((curt
-prevt
).total
!"msecs")/1000.0;
415 //glClearColor(0, 0, 0, 0);
416 glClearColor(0.3, 0.3, 0.3, 0);
417 glClear(glNVGClearFlags
);
420 nvg
.beginFrame(GWidth
, GHeight
);
421 scope(exit
) nvg
.endFrame();
423 if (useDirectRendering
) {
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
);
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.");
440 // image is not rasterized, do it now
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...");
452 addw
/2, addh
/2, // ofs
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
[]);
461 scope(exit
) nvg
.restore();
463 nvg
.rect(0, 0, GWidth
, GHeight
);
464 nvg
.fillPaint(nvg
.imagePattern(0, 0, GWidth
, GHeight
, 0, vgimg
, 1));
468 //vg.endFrame(); // flush rendering
469 //vg.beginFrame(GWidth, GHeight); // restart frame
473 scope(exit
) nvg
.restore();
475 nvg
.fontFace
= "sans";
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
;
483 auto rh
= (th
+3)*3+10;
485 nvg
.translate(nvg
.width
-rw
-2, nvg
.height
-rh
-2);
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"));
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*/,
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; }
521 if (pathMode
== PathMode
.max
) pathMode
= PathMode
.min
; else ++pathMode
;
522 if (useDirectRendering
) sdwindow
.redrawOpenGlSceneNow();
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; }
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();
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; }