Drawing with JyScript
New graphics features
| |
JyScript's graphics commands are essentially the same as those in
PiScript. There are a few novel features, however.
Alpha: Colors in PostScript are opaque. Java, however,
allows colors to have a specified transparency by adding a fourth component
called the alpha value.
Here is a figure drawn without transparency.
|
beginpage()
center()
scale(50)
setlinewidth(2)
newpath()
box(-2, -2, 4, 4)
fill(1,0,0)
stroke()
newpath()
circle(1, 1, 1.5)
fill(0, 0, 1)
stroke()
endpage()
|
|
Now we will give the blue used to paint the circle an alpha value
of 0.6, roughly meaning it is 60% opaque.
|
beginpage()
center()
scale(50)
setlinewidth(2)
newpath()
box(-2, -2, 4, 4)
fill(1,0,0)
stroke()
newpath()
circle(1, 1, 1.5)
fill(0, 0, 1, 0.6)
stroke()
endpage()
|
|
An alpha value of 1 is opaque, while 0 is transparent.
Another way to do this is using the setcomposite
function.
|
beginpage()
center()
scale(50)
setlinewidth(2)
newpath()
box(-2, -2, 4, 4)
fill(1,0,0)
stroke()
gsave()
setlinewidth(20)
setcomposite(0.6) # <-----
newpath()
circle(1, 1, 1.5)
fill(0, 0, 1)
stroke()
grestore()
endpage()
|
|
You will want to think about why the circle's boundary appears as
it does.
tstroke: You may stroke with the linewidth interpreted in
the user coordinate system. This is actually the default behavior in
PostScript.
|
beginpage()
center()
scale(75)
newpath()
for i in range(-2, 3):
moveto(-2, i)
lineto(2, i)
moveto(i, -2)
lineto(i, 2)
stroke(0.6)
gsave()
newpath()
moveto(-2, 0)
lineto(2, 0)
moveto(0, -2)
lineto(0, 2)
setlinewidth(2)
stroke()
grestore()
newpath()
circle([0, 0], 1)
tstroke([0, 0, 1, 0.6]) # <-----
setlinewidth(2)
stroke([1, 0, 0])
endpage()
|
|
Writing image files:
writePNG([300, 300, draw], "tstroke.png", 1.5)
|
The second argument is a factor by which the dimensions of the
panel are scaled. In this way, we may create high-resolution images.
We may also write JPEG files with
writeJPEG([300, 300, draw], "tstroke.jpg", 1.5, 0.8)
|
where the third argument represents a compression factor.
Antialiasing: Since Java is drawing on a computer screen,
our figures necessarily have a low-resolution. Antialiasing is a
technique that helps create the illusion of smoothness by coloring
pixels near the circles with varying shades of gray.
| Without antialiasing |
With antialiasing |
 |
 |
Antialiasing is used by default. You may change this, however,
with antialiasing(False) and
antialiasing(True). Rectangles sometimes appear fuzzy
when antialiasing is on. (The following figures are drawn with
purestroke, which we'll meet in a moment, turned off to
emphasize the effect of antialiasing.)
| Without antialiasing |
With antialiasing |
 |
 |
Purestroke: By default, Java adds 0.5 to pixel coordinates
when stroking lines. This typically makes horizontal and vertical
lines, drawn with a line width of 1, look better.
This usually isn't what we want for mathematical illustrations
since we often carefully compute our coordinates. By default,
JyScript disables this behavior. However, if you are drawing lots of
rectangles, you may wish to restore the Java's default behavior with
purestroke(False).
To illustrate the kind of thing you might see, here is the graph
of a cubic function drawn with and without purestroke.
default: purestroke(True) |
purestroke(False) |
 |
 |
Notice how, without purestroke, the lines look sharper but the
graph looks jagged. We may turn purestroke on and off as we please to
fine tune a figure.
|
beginpage()
center()
scale(75)
purestroke(False)
newpath()
for i in range(-2, 3):
moveto(-2, i)
lineto(2, i)
moveto(i, -2)
lineto(i, 2)
stroke(0.6)
newpath()
moveto(-2, 0)
lineto(2, 0)
moveto(0, -2)
lineto(0, 2)
stroke()
purestroke(True)
newpath()
graph(lambda x: x**3 - x, -2, 2)
setlinewidth(2)
stroke([0,0,1])
endpage()
|
|
You will not typically need to adjust the antialiasing and
purestroke features, but they can help you fine tune figures.
|
Working with multiple panels
| |
With a few changes, we may include several figures into a single
frame.
Let's see how to create this:
First, we need to import JyScript rather than
JyModule.
Also, we will have different drawing functions for each of the
figures. Consequently, our drawing functions and commands must have
an argument to specify which figure to draw on.
def left(p):
p.beginpage()
p.center()
p.scale(50)
p.newpath()
p.box(-1, -1, 2, 2)
p.fill(0, 0, 1)
p.stroke()
p.endpage()
def right(p):
p.beginpage()
p.center()
p.scale(50)
p.newpath()
p.box(-1, -1, 2, 2)
p.fill(1, 0, 0)
p.stroke()
p.endpage()
|
Finally, we open the frame using a list.
openframe([ [ 200, 200, left], [200, 200, right] ])
|
Here's the whole thing:
from JyScript import *
def left(p):
p.beginpage()
p.center()
p.scale(50)
p.newpath()
p.box(-1, -1, 2, 2)
p.fill(0, 0, 1)
p.stroke()
p.endpage()
def right(p):
p.beginpage()
p.center()
p.scale(50)
p.newpath()
p.box(-1, -1, 2, 2)
p.fill(1, 0, 0)
p.stroke()
p.endpage()
openframe([ [ 200, 200, left], [200, 200, right] ])
|
Anynumber of figures may be added in the list.
openframe([ [ 200, 200, left], [200, 200, middle], [200, 200, right] ])
|
Another common way to place multiple figures inside a frame is
with one large figure in the center and smaller ones around the
borders designated by north, south, east and west.
Here we use a dictionary to indicate the placement of the figures.
from JyScript import *
def function(p):
....
def slider(p):
....
openframe( {"center": [300, 300, function],
"south": [300, 40, slider]})
|
or
openframe( {"c": [300, 300, function],
"s": [300, 40, slider]})
|
|
Under the hood
| |
Behind the scenes, there is class, called JyPanel,
that holds individual figures. We may create JyPanels
directly. For example, the last example could have been coded as
top = JyPanel(300, 300, function)
bottom = JyPanel(300, 40, slider)
openframe({'c': top, 's': bottom})
|
The argument in the drawing functions we define is simply the
JyPanel on which we would like to draw.
At times, it may be useful to work directly with a
JyPanel. For instance, if all we want to do is create a
PNG file, we may do this:
from JyScript import *
def draw(p):
p.beginpage()
p.box(50, 50, 100, 100)
p.fill(0, 0, 1)
p.stroke()
p.endpage()
panel = JyPanel(200, 200, draw)
panel.writePNG('pngbox.png', 2)
|
Users with some Java experience may also create more complicated
layouts by working with the JyPanels.
|
Applets
| |
Applets will be converted in Java byte code that may be
distributed in a browser.
To write applets, we need to use the JyScript
module. The applet will also be a class that subclasses
JApplet. The name of the class must agree with the name
of the file.
Therefore, a typical beginning is something like:
from JyScript import *
from javax.swing import *
class BoxApplet(JApplet):
|
Our methods will be instance methods.
def draw(self, p):
p.beginpage()
p.center()
p.scale(50)
p.box(self.pos, -1, 2, 2)
p.fill(0, 0, 1)
p.stroke()
p.endpage()
|
Then we need to create a method to initialize the applet.
def __init__(self):
layoutapplet(200, 200, self.draw, self)
|
Here is the whole file:
from JyScript import *
from javax.swing import *
class BoxApplet(JApplet):
def draw(self, p):
p.beginpage()
p.center()
p.scale(50)
p.box(-1, -1, 2, 2)
p.fill(0, 0, 1)
p.stroke()
p.endpage()
def __init__(self):
layoutapplet(200, 200, self.draw, self)
|
A convenient way to test our applet is to add
and test with jython BoxApplet.py.
To create something we may post on the web, remove the
testapplet line, run
jythonc -j box.jar -c BoxApplet.py
|
and ignore the dire warning. This creates a jar file
box.jar that may be read by an HTML file with the
following tag:
<applet
code=BoxApplet
width=200
height=240
archive=box.jar>
</applet>
Another example
|
|