2 summary:: Draw custom graphics
4 categories:: GUI>Accessories
5 related:: Classes/QPenPrinter
8 A class which allows you to draw custom graphics on a UserView or Window.
10 The following methods must be called within a link::Classes/Window#-drawFunc:: or a link::Classes/UserView#-drawFunc:: function, and will only be visible once the window or the view is refreshed. Each call to link::Classes/Window#-refresh:: or link::Classes/UserView#-refresh:: will 'overwrite' all previous drawing by executing the currently defined function, unless link::Classes/UserView#-clearOnRefresh:: is set to code::false::
15 subsection:: Construct path
16 The following methods define paths. You will need to call link::#*stroke:: or link::#*fill:: to actually draw them.
19 Move the Pen to point.
21 An instance of link::Classes/Point::
24 Draw a line (define a path) from the current position to point.
26 An instance of link::Classes/Point::
29 Draw a line (define a path) from p1 to p2. Current position will be p2.
31 An instance of link::Classes/Point::
33 An instance of link::Classes/Point::
36 Draws a cubic bezier curve from the current position to point.
37 strong::cpoint1:: and strong::cpoint2:: are control points determining the curve's curvature.
39 An instance of link::Classes/Point::
41 An instance of link::Classes/Point::
43 An instance of link::Classes/Point::
46 Draws a quad bezier curve from the current position to point.
47 strong::cpoint1:: is a control point determining the curve's curvature.
49 An instance of link::Classes/Point::
51 An instance of link::Classes/Point::
54 Draws an arc of a circle using a radius and tangent points.
56 The end point of the first tangent line. Its start point is the current position. An instance of link::Classes/Point::
58 The end point of the second tangent line. Its start point is point1. An instance of link::Classes/Point::
60 The radius of the arc.
65 var w = Window("arcTo", Rect(100, 200, 300, 300)).front;
68 Pen.fillColor = Color.blue;
69 Pen.strokeColor = Color.red;
71 Pen.arcTo(200@150, 200@225, r);
72 Pen.arcTo(200@225, 100@225, r);
73 Pen.arcTo(100@225, 100@150, r);
74 Pen.arcTo(100@150, 150@150, r);
82 Draw an arc around the link::Classes/Point:: strong::center::, at strong::radius:: number of pixels. strong::startAngle:: and strong::arcAngle:: refer to the starting angle and the extent of the arc, and are in radians [0..2pi].
88 w.view.background_(Color.white);
90 Pen.translate(100, 100);
93 Pen.color = Color.red(rrand(0.0, 1), rrand(0.0, 0.5));
94 Pen.addArc((100.rand)@(100.rand), rrand(10, 100), 2pi.rand, pi);
95 Pen.perform([\stroke, \fill].choose);
103 Draw a wedge around the link::Classes/Point:: strong::center::, at strong::radius:: number of pixels. strong::startAngle:: and strong::sweepLength:: refer to the starting angle and the extent of the arc, and are in radians [0..2pi].
108 w = Window.new.front;
109 w.view.background_(Color.white);
111 Pen.translate(100, 100);
114 Pen.color = Color.blue(rrand(0.0, 1), rrand(0.0, 0.5));
115 Pen.addWedge((100.rand)@(100.rand), rrand(10, 100), 2pi.rand, 2pi.rand);
116 Pen.perform([\stroke, \fill].choose);
123 method:: addAnnularWedge
124 Draw an annular wedge around the link::Classes/Point:: strong::center::, from strong::innerRadius:: to strong::outerRadius:: in pixels. strong::startAngle:: and strong::sweepLength:: refer to the starting angle and the extent of the arc, and are in radians [0..2pi].
129 w = Window.new.front;
130 w.view.background_(Color.white);
132 Pen.translate(100, 100);
135 Pen.color = Color.green(rrand(0.0, 1), rrand(0.0, 0.5));
137 (100.rand)@(100.rand),
143 Pen.perform([\stroke, \fill].choose);
151 Adds a link::Classes/Rect:: to the drawing.
156 w = Window.new.front;
157 w.view.background_(Color.white);
161 Pen.color = Color.green(rrand(0.0, 1), rrand(0.0, 0.5));
163 Rect(20, 20, (w.bounds.width-40).rand, (w.bounds.height-40).rand)
165 Pen.perform([\stroke, \fill].choose);
173 Adds an Oval shape that fits inside the link::Classes/Rect:: to the current path.
176 subsection:: Draw the path
179 Outline the previous defined path.
184 w = Window.new.front;
185 w.view.background_(Color.white);
188 Pen.strokeColor = Color.red;
205 Fill the previous defined path.
210 w = Window.new.front;
211 w.view.background_(Color.white);
214 Pen.fillColor = Color.red;
231 Draw the previous defined path using any of the following options:
235 ## 1 || fill using even-odd rule
237 ## 3 || fill and stroke the current path
238 ## 4 || fill and stroke using even-odd rule
244 w = Window.new.front;
245 w.view.background_(Color.white);
248 Pen.fillColor = Color.red;
258 Pen.draw(4); // fill and then stroke
265 Fill and stroke the current path. Shortcut to the draw(3) method.
268 subsection:: Construct and draw
269 These methods do not require separate calls to link::#*stroke:: or link::#*fill::.
272 Strokes a link::Classes/Rect:: into the window.
275 Draws a filled link::Classes/Rect:: into the window.
278 Strokes an oval into the window.
281 Draws a filled oval into the window.
283 subsection:: Gradients
285 method:: fillAxialGradient
286 Fills an Axial gradient.
291 w = Window.new.front;
294 Pen.addRect(w.view.bounds.insetBy(30));
295 Pen.fillAxialGradient(w.view.bounds.leftTop, w.view.bounds.rightBottom, Color.rand, Color.rand);
301 method:: fillRadialGradient
302 Fills a Radial gradient.
307 w = Window.new.front;
310 Pen.addOval(w.view.bounds.insetBy(30));
311 Pen.fillRadialGradient(w.view.bounds.center,
312 w.view.bounds.center, 0, w.bounds.width, Color.rand, Color.rand);
318 subsection:: Graphics State Methods
320 The following commands transform the graphics state, i.e. they effect all subsequent drawing commands. These transformations are cumulative, i.e. each command applies to the previous graphics state, not the original one.
323 Translate the coordinate system to have its origin moved by x,y
328 w = Window.new.front;
329 w.view.background_(Color.white);
331 Pen.strokeColor = Color.blue;
332 Pen.translate(200,100); // 0@0 is now 200@100
337 Pen.lineTo(-100@100);
345 Cumulative translations:
348 w = Window.new.front;
349 w.view.background_(Color.clear);
351 Pen.strokeColor = Color.black;
352 35.do { // draw 35 lines
355 Pen.translate(10, 0); // shift 10 to the right every time
364 Scales subsequent drawing. x and y are scaling factors (i.e. 1 is normal, 2 is double size, etc.).
369 w = Window.new.front;
370 w.view.background_(Color.white);
372 Pen.strokeColor = Color.green;
373 Pen.translate(200,100);
375 Pen.moveTo(0@0); // you have to set a starting point...
379 Pen.lineTo(-100@100);
389 Skews subsequent drawing. x and y are skewing factors (i.e. 1 is normal).
394 w = Window.new.front;
395 w.view.background_(Color.white);
397 Pen.fillColor = Color.green(0.5, 0.8);
398 Pen.translate(200,100);
400 Pen.moveTo(0@0); // you have to set a starting point...
404 Pen.lineTo(-100@100);
414 Rotates subsequent drawing around the link::Classes/Point:: code::x@y:: by the amount strong::angle:: in radians [0..2pi].
419 w = Window.new.front;
420 w.view.background_(Color.white);
423 Pen.translate(220, 200);
426 Pen.fillColor = Color.hsv(c.fold(0, 1), 1, 1, 0.5);
427 Pen.moveTo(0@0); // you have to set a starting point...
431 Pen.lineTo(-100@100);
444 Set transformation matrix to transform coordinate system.
445 See link::#matrix_example:: for an example.
446 argument:: matrixArray
447 A matrix array in the form
449 [ zoomX, shearingY, shearingX, zoomY, translateX, translateY ]
453 Sets the width of the Pen for the whole stroke
456 Draw function, and then revert to the previous graphics state. This allows you to make complex transformations of the graphics state without having to explicitly revert to get back to 'normal'.
461 // modified by an example of Stefan Wittwer
462 w = Window.new.front;
463 w.view.background_(Color.white);
466 Pen.fillColor = Color.gray(0, 0.5);
467 Pen.addArc(0@0, 20, 0, 2pi);
471 Pen.use { // draw something complex...
473 Pen.translate(100,100);
474 Pen.fillColor = Color.blue;
475 Pen.addArc(0@0, 10, 0, 2pi);
480 Pen.strokeColor = Color.red(0.8, rrand(0.7, 1));
486 // now go on with all params as before
487 // translation, skewing, width, and color modifications do not apply
488 Pen.line(10@120, 300@120);
496 Make a path, consisting of the drawing made in function.
498 Unfortunately not working for now...
499 (there's no Pen.endPath which currently is used in this method)
503 Discard any previous path.
505 method:: beginTransparencyLayer
506 Begins a new transparency layer. Transparency layers are useful when you want to apply an effect to a group of objects or create a composite graphic. See link::#transparency_layer_example:: for an example.
508 method:: endTransparencyLayer
509 Ends the current transparency layer.
512 Use the previously defined path as a clipping path.
517 w = Window.new.front;
518 w.view.background_(Color.white);
520 // outline the clipping path
529 // everything else we draw is now clipped
530 Pen.fillColor = Color.yellow;
531 Pen.fillRect(Rect(0,0,400,400));
532 Pen.fillColor = Color.red;
548 Turns on/off anti-aliasing.
553 var w = Window("smoothing", Rect(100, 200, 500, 300)).front;
554 w.view.background_(Color.white);
556 Pen.strokeColor = Color.grey(0.25);
557 Pen.smoothing_(false); //no anti-aliasing
559 Pen.moveTo(50@50.rrand(250));
560 Pen.lineTo(250@50.rrand(250));
563 Pen.smoothing_(true); //anti-aliasing (default)
565 Pen.moveTo(250@50.rrand(250));
566 Pen.lineTo(450@50.rrand(250));
574 Will fill the current path with a shadow.
575 You should use this option between Pen.push / Pen.pop (or Pen.use)
578 Set the lines joining style according to the defined options:
586 Set the lines joining style according to the defined options:
596 w = Window.new.front;
597 w.view.background_(Color.white);
598 StaticText(w, Rect(0,0,180,20))
599 .string_(" Change Line Cap & Join Styles: ");
600 y = PopUpMenu(w, Rect(180,0,130,20))
601 .items_(["Butt - Miter", "Round - Round", "Square - Bevel"])
602 .action_({w.refresh});
604 Pen.strokeColor = Color.red;
606 Pen.capStyle_(y.value);
607 Pen.joinStyle_(y.value);
622 Set the opacity level.
625 Set the blending mode to use.
626 See link::#blending_modes:: for more information.
629 Set the line dash pattern.
630 pattern should be a link::Classes/FloatArray:: of values that specify the lengths of the painted segments and not painted segments.
633 Simple rotating and scaling:
636 w = Window("Pen Rotation and Scaling", Rect(128, 64, 360, 360));
639 // use the same rect for everything, just scale and rotate
640 var r = Rect(0,0,200,80);
641 Pen.fillColor = Color.black;
642 // offset all subsequent co-ordinates
643 Pen.translate(80,20);
645 Pen.fillColor = Color.red;
646 // scale all subsequent co-ordinates
649 // rotate all subsequent co-ordinates
652 Pen.strokeColor = Color.blue;
653 // lather, rinse, repeat
658 Pen.fillColor = Color.yellow(1,0.5);
661 Pen.translate(20,-20);
669 Redraw at random interval, different every time:
673 w = Window("my name is... panel", Rect(128, 64, 800, 800));
674 w.view.background = Color.white;
675 w.onClose = { run = false; };
682 Pen.moveTo(Point(10.rand * 80 + 40, 10.rand * 80 + 40));
683 Pen.lineTo(Point(10.rand * 80 + 40, 10.rand * 80 + 40));
689 { while { run } { w.refresh; 1.0.rand.wait } }.fork(AppClock)
696 w = Window("my name is... panel", Rect(128, 64, 800, 500));
697 w.view.background = Color.white;
698 w.onClose = { run = false; };
704 Pen.width = rrand(0,4) + 0.5;
706 Pen.moveTo(Point(800.rand, 500.rand));
707 Pen.lineTo(Point(800.rand, 500.rand));
713 { while { run } { w.refresh; 1.0.rand.wait } }.fork(AppClock)
718 subsection:: Animation
720 Uses random seed to 'store' data
721 By reseting the seed each time the same random values and shapes are generated for each 'frame'
722 These can then be subjected to cumulative rotation, etc., by simply incrementing the phase var.
725 // By James McCartney
726 var w, h = 700, v = 700, seed, run = true, phase = 0;
727 w = Window("wedge", Rect(40, 40, h, v), false);
728 w.view.background = Color.rand(0,0.3);
729 w.onClose = { run = false }; // stop the thread on close
731 // store an initial seed value for the random generator
736 // reset this thread's seed for a moment
737 thisThread.randSeed = Date.seed;
738 // now a slight chance of a new seed or background color
739 if (0.006.coin) { seed = Date.seed; };
740 if (0.02.coin) { w.view.background = Color.rand(0,0.3); };
741 // either revert to the stored seed or set the new one
742 thisThread.randSeed = seed;
743 // the random values below will be the same each time if the seed has not changed
744 // only the phase value has advanced
745 Pen.translate(h/2, v/2);
746 // rotate the whole image
747 // negative random values rotate one direction, positive the other
748 Pen.rotate(phase * 1.0.rand2);
749 // scale the rotated y axis in a sine pattern
750 Pen.scale(1, 0.3 * sin(phase * 1.0.rand2 + 2pi.rand) + 0.5 );
751 // create a random number of annular wedges
753 Pen.color = Color.rand(0.0,1.0).alpha_(rrand(0.1,0.7));
755 Pen.addAnnularWedge(Point(0,0), a = rrand(60,300), a + 50.rand2, 2pi.rand
756 + (phase * 2.0.rand2), 2pi.rand);
757 if (0.5.coin) {Pen.stroke}{Pen.fill};
762 // fork a thread to update 20 times a second, and advance the phase each time
763 { while { run } { w.refresh; 0.05.wait; phase = phase + 0.01pi;} }.fork(AppClock)
769 var w, phase = 0, seed = Date.seed, run = true;
770 w = Window("my name is... panel", Rect(128, 64, 800, 800));
771 w.view.background = Color.blue(0.4);
772 w.onClose = { run = false; };
776 if (0.02.coin) { seed = Date.seed; };
777 thisThread.randSeed = seed;
778 Pen.strokeColor = Color.white;
782 var r1 = 230 + (50 * a);
783 var a1 = 2pi / 24 * b + phase;
784 var r2 = 230 + (50 * (a + 1.rand2).fold(0,3));
785 var a2 = 2pi / 24 * (b + (3.rand2)).wrap(0,23) + phase;
786 Pen.width = 0.2 + 1.0.linrand;
788 Pen.moveTo(Polar(r1, a1).asPoint + Point(400,400));
789 Pen.lineTo(Polar(r2, a2).asPoint + Point(400,400));
792 thisThread.randSeed = Date.seed;
796 var r1 = 230 + (50 * a);
797 var a1 = 2pi / 24 * b + phase;
798 var r2 = 230 + (50 * (a + 1.rand2).fold(0,3));
799 var a2 = 2pi / 24 * (b + (3.rand2)).wrap(0,23) + phase;
800 Pen.width = 0.2 + 1.5.linrand;
802 Pen.moveTo(Polar(r1, a1).asPoint + Point(400,400));
803 Pen.lineTo(Polar(r2, a2).asPoint + Point(400,400));
809 { while { run } { w.refresh; 0.1.wait; phase = phase + (2pi/(20*24)) } }.fork(AppClock)
816 var w, h = 800, v = 600, seed = Date.seed, phase = 0, zoom = 0.7, zoomf = 1, run = true;
817 w = Window("affines", Rect(40, 40, h, v));
818 w.view.background = Color.blue(0.4);
819 w.onClose = { run = false };
822 thisThread.randSeed = Date.seed;
823 if (0.0125.coin) { seed = Date.seed; phase = 0; zoom = 0.7; zoomf = exprand(1/1.01, 1.01); }
824 { phase = phase + (2pi/80); zoom = zoom * zoomf; };
825 thisThread.randSeed = seed;
827 var p1 = Point(20.rand2 + (h/2), 20.rand2 + (v/2));
828 var p2 = Point(20.rand2 + (h/2), 20.rand2 + (v/2));
829 var xscales = { exprand(2** -0.1, 2**0.1) } ! 2;
830 var yscales = { exprand(2** -0.1, 2**0.1) } ! 2;
831 var xlates = { 8.rand2 } ! 2;
832 var ylates = { 8.rand2 } ! 2;
833 var rots = { 2pi.rand + phase } ! 2;
835 xscales = (xscales ++ (1/xscales)) * 1;
836 yscales = (yscales ++ (1/yscales)) * 1;
837 xlates = xlates ++ xlates.neg;
838 ylates = ylates ++ xlates.neg;
839 rots = rots ++ rots.neg;
840 xform = {|i| [xlates[i], ylates[i], rots[i], xscales[i], yscales[i]] } ! 4;
841 Pen.strokeColor = Color.grey(1,0.5);
842 Pen.width = 8.linrand + 1;
843 Pen.translate(400, 400);
844 Pen.scale(zoom, zoom);
845 Pen.translate(-400, -400);
847 var p, rot, xlate, ylate, xscale, yscale;
848 Pen.width = 8.linrand + 1;
850 #rot, xlate, ylate, xscale, yscale = xform.choose;
851 Pen.translate(xlate, ylate);
852 Pen.rotate(rot, h/2, v/2);
853 Pen.scale(xscale, yscale);
861 { while { run } { w.refresh; 0.05.wait; } }.fork(AppClock)
866 subsection:: Matrix example
869 var controlWindow, w;
870 var r, a, b, c, d, matrix = [1, 0, 0, 1, 10, 10];
871 var sliders, spex, name;
873 w = Window.new.front;
874 w.view.background_(Color.white);
876 // create a controller-window
877 controlWindow = Window("matrix controls", Rect(400,200,350,120));
880 // determine the rectangle to be drawn
881 r = Rect.fromPoints(a = 0 @ 0, c = 180 @ 180);
887 Pen.strokeColor = Color.red;
892 Pen.color = Color.blue;
898 Pen.font = Font( "Helvetica-Bold", 12 );
899 Pen.stringAtPoint( "A", a - 6 );
900 Pen.stringAtPoint( "B", b - 6 );
901 Pen.stringAtPoint( "C", c - (0@6) );
902 Pen.stringAtPoint( "D", d - (0@6) );
904 Pen.font = Font( "Helvetica", 10 );
905 Pen.stringInRect( "a matrix test", r.moveBy( 50, 50 ));
908 controlWindow.view.decorator = sliders = FlowLayout(controlWindow.view.bounds);
917 name = #[zoomX, shearingY, shearingX, zoomY, translateX, translateY];
919 EZSlider(controlWindow, 300 @ 14, name[i], spex[i], { |ez| var val;
921 [i, val.round(10e-4)].postln;
923 w.refresh; // reevaluate drawFunc function
931 subsection:: Transparency layer example
934 w = Window.new("Transparency Layer test", Rect(400,400,430,450)).front;
936 Color.blue.setStroke;
939 Pen.setShadow(2@2, 10, Color.black);
940 Pen.beginTransparencyLayer;
943 Pen.addOval(Rect(20,40,100,100));
947 Pen.addOval(Rect(30,70,100,100));
951 Pen.addOval(Rect(60,40,100,100));
954 "With Transparency Layer".drawCenteredIn(Rect(30, 40, 100, 100), Font.default, Color.white);
955 Pen.endTransparencyLayer;
959 Pen.translate(200, 0);
960 Pen.setShadow(2@2, 10, Color.black);
964 Pen.addOval(Rect(20,40,100,100));
968 Pen.addOval(Rect(30,70,100,100));
972 Pen.addOval(Rect(60,40,100,100));
975 "Without Transparency Layer".drawCenteredIn(Rect(30, 40, 100, 100), Font.default, Color.white);
979 Pen.translate(0, 200);
980 Pen.setShadow(2@2, 10, Color.black);
981 Pen.beginTransparencyLayer;
984 Pen.addOval(Rect(20,40,170,170));
985 Pen.fillAxialGradient(w.view.bounds.leftTop, w.view.bounds.rightBottom, Color.rand, Color.rand);
988 "With Transparency Layer".drawCenteredIn(Rect(20,40,170,170), Font.default, Color.white);
990 Pen.endTransparencyLayer;
994 Pen.translate(200, 200);
995 Pen.setShadow(2@2, 10, Color.black);
996 Pen.addOval(Rect(20,40,170,170));
997 Pen.fillAxialGradient(w.view.bounds.leftTop, w.view.bounds.rightBottom, Color.rand, Color.rand);
999 "Without Transparency Layer".drawCenteredIn(Rect(20,40,170,170), Font.default, Color.white);
1006 subsection:: Blending modes
1009 different blend modes:
1010 OS X 10.4 and > Only
1011 --------------------
1030 --------------------
1036 21 - DestinationOver
1038 23 - DestinationATop
1045 var blendMode=0, blendString="Normal";
1046 w = Window.new.front;
1047 m = SCPopUpMenu(w, Rect(10, w.view.bounds.height - 30, 150, 20));
1068 blendMode = view.value;
1069 blendString = view.items.at(blendMode);
1075 Pen.blendMode_(blendMode);
1076 Pen.color = Color.green(0.6, 0.10);
1078 Rect(20, 20, 20 + (i*4), 20 + (i*4));