This class is meant as an emulation of Pen. last mod: 16-jan-08 sciss
no-op / not working | |
String -> *bounds | calls a cocoa primitive. there is no replacement in SwingOSC due to the fact that this would need to be implemented asynchronously |
different behaviour | |
stroke | swing's stroke is a tiny bit thicker than cocoa's ; with antialiasing off the stroke is about 1px thicker |
color | Color.set does NOT work (it calls a cocoa primitive); instead we use JPen.color_ to set the color (similiarily JPen.strokeColor_ and JPen.fillColor_). |
strings |
|
extended functionality | |
curveTo, quadCurveTo | have been implemented |
known issues / todo | |
speed | especially for large bodies of drawing functions this can be significantly slower than the cocoa class |
string location | the calculation in stringAtPoint is a bit unclear; adding ascent plus descent results in y-position similiar to cocoa, albeit a bit illogic |
font size | font sizes and line heights (when using stringInRect) are a little bit smaller than in cocoa |
Note: please use the abstraction layer GUI.pen if possible! (see GUI)
A helper class to draw on a JSCWindow, a JSCUserView, or a JSCTabletView. It cannot be instantiated but only provides static methods.
The following methods must be called within an JSCWindow-drawHook or a JSCUserView-drawFunc function, and will only be visible once the window or the view is refreshed. Each call to JSCWindow-refresh or JSCUserView-refresh will 'overwrite' all previous drawing by executing the currently defined function.
See also: JSCUserView, JSCWindow, JSCTabletView, Color, JFont, Point, Rect
The following methods define paths. You will need to call *stroke
or *fill
to actually draw them:
JPen.moveTo( <(Point) point> )
Moves the pen to point. Use this method to start a new (unconnected) polyline. See *stroke
for an example.
JPen.lineTo( <(Point) point> )
Extends the shape by drawing a line from the current position to a given point. See *stroke
for an example.
JPen.line( <(Point) p1>, <(Point) p2> )
Adds a line between the points p1
and p2
. The new current position is set to p2. Example:
( w = JSCWindow.new.front; w.view.background_( Color.white ); w.drawHook = { var x1, y1, x2, y2, x1a, y1a, x2a, y2a, txr, tyr, rr; JPen.translate( 200, 200 ); JPen.scale( 0.5, 0.5 ); x1 = 175.0.bilinrand; x2 = 175.0.bilinrand; y1 = 175.0.bilinrand; y2 = 175.0.bilinrand; x1a = 15.0.bilinrand; x2a = 15.0.bilinrand; y1a = 15.0.bilinrand; y2a = 15.0.bilinrand; txr = 2.0.bilinrand; tyr = 2.0.bilinrand; rr = 0.05pi.bilinrand; JPen.moveTo( 175 @ 0 ); 200.do { arg i; JPen.translate( txr, tyr ); JPen.rotate( rr ); JPen.line( x1 @ y1, x2 @ y2 ); x1 = x1 + x1a; x2 = x2 + x2a; y1 = y1 + y1a; y2 = y2 + y2a; }; JPen.stroke; }; ) ( var run = true; w.onClose = { run = false }; // closing window stops animation { while { run } { w.refresh; 0.75.wait }}.fork( AppClock ); )
JPen.curveTo( <(Point) point>, <(Point) cpoint1>, <(Point) cpoint2> )
Adds a cubic-spline curve from the current position to point
. cpoint1
and cpoint2
are help-points determining the curve's curvature. Example:
( w = JSCWindow.new.front; w.view.background_( Color.white ); w.drawHook = { var x1, y1, x2, y2, x1a, y1a, x2a, y2a, txr, tyr, rr; JPen.translate( 200, 200 ); JPen.scale( 0.5, 0.5 ); x1 = 175.0.bilinrand; x2 = 175.0.bilinrand; y1 = 175.0.bilinrand; y2 = 175.0.bilinrand; x1a = 25.0.bilinrand; x2a = 25.0.bilinrand; y1a = 25.0.bilinrand; y2a = 25.0.bilinrand; txr = 4.0.bilinrand; tyr = 4.0.bilinrand; rr = 0.05pi.bilinrand; JPen.moveTo( 175 @ 0 ); 100.do { JPen.translate( txr, tyr ); JPen.rotate( rr ); JPen.curveTo( 175 @ 0, x1 @ y1, x2 @ y2 ); x1 = x1 + x1a; x2 = x2 + x2a; y1 = y1 + y1a; y2 = y2 + y2a; }; JPen.stroke; }; ) ( var run = true; w.onClose = { run = false }; // closing window stops animation { while { run } { w.refresh; 0.75.wait }}.fork( AppClock ); )
JPen.quadCurveTo( <(Point) point>, <(Point) cpoint1> )
Adds a quadratic-spline curve from the current position to point
. cpoint1
is a help-point determining the curve's curvature. Example:
( var ang1a, ang2a, ang1s, ang2s, rad1a, rad2a, trans; ang1a = Array.fill( 4, 0.0 ); ang2a = Array.fill( 4, 0.0 ); ang1s = Array.fill( 4, 0.0 ); ang2s = Array.fill( 4, 0.0 ); rad1a = Array.fill( 4, 0.0 ); rad2a = Array.fill( 4, 0.0 ); trans = [ 225 @ 225, 350 @ 0, 0 @ 350, -350 @ 0 ]; w = JSCWindow.new.front; w.view.background_( Color.white ); d = 0.0; w.drawHook = { var ang1, ang2, rad1, rad2; var di = 1.0 - d; JPen.scale( 0.5, 0.5 ); 4.do({ arg i; JPen.translate( trans[ i ].x, trans[ i ].y ); ang1s[ i ] = ang1s[ i ] * d + (pi.bilinrand * di); ang2s[ i ] = ang2s[ i ] * d + (pi.bilinrand * di); ang1 = ang1s[ i ]; ang2 = ang2s[ i ]; rad1 = 150; rad2 = 75; ang1a[ i ] = ang1a[ i ] * d + ((pi/16).bilinrand * di); ang2a[ i ] = ang2a[ i ] * d + ((pi/16).bilinrand * di); rad1a[ i ] = (rad1a[ i ] * d + (20.0.bilinrand * di)).clip( -20.0, 20.0 ); rad2a[ i ] = (rad2a[ i ] * d + (10.0.bilinrand * di)).clip( -10.0, 10.0 ); 100.do { JPen.moveTo( 0 @ 0 ); JPen.quadCurveTo( (ang1.cos * rad1) @ (ang1.sin * rad1), (ang2.cos * rad2) @ (ang2.sin * rad2) ); ang1 = ang1 + ang1a[ i ]; ang2 = ang2 + ang2a[ i ]; rad1 = (rad1 + rad1a[ i ]).clip( 0, 150 ); rad2 = (rad2 + rad2a[ i ]).clip( 0, 150 ); }; JPen.stroke; }); }; ) ( var run = true; w.onClose = { run = false }; // closing window stops animation d = 0.97; // animation decay { while { run } { w.refresh; 0.1.wait }}.fork( AppClock ); ) d = 0.8; // faster d = 0.99; // really slow
JPen.addArc( <(Point) center>, <(Number) radius>, <(Number) startAngle>, <(Number) arcAngle> )
Adds an arc around the center
, at radius
number of pixels. startAngle
and arcAngle
refer to the starting angle and the extent of the arc, and are in radians [0..2pi]. Example:
( w = JSCWindow.new.front; w.view.background_( Color.white ); w.drawHook = { JPen.translate( 100, 100 ); 10.do { JPen.color = Color.red( rrand( 0.0, 1 ), rrand( 0.0, 0.5 )); JPen.addArc( 100.rand @ 100.rand, rrand( 10, 100 ), 2pi.rand, pi ); JPen.perform([ \stroke, \fill ].choose ); }; }; ) ( var run = true; w.onClose = { run = false }; // closing window stops animation { while { run } { w.refresh; 0.1.wait }}.fork( AppClock ); )
JPen.addWedge( <(Point) center>, <(Number) radius>, <(Number) startAngle>, <(Number) arcAngle> )
Adds a wedge around the center
, at radius
number of pixels. startAngle
and arcAngle
refer to the starting angle and the extent of the arc, and are in radians [0..2pi]. Example:
( w = JSCWindow.new.front; w.view.background_( Color.white ); w.drawHook = { // set the Color JPen.translate( 100, 100 ); 10.do { JPen.color = Color.blue( rrand( 0.0, 1 ), rrand( 0.0, 0.5 )); JPen.addWedge( 100.rand @ 100.rand, rrand( 10, 100 ), 2pi.rand, 2pi.rand ); JPen.perform([ \stroke, \fill ].choose ); }; }; ) ( var run = true; w.onClose = { run = false }; // closing window stops animation { while { run } { w.refresh; 0.1.wait }}.fork( AppClock ); )
JPen.addAnnularWedge( <(Point) center>, <(Number) innerRadius>, <(Number) outerRadius>, <(Number) startAngle>, <(Number) arcAngle> )
Adds an annular wedge around the center
, from innerRadius
to outerRadius
in pixels. startAngle
and arcAngle
refer to the starting angle and the extent of the arc, and are in radians [0..2pi]. Example:
( w = JSCWindow.new.front; w.view.background_( Color.white ); w.drawHook = { JPen.translate( 100, 100 ); 500.do { JPen.color = Color.green( rrand( 0.0, 1 ), rrand( 0.0, 0.5 )); JPen.addAnnularWedge( 100.rand @ 100.rand, rrand( 10, 50 ), rrand( 51, 100 ), 2pi.rand, 2pi.rand ); JPen.perform([ \stroke, \fill ].choose ); }; }; ) ( var run = true; w.onClose = { run = false }; // closing window stops animation { while { run } { w.refresh; 1.0.wait }}.fork( AppClock ); )
JPen.addRect( <(Rect) rect> )
Adds a rectangle to the drawing. Example:
( w = JSCWindow.new.front; w.view.background_( Color.white ); w.drawHook = { var x1, y1, x2, y2, x1a, y1a, x2a, y2a, txr, tyr, x1o, y1o, x2o, y2o, size; JPen.translate( 200, 200 ); JPen.scale( 0.25, 0.25 ); JPen.width = 2.0; x1 = 175.0.bilinrand; x2 = 175.0.bilinrand; y1 = 175.0.bilinrand; y2 = 175.0.bilinrand; x1a = 15.0.rand; x2a = 15.0.rand; y1a = 15.0.rand; y2a = 15.0.rand; txr = -15.0.rand; tyr = -15.0.rand; JPen.moveTo( 175 @ 0 ); 200.do { arg i; JPen.translate( txr, tyr ); x1o = x1; y1o = y1; x2o = x2; y2o = y2; x1 = x1 + x1a; x2 = x2 + x2a; y1 = y1 + y1a; y2 = y2 + y2a; size = max( (x2 - x1).abs, (y2 - y1).abs ); JPen.addRect( Rect( x1, y1, size, size )); }; JPen.stroke; }; ) ( var run = true; w.onClose = { run = false }; // closing window stops animation { while { run } { w.refresh; 0.1.wait }}.fork( AppClock ); )
JPen.stroke
Outline the path previously defined with any of the above commands. The outline is stroked using the current pen-width (see *width
) and current stroke-color. The stroke-color is set using JPen.strokeColor_
or JPen.color_
(the latter sets both stroke- and fill-color). Example:
( w = JSCWindow.new.front; w.view.background_( Color.white ); w.drawHook = { // set the Color JPen.color = Color.red; JPen.moveTo( 200 @ 100 ); JPen.lineTo( 250 @ 200 ); JPen.lineTo( 300 @ 200 ); JPen.lineTo( 200 @ 250 ); JPen.lineTo( 100 @ 200 ); JPen.lineTo( 150 @ 200 ); JPen.lineTo( 200 @ 100 ); JPen.stroke; }; )
JPen.fill
Fills the path previously defined with any of the above commands. The area is filled using the current fill-color. The fill-color is set using JPen.fillColor_
or JPen.color_
(the latter sets both stroke- and fill-color). Example:
( w = JSCWindow.new.front; w.view.background_( Color.white ); w.drawHook = { // set the Color JPen.color = Color.red; JPen.moveTo( 200 @ 100 ); JPen.lineTo( 250 @ 200 ); JPen.lineTo( 300 @ 200 ); JPen.lineTo( 200 @ 250 ); JPen.lineTo( 100 @ 200 ); JPen.lineTo( 150 @ 200 ); JPen.lineTo( 200 @ 100 ); JPen.fill; }; )
The following methods do not require separate stroke
or fill
calls. They directly paint a primitive geometric form.
JPen.strokeRect( <(Rect) rect> )
Strokes the outline of a rectangle. Example:
( w = JSCWindow( "strokeRect", Rect( 128, 64, 360, 360 )); w.drawHook = { var r; r = Rect( 100, 100, 160, 80 ); JPen.color = Color.black.alpha_( 0.8 ); JPen.strokeRect( r ); }; w.front; )
Notice how the coordinates appear to lie between two pixels not in a pixel's center, so the stroke appears blurred. You can avoid this behaviour by shifting the coordinates by 0.5, 0.5:
( w = JSCWindow( "strokeRect", Rect( 128, 64, 360, 360 )); w.drawHook = { var r; JPen.translate( 0.5, 0.5 ); // ! r = Rect( 100, 100, 160, 80 ); JPen.color = Color.black.alpha_( 0.8 ); JPen.strokeRect( r ); }; w.front; )
JPen.fillRect( <(Rect) rect> )
Fills the area of a rectangle. Example:
( w = JSCWindow( "fillRect", resizable: false ); w.view.background = Color.white; w.drawHook = { var r; 200.do({ JPen.color = Color.black.alpha_( 1.0.rand.pow( 6 )); JPen.fillRect( Rect( 200.0.rand, 200.0.rand, 200.0.rand, 200.0.rand )); }); }; w.front; ) ( var run = true; w.onClose = { run = false }; // closing window stops animation { while { run } { w.refresh; 0.2.wait }}.fork( AppClock ); )
JPen.strokeOval( <(Rect) rect> )
Strokes the outline of an ellipse. The ellipse is specified by its bounding (framing) rectangle. Example:
( w = JSCWindow( "strokeOval", Rect( 128, 64, 360, 360 )); w.drawHook = { var h, v, r; v = h = 300.0; r = Rect( 100, 100, 160, 80 ); JPen.width = 10; JPen.color = Color.black.alpha_( 0.8 ); JPen.strokeOval( r ); }; w.front; )
JPen.fillOval( <(Rect) rect> )
Fills the area of an ellipse. The ellipse is specified by its bounding (framing) rectangle. Example:
( w = JSCWindow( "fillOval", resizable: false ); w.view.background = Color.black; w.drawHook = { var r; 200.do({ JPen.color = Color.white.alpha_( 1.0.rand.pow( 6 )); JPen.fillOval( Rect( 200.0.rand, 200.0.rand, 200.0.rand, 200.0.rand )); }); }; w.front; ) ( var run = true; w.onClose = { run = false }; // closing window stops animation { while { run } { w.refresh; 0.2.wait }}.fork( AppClock ); )
These commands draw text. The font is configured by calling JPen.font_( <(JFont) font> )
. The text color is taken from JPen.fillColor_( <(Color) c> )
.
JPen.stringAtPoint( <(String) str>, <(Point) point> )
Draws a text str
string left aligned from a given point
. There are no line breaks, and the text is vertically aligned such that the given y position referes to the top of the text. Example:
( w = JSCWindow( "stringAtPoint", Rect( 128, 64, 360, 360 ), false ); w.drawHook = { var pt; JPen.font = JFont( "Monospaced", 16 ); JPen.strokeColor = Color.red; 12.do({ pt = Point( rrand( 10, 280 ), rrand( 10, 340 )); JPen.stringAtPoint( "Evidence", pt ); JPen.moveTo( pt.translate( -8 @ 0 )); JPen.lineTo( pt.translate( -2 @ 0 )); JPen.moveTo( pt.translate( 0 @ (-8) )); JPen.lineTo( pt.translate( 0 @ (-2) )); JPen.moveTo( pt.translate( 2 @ 0 )); JPen.lineTo( pt.translate( 8 @ 0 )); JPen.moveTo( pt.translate( 0 @ 2 )); JPen.lineTo( pt.translate( 0 @ 8 )); JPen.stroke; }); }; w.front; )
JPen.stringInRect( <(String) str>, <(Rect) rect> ) // left/top aligment JPen.stringCenteredIn( <(String) str>, <(Rect) rect> ) // x-centered/y-centered aligment JPen.stringLeftJustIn( <(String) str>, <(Rect) rect> ) // left/y-centered aligment JPen.stringRightJustIn( <(String) str>, <(Rect) rect> ) // right/y-centered aligment
Draws a text str
string inside a bouding box rect
with the given alignment. Example:
( w = JSCWindow( "String in Rect", Rect( 128, 64, 300, 560 ), false ).front; w.view.background_(Color.white); w.drawHook = { var rect, txt; // umlaute and accents don't work at the moment! txt = "Il arrive que la realite soit trop complexe pour la transmission orale."; JPen.font = JFont( "SansSerif", 14 ); JPen.strokeColor = Color.blue; rect = Rect( 50, 50, 200, 100 ); JPen.stringInRect( txt, rect ); JPen.strokeRect( rect ); rect = rect.moveBy( 0, rect.height + 20 ); JPen.stringLeftJustIn( txt, rect ); JPen.strokeRect( rect ); rect = rect.moveBy( 0, rect.height + 20 ); JPen.stringCenteredIn( txt, rect ); JPen.strokeRect( rect ); rect = rect.moveBy( 0, rect.height + 20 ); JPen.stringRightJustIn( txt, rect ); JPen.strokeRect( rect ); }; )
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.
JPen.translate( <(Number) x>, <(Number) y> )
Translate the coordinate system to have its origin moved by x, y. Note that this is a concatenation with the previous affine transforms, so for example if the coordinate system had previously been rotated by 90 degrees, translating along the x-axis actually moves the origin vertically. Example:
( w = JSCWindow.new.front; w.view.background_( Color.white ); w.drawHook = { // set the Color JPen.color = Color.blue; JPen.translate( 200, 100 ); // 0@0 in current context is now 200@100 regarding the whole view JPen.moveTo( 0 @ 0 ); JPen.lineTo( 50 @ 100 ); JPen.lineTo( 100 @ 100 ); JPen.lineTo( 0 @ 150 ); JPen.lineTo( -100 @ 100 ); JPen.lineTo( -50 @ 100 ); JPen.lineTo( 0 @ 0 ); JPen.stroke; }; ) // cumulative translations ( w = JSCWindow.new.front; w.drawHook = { // set the Color JPen.color = Color.black; // draw 35 lines 35.do { JPen.moveTo( 0 @ 0 ); JPen.lineTo( 50 @ 350 ); // shift 10 to the right every time JPen.translate( 10, 0 ); JPen.stroke; }; }; )
JPen.scale( <(Number) x>, <(Number) y> )
Scales subsequent drawing. x
and y
are scaling factors (i.e. 0.5 is half size or zoom-out, 2 is double size or zoom-in, etc.). Notice that the drawing "pen tip" scales accordingly (see also the *width
section). Example:
( w = JSCWindow.new.front; w.view.background_( Color.white ); w.drawHook = { // set the Color JPen.color = Color.green; JPen.translate( 200, 100 ); JPen.scale( 0.5, 2 ); // you have to set a starting point... JPen.moveTo( 0 @ 0 ); JPen.lineTo( 50 @ 100 ); JPen.lineTo( 100 @ 100 ); JPen.lineTo( 0 @ 150 ); JPen.lineTo( -100 @ 100 ); JPen.lineTo( -50 @ 100 ); JPen.lineTo( 0 @ 0 ); JPen.stroke; }; )
JPen.skew(<(Number) x>, <(Number) y> )
Skews subsequent drawing. A value of zero means no skewing. Negative x
values skew the bottom to the left, negative y
values skew the right to the top. Positive x
values skew the bottom to the right, positive y
values skew the right to the bottom. Example:
( w = JSCWindow.new.front; w.view.background_( Color.white ); w.drawHook = { // set the Color JPen.color = Color.green( 0.5, 0.8 ); JPen.translate( 200, 100 ); JPen.skew( 0.5, 0.2 ); // you have to set a starting point... JPen.moveTo( 0 @ 0 ); JPen.lineTo( 50 @ 100 ); JPen.lineTo( 100 @ 100 ); JPen.lineTo( 0 @ 150 ); JPen.lineTo( -100 @ 100 ); JPen.lineTo( -50 @ 100 ); JPen.lineTo( 0 @ 0 ); JPen.stroke; }; )
JPen.rotate( <(Number) angle>, <(Number) x = 0>, <(Number) y = 0> )
Rotates subsequent drawing around the Point x @ y
by the amount angle
in radians [0..2pi] (in clockwise orientation). Example:
( w = JSCWindow.new.front; w.view.background_( Color.white ); c = 0; w.drawHook = { JPen.translate( 220, 200 ); 10.do({ JPen.translate( 0, 10 ); // set the Color for all "real" drawing JPen.color = Color.hsv( c.fold( 0, 1 ), 1, 1, 0.5 ); // you have to set a starting point... JPen.moveTo( 0 @ 0 ); JPen.lineTo( 50 @ 100 ); JPen.lineTo( 100 @ 100 ); JPen.lineTo( 0 @ 150 ); JPen.lineTo( -100 @ 100 ); JPen.lineTo( -50 @ 100 ); JPen.lineTo( 0 @ 0 ); JPen.fill; JPen.rotate( 0.2pi ); c = c + 0.1; }); c = c - 0.95; // for subsequent animation }; ) ( var run = true; w.onClose = { run = false }; // closing window stops animation { while { run } { w.refresh; 0.05.wait }}.fork( AppClock ); )
JPen.matrix( <(Array) array> )
Transforms (re-sets) the coordinate system, using the coefficients of an affine-transform matrix.
array = [a, b, c, d, x, y]
a
: zoomX (1.0 = no zooming, 0.5 = zoom out, 2.0 = zoom in)b
: shearingY (0.0 = no shearing)c
: shearingXd
: zoomYx
: translateX (0.0 = no translation. positive values = move right, negative values = move left)y
: translateY (0.0 = no translation. positive values = move down, negative values = move up)
Successive calls of *matrix
concatenate with the previous matrix. Thus, if you need to undo the transforms, you should make use of the *use
method (see below). Example:
( var controlWindow, w; var r, a, b, c, d, matrix = [1, 0, 0, 1, 10, 10]; var sliders, spex, name; w = JSCWindow.new.front; w.view.background_(Color.white); // create a controller-window controlWindow = JSCWindow("matrix controls", Rect(400,200,330,160), resizable: false ); controlWindow.front; // determine the rectangle to be drawn r = Rect.fromPoints(a = 0 @ 0, c = 180 @ 180); b = r.leftBottom; d = r.rightTop; // the drawHook w.drawHook = { JPen.color = Color.red; JPen.matrix = matrix; // ! JPen.width = 5; JPen.strokeRect(r); JPen.strokeOval(r); JPen.color = Color.blue; JPen.width = 0.25; JPen.line(a, c); JPen.line(b, d); JPen.stroke; JPen.font = JFont( "Helvetica-Bold", 12 ); JPen.fillColor = Color.black; JPen.stringAtPoint( "A", a - 6 ); JPen.stringAtPoint( "B", b - 6 ); JPen.stringAtPoint( "C", c - (0 @ 6) ); JPen.stringAtPoint( "D", d - (0 @ 6) ); JPen.font = JFont( "Helvetica-Bold", 10 ); JPen.stringInRect( "a matrix test", r.moveBy( 50, 50 )); }; controlWindow.view.decorator = sliders = FlowLayout(controlWindow.view.bounds); spex = [ [ -2.0, 2.0 ].asSpec, [ -2.0, 2.0 ].asSpec, [ -2.0, 2.0 ].asSpec, [ -2.0, 2.0 ].asSpec, [ -200.0, 200.0 ].asSpec, [ -200.0, 200.0 ].asSpec ]; name = #[ zoomX, shearingY, shearingX, zoomY, translateX, translateY ]; 6.do { arg i; JEZSlider( controlWindow, 300 @ 22, name[i], spex[i], { arg ez; var val; val = ez.value; [ i, val.round(10e-4) ].postln; matrix[ i ] = val; w.refresh; // reevaluate drawHook function }, matrix[ i ]); sliders.nextLine; }; )
JPen.width_( <(Number) width = 1> )
Sets the width in pixels of the pen for the successive *stroke
calls. The pen "tip" is additionally affected by affine transforms such as scaling and skewing. Example:
( w = JSCWindow.new.front; w.view.background_( Color.white ); w.drawHook = { // set the Color JPen.color = Color.blue( 0.5, 0.5 ); JPen.translate( 200,100 ); JPen.width = 10; // you have to set a starting point... JPen.moveTo( 0 @ 0 ); JPen.lineTo( 50 @ 100 ); JPen.lineTo( 100 @ 100 ); JPen.lineTo( 0 @ 150 ); JPen.lineTo( -100 @ 100 ); JPen.lineTo( -50 @ 100 ); JPen.lineTo( 0 @ 0 ); JPen.stroke; }; )
JPen.use( <(Function) function> )
Stores the current graphics state, executes a function
, and then revert to the previous graphics state. This allows you to make complex transformations of the graphics state (e.g. affine transforms, colour changes etc.) without having to explicitly revert to get back to 'normal'. Example:
( // modified by an example of Stefan Wittwer w = JSCWindow.new.front; w.view.background_( Color.white ); w.drawHook = { //paint origin JPen.color = Color.gray( 0, 0.5 ); JPen.addArc( 0 @ 0, 20, 0, 2pi ); JPen.fill; JPen.width = 10; JPen.use({ // draw something complex... JPen.width = 0.5; JPen.translate( 100, 100 ); JPen.color = Color.blue; JPen.addArc( 0 @ 0, 10, 0, 2pi ); JPen.fill; 20.do { JPen.moveTo( 0 @ 0 ); JPen.lineTo( 100 @ 0 ); JPen.color = Color.red( 0.8, rrand( 0.7, 1 )); JPen.stroke; JPen.skew( 0, 0.1 ); }; }); // now go on with all params as before // translation, skewing, width, and color modifications do not apply JPen.line( 10 @ 120, 300 @ 120); JPen.stroke; }; )
JPen.beginPath
Discards any previously defined path. Example:
// incomplete arrow ( w = JSCWindow.new.front; w.view.background_( Color.white ); w.drawHook = { // set the Color JPen.color = Color.blue; JPen.translate( 200, 100 ); JPen.moveTo( 0 @ 0 ); JPen.lineTo( 50 @ 100 ); JPen.lineTo( 100 @ 100 ); // forget what we just drew (the pink lines) JPen.beginPath; JPen.moveTo( 100 @ 100 ); JPen.lineTo( 0 @ 150 ); JPen.lineTo( -100 @ 100 ); JPen.lineTo( -50 @ 100 ); JPen.lineTo( 0 @ 0 ); JPen.stroke; }; )
JPen.clip
Uses the previously defined path as a clipping shape, that is drawing successive shapes will clip them so that only the parts inside the clipping shape are visible. Concatenative calls of *clip
intersect with the previous clipping. Thus, if you need to undo clippings, you must make use of the *use
method (see above).. Example:
( w = JSCWindow.new.front; w.view.background_( Color.white ); w.drawHook = { // outline the clipping path JPen.addRect( Rect( 110, 110, 180, 30 )); JPen.addRect( Rect( 110, 145, 180, 30 )); JPen.addRect( Rect( 110, 180, 180, 30 )); JPen.addRect( Rect( 110, 215, 180, 30 )); // now clip JPen.clip; // everything else we draw is now clipped JPen.color = Color.yellow; JPen.fillRect( Rect( 0, 0, 400, 400 )); JPen.color = Color.red; JPen.moveTo( 200 @ 100 ); JPen.lineTo( 250 @ 200 ); JPen.lineTo( 300 @ 200 ); JPen.lineTo( 200 @ 250 ); JPen.lineTo( 100 @ 200 ); JPen.lineTo( 150 @ 200 ); JPen.fill; }; )
JPen.setSmoothing( <(Boolean) flag> )
Turns on/off anti-aliasing. WARNING: non-antialiased string drawing currently looks very different in cocoa and swing (much thicker in swing). Example:
( w = JSCWindow.new.front; w.view.background_( Color.white ); w.drawHook = { JPen.width = 2; 2.do({ arg i; JPen.setSmoothing( i == 1 ); JPen.strokeOval( Rect( 100, 100, 50, 50 )); JPen.moveTo( 100 @ 200 ); JPen.quadCurveTo( 200 @ 300, 100 @ 300 ); JPen.stroke; JPen.translate( 100, 0 ); }); }; )
( // simple rotating and scaling w = JSCWindow( "Pen Rotation and Scaling", Rect( 128, 64, 360, 360 )); w.drawHook = { var h, v; v = h = 300.0; JPen.use({ // use the same rect for everything, just scale and rotate var r = Rect( 0, 0, 200, 80 ); JPen.color = Color.black; // offset all subsequent co-ordinates JPen.translate( 80, 20 ); JPen.fillRect( r ); JPen.color = Color.red; // scale all subsequent co-ordinates JPen.scale( 0.8, 0.8 ); JPen.translate( 8, 10 ); // rotate all subsequent co-ordinates JPen.rotate( 0.1pi ); JPen.fillRect( r ); JPen.color = Color.blue; // lather, rinse, repeat JPen.scale( 0.8, 0.8 ); JPen.rotate( 0.1pi ); JPen.width = 3; JPen.strokeRect( r ); JPen.color = Color.yellow( 1, 0.5 ); JPen.scale( 0.8, 0.8 ); JPen.rotate( 0.1pi ); JPen.translate( 20, -20 ); JPen.fillOval( r ); }); }; w.front; ) // redraw at random interval // different every time ( var w, run = true; w = JSCWindow("my name is... panel", Rect(128, 64, 800, 800)); w.view.background = Color.white; w.onClose = { run = false }; w.front; w.drawHook = { JPen.use { JPen.width = 0.2; 400.do { JPen.beginPath; JPen.moveTo(Point(10.rand * 80 + 40, 10.rand * 80 + 40)); JPen.lineTo(Point(10.rand * 80 + 40, 10.rand * 80 + 40)); JPen.stroke; }; }; }; { while { run } { w.refresh; 1.0.rand.wait } }.fork(AppClock) ) ( var w, run = true; w = JSCWindow("my name is... panel", Rect(128, 64, 800, 500)); w.view.background = Color.white; w.onClose = { run = false }; w.front; w.drawHook = { JPen.use { JPen.width = 2; 80.do { JPen.width = rrand(0,4) + 0.5; JPen.beginPath; JPen.moveTo(Point(800.rand, 500.rand)); JPen.lineTo(Point(800.rand, 500.rand)); JPen.stroke; }; }; }; { while { run } { w.refresh; 1.0.rand.wait } }.fork(AppClock) ) // Animation // Uses random seed to 'store' data // By reseting the seed each time the same random values and shapes are generated for each 'frame' // These can then be subjected to cumulative rotation, etc., by simply incrementing the phase var. ( // By James McCartney (slightly modified) var w, h = 700, v = 700, seed, run = true, phase = 0, bg; w = JSCWindow("wedge", Rect(40, 40, h, v), false); bg = Color.rand(0,0.3); w.onClose = { run = false }; // stop the thread on close w.front; // store an initial seed value for the random generator seed = Date.seed; w.drawHook = { JPen.width = 2; JPen.use { // reset this thread's seed for a moment thisThread.randSeed = Date.seed; // now a slight chance of a new seed or background color if (0.006.coin) { seed = Date.seed }; if (0.02.coin) { bg = Color.rand( 0, 0.3 )}; JPen.fillColor = bg; JPen.fillRect( Rect( 0, 0, h, v )); // preferably do this rather than switching w.view.background // either revert to the stored seed or set the new one thisThread.randSeed = seed; // the random values below will be the same each time if the seed has not changed // only the phase value has advanced JPen.translate(h/2, v/2); // rotate the whole image // negative random values rotate one direction, positive the other JPen.rotate(phase * 1.0.rand2); // scale the rotated y axis in a sine pattern JPen.scale(1, 0.3 * sin(phase * 1.0.rand2 + 2pi.rand) + 0.5 ); // create a random number of annular wedges rrand(6,24).do { JPen.color = Color.rand( 0.0, 1.0 ).alpha_( rrand( 0.1, 0.7 )); JPen.beginPath; JPen.addAnnularWedge(Point(0,0), a = rrand(60,300), a + 50.rand2, 2pi.rand + (phase * 2.0.rand2), 2pi.rand); if (0.5.coin) {JPen.stroke}{JPen.fill}; }; }; }; // fork a thread to update 20 times a second, and advance the phase each time { while { run } { w.refresh; 0.05.wait; phase = phase + 0.01pi;} }.fork(AppClock) ) ( var w, phase = 0, seed = Date.seed, run = true; w = JSCWindow("my name is... panel", Rect(128, 64, 800, 800)); w.view.background = Color.blue(0.4); w.onClose = { run = false }; w.front; w.drawHook = { JPen.use { if (0.02.coin) { seed = Date.seed }; thisThread.randSeed = seed; JPen.color = Color.white; 200.do { var a = 4.rand; var b = 24.rand; var r1 = 230 + (50 * a); var a1 = 2pi / 24 * b + phase; var r2 = 230 + (50 * (a + 1.rand2).fold(0,3)); var a2 = 2pi / 24 * (b + (3.rand2)).wrap(0,23) + phase; JPen.width = 0.2 + 1.0.linrand; JPen.beginPath; JPen.moveTo(Polar(r1, a1).asPoint + Point(400,400)); JPen.lineTo(Polar(r2, a2).asPoint + Point(400,400)); JPen.stroke; }; thisThread.randSeed = Date.seed; 40.do { var a = 4.rand; var b = 24.rand; var r1 = 230 + (50 * a); var a1 = 2pi / 24 * b + phase; var r2 = 230 + (50 * (a + 1.rand2).fold(0,3)); var a2 = 2pi / 24 * (b + (3.rand2)).wrap(0,23) + phase; JPen.width = 0.2 + 1.5.linrand; JPen.beginPath; JPen.moveTo(Polar(r1, a1).asPoint + Point(400,400)); JPen.lineTo(Polar(r2, a2).asPoint + Point(400,400)); JPen.stroke; }; }; }; { while { run } { w.refresh; 0.1.wait; phase = phase + (2pi/(20*24)) } }.fork(AppClock) ) // note: on a fast machine try // ... and replace the loop by 600.do // hmmmmm! ( var w, h = 800, v = 600, seed = Date.seed, phase = 0, zoom = 0.7, zoomf = 1, run = true; w = JSCWindow("affines", Rect(40, 40, h, v), resizable: false ); w.view.background = Color.blue(0.4); w.onClose = { run = false }; w.front; w.drawHook = { thisThread.randSeed = Date.seed; if (0.0125.coin) { seed = Date.seed; phase = 0; zoom = 0.7; zoomf = exprand(1/1.01, 1.01) } { phase = phase + (2pi/80); zoom = zoom * zoomf }; thisThread.randSeed = seed; JPen.use { var p1 = Point(20.rand2 + (h/2), 20.rand2 + (v/2)); var p2 = Point(20.rand2 + (h/2), 20.rand2 + (v/2)); var xscales = { exprand(2** -0.1, 2**0.1) } ! 2; var yscales = { exprand(2** -0.1, 2**0.1) } ! 2; var xlates = { 8.rand2 } ! 2; var ylates = { 8.rand2 } ! 2; var rots = { 2pi.rand + phase } ! 2; var xform; xscales = (xscales ++ (1/xscales)) * 1; yscales = (yscales ++ (1/yscales)) * 1; xlates = xlates ++ xlates.neg; ylates = ylates ++ xlates.neg; rots = rots ++ rots.neg; xform = {|i| [xlates[i], ylates[i], rots[i], xscales[i], yscales[i]] } ! 4; JPen.color = Color.grey( 1, 0.5 ); JPen.width = 8.linrand + 1; JPen.translate(400, 400); JPen.scale(zoom, zoom); JPen.translate(-400, -400); // JJJ OK this is too heavy // 1200.do { } 200.do { var p, rot, xlate, ylate, xscale, yscale; JPen.width = 8.linrand + 1; JPen.beginPath; #rot, xlate, ylate, xscale, yscale = xform.choose; JPen.translate(xlate, ylate); JPen.rotate(rot, h/2, v/2); JPen.scale(xscale, yscale); JPen.moveTo(p1); JPen.lineTo(p2); JPen.stroke; }; }; }; { while { run } { w.refresh; 0.05.wait }}.fork(AppClock) ) // gimmick by nick collins (from some sc-users list posting): ( var linetext, drawletter; var w, h = 800, v = 60, seed = Date.seed, run = true; var time, name, sourcestring; var yellowness, penwidth; //name=[\s,\u,\p,\e,\r,\c,\o,\l,\l,\i,\d,\e,\r]; //sourcestring= "any lower case text"; sourcestring= "welcome to supercollider"; name=Array.fill(sourcestring.size,{arg i; sourcestring[i].asSymbol}); time=0; linetext=('a':[[[0,1],[0.5,0]],[[0.5,0],[1,1]],[[0.25,0.5],[0.75,0.5]]],'b':[[[0,1],[0,0]],[[0,1],[1,1]],[[0,0],[1,0]],[[0,0.5],[0.75,0.5]],[[0.75,0.5],[1,0.75]],[[0.75,0.5],[1,0.25]],[[1,0.75],[1,1]],[[1,0.25],[1,0]]],'c':[[[0,1],[0,0]],[[0,0],[1,0]],[[0,1],[1,1]]],'d':[[[0,1],[0,0]],[[0,0],[0.75,0]],[[0,1],[0.75,1]],[[0.75,1],[1,0.75]],[[0.75,0],[1,0.25]],[[1,0.25],[1,0.75]]],'e':[[[0,0],[0,1]],[[0,0],[1,0]],[[0,1],[1,1]],[[0,0.5],[1,0.5]]],'f':[[[0,0],[0,1]],[[0,0],[1,0]],[[0,0.5],[1,0.5]]],'g':[[[0,1],[0,0]],[[0,0],[1,0]],[[0,1],[1,1]],[[1,1],[1,0.5]],[[0.5,0.5],[1,0.5]]],'h':[[[0,1],[0,0]],[[0,0.5],[1,0.5]],[[1,1],[1,0]]],'i':[[[0,0],[1,0]],[[0.5,0],[0.5,1]],[[0,1],[1,1]]],'j':[[[0,0],[1,0]],[[0.5,0],[0.5,1]],[[0,1],[0.5,1]]],'k':[[[0,1],[0,0]],[[0,0.5],[1,1]],[[0,0.5],[1,0]]],'l':[[[0,1],[0,0]],[[0,1],[1,1]]],'m':[[[0,1],[0,0]],[[0,0],[0.5,0.5]],[[0.5,0.5],[1,0]],[[1,0],[1,1]]],'n':[[[0,1],[0,0]],[[0,0],[1,1]],[[1,1],[1,0]]],'o':[[[0,1],[0,0]],[[0,0],[1,0]],[[0,1],[1,1]],[[1,0],[1,1]]],'p':[[[0,0],[0,1]],[[0,0],[1,0]],[[0,0.5],[1,0.5]],[[1,0],[1,0.5]]],'q':[[[0,0],[0,0.75]],[[0,0],[0.75,0]],[[0,0.75],[0.75,0.75]],[[0.75,0],[0.75,0.75]],[[0.5,0.5],[1,1]]],'r':[[[0,0],[0,1]],[[0,0],[1,0]],[[0,0.5],[1,0.5]],[[1,0],[1,0.5]],[[0,0.5],[1,1]]],'s':[[[0,0],[0,0.5]],[[0,0],[1,0]],[[0,1],[1,1]],[[0,0.5],[1,0.5]],[[1,0.5],[1,1]]],'t':[[[0,0],[1,0]],[[0.5,0],[0.5,1]]],'u':[[[0,1],[0,0]],[[0,1],[1,1]],[[1,0],[1,1]]],'v':[[[0,0],[0.5,1]],[[0.5,1],[1,0]]],'w':[[[0,0],[0.25,1]],[[0.25,1],[0.5,0.5]],[[0.5,0.5],[0.75,1]],[[0.75,1],[1,0]]],'x':[[[0,0],[1,1]],[[0,1],[1,0]]],'y':[[[0,0],[0.5,0.5]],[[0.5,0.5],[1,0]],[[0.5,0.5],[0.5,1]]],'z':[[[0,1],[1,0]],[[0,0],[1,0]],[[0,1],[1,1]]],(" ".asSymbol):[[[0,1],[1,1]],[[0,0.8],[0,1]],[[1,0.8],[1,1]]]); w = JSCWindow("welcome", Rect(40, 500, h, v), resizable: false ); w.view.background = Color.blue(0.5); w.onClose = { run = false }; w.front; drawletter= { arg which, startx, starty, xscale=100, yscale,prop=1.0; var data; yscale= yscale ? xscale; data= linetext[which]; prop=(round((data.size)*prop).asInteger).max(1); prop.do({ arg i; var val=data[i]; JPen.beginPath; JPen.line( Point( startx + ( xscale * val[ 0 ][ 0 ]), starty + (yscale * val[ 0 ][ 1 ])), Point( startx + (xscale * val[ 1 ][ 0 ]), starty + (yscale * val[ 1 ][ 1 ]))); JPen.stroke; }); }; yellowness=rrand(0.7,0.9); penwidth=rrand(2,3); w.drawHook = { JPen.use { var xoscil, xsizoscil,yoscil, todraw, usedtime; JPen.width= penwidth; JPen.color = Color.yellow( yellowness ); usedtime=time.min(1.0); todraw=(round((name.size)*usedtime).asInteger).max(1); todraw.do({ arg j; xoscil= sin(2*pi*time+(j*pi*0.13))*140/(1+(10*time)); yoscil= sin(2*pi*time+(j*pi*0.03))*200/(1+(200*time)); xsizoscil= time*5+5; drawletter.value( name[ j ], 50 + (25 * j) + xoscil, 10 + yoscil, xsizoscil, xsizoscil, usedtime); }); }; }; { while { time < 2.0 } { w.refresh; time=(time+0.025); //%2.0; 0.05.wait; } }.fork; // (AppClock); ) // funcadelics from Swiki (uses addField method from PenExtensions) // not very smooth on slower computers ;-C ( var a, w, i=0, run=true, cfunc, lfunc; a = Array.fill2D(10, 20, { arg i, j; (i & j + i) % j / 10 }); w = JSCWindow("si", Rect(128, 164, 460, 460), resizable: false ); w.view.background = Color.black; cfunc = { |val| Color( 0.2*i.fold(0, 0.4), val*i.fold(0, val.wrap(0, 0.4)), val*i.fold(0, 1.1), 0.5 ) }; lfunc = {|val| 1/ val ** i.fold(0, 1.1) * i.fold(val,2) * 0.5 }; w.drawHook = { i = i + 0.01; JPen.rotate(i, 240, 240); JPen.translate(100,100); JPen.addField(a, Rect(0, 0, 200, 200), \fillOval, cfunc, lfunc) }; w.front; w.onClose = { run = false }; { while { run } { w.refresh; 0.05.wait }}.fork; // (AppClock); )
redFrik drawings: http://swiki.hfbk-hamburg.de:8888/MusicTechnology/833. You'll have to replace dup( 20000 )
with dup( 2000 )
, after all there's way too much OSC traffic and probably Java2D being slower than native cocoa painting ...