SwingOSC – Java-based GUI classes

This class is meant as an emulation of Pen. last mod: 16-jan-08 sciss

no-op / not working
String -> *boundscalls a cocoa primitive. there is no replacement in SwingOSC due to the fact that this would need to be implemented asynchronously
different behaviour
strokeswing's stroke is a tiny bit thicker than cocoa's ; with antialiasing off the stroke is about 1px thicker
colorColor.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
  • String.drawAtPoint does NOT work ; instead we use JPen.stringAtPoint to draw a string (similiarily JPen.stringInRect, JPen.stringCenteredIn etc.). JPen.font_ to set font.
  • stringCenteredIn, stringLeftJustIn, stringRightJustIn break lines according to the bounding box instead of actually calling stringAtPoint, which is obviously more logic (otherwise you wouldn't need a rect argument. they are still compatible with cocoa Pen if the rect width is greater than the text bounds width)
  • the string drawing commands do not overwrite the strokeColor setting (bug in cocoa)
extended functionality
curveTo, quadCurveTohave been implemented
known issues / todo
speedespecially for large bodies of drawing functions this can be significantly slower than the cocoa class
string locationthe calculation in stringAtPoint is a bit unclear; adding ascent plus descent results in y-position similiar to cocoa, albeit a bit illogic
font sizefont sizes and line heights (when using stringInRect) are a little bit smaller than in cocoa

 

JPen

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

 

Shape Constructing Drawing Methods

The following methods define paths. You will need to call *stroke or *fill to actually draw them:

*moveTo

JPen.moveTo( <(Point) point> )

Moves the pen to point. Use this method to start a new (unconnected) polyline. See *stroke for an example.

*lineTo

JPen.lineTo( <(Point) point> )

Extends the shape by drawing a line from the current position to a given point. See *stroke for an example.

*line

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 );
    )

 

*curveTo

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 );
    )

 

*quadCurveTo

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

 

*addArc

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 );
    )

 

*addWedge

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 );
    )

 

*addAnnularWedge

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 );
    )

 

*addRect

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 );
    )

 

*stroke

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;
    };
    )

 

*fill

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;
    };
    )          

 

Basic Shape Drawing Methods

The following methods do not require separate stroke or fill calls. They directly paint a primitive geometric form.

*strokeRect

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;
    )

 

*fillRect

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 );
    )

 

*strokeOval

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;
    )

 

*fillOval

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 );
    )

 

String Drawing Methods

These commands draw text. The font is configured by calling JPen.font_( <(JFont) font> ). The text color is taken from JPen.fillColor_( <(Color) c> ).

*stringAtPoint

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;
    )

 

*stringInRect, *stringCenteredIn, *stringLeftJustIn, *stringRightJustIn

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 );
    };
    )

 

Graphics State Methods

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.

*translate

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;
        };
    };
    )

 

*scale

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;
    };
    )

 

*skew

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;
    };
    )

 

*rotate

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 );
    )

 

*matrix_

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]

 

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 ];
    GUI.useID( \swing, { 6.do { arg i;
        EZSlider( 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;
    }});
)

 

*width_

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;
    };
    )

 

*use

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;
    };
    )

 

*beginPath

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;
    };
    )

 

*clip

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;
    };
    )

 

*setSmoothing

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 );
        });
    };
    )

 

Further Examples

(
// 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);
)

 

More Examples

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 ...