Статьи

GraphicsBuilder Tutorial II: Контуры и формы

Вторая часть учебного цикла GraphicsBuilder , на этот раз показаны контуры и недостающие фигуры из предыдущей части.

Вступление

В первой части этой серии были представлены основные и дополнительные фигуры, теперь давайте продолжим с контурами и вернемся к отсутствующим фигурам: текст и морфинг. Вы можете выбрать один из 4 основных вариантов:

  • линия: рисует прямую линию
  • ломаная линия: рисует последовательность соединенных линий, определенных массивами координат x и y.
  • cubicCurve: рисует кубический сегмент параметрической кривой.
  • quadCurve: рисует сегмент квадратичной параметрической кривой.

Все контуры имеют схожие свойства как формы, такие как borderColor, borderWidth и opacity, что делает код легко взаимозаменяемым при необходимости. Прежде чем мы перейдем к надлежащим примерам, вы, возможно, заметили, что на рисунках в первой части показаны некоторые неровности в углах каждой фигуры, потому что мы собираемся отобразить контуры, на которых неровности будут выглядеть уродливее. Хорошо, что Java2D поддерживает сглаживание, чтобы избавиться от этих неприятных неровностей, и поскольку установка параметров сглаживания очень распространена, когда дело доходит до чертежей, GraphicsBuilder имеет удобный ярлык для их установки, просто используйте узел antialias () . На следующих рисунках будет показано, что происходит до и после применения настроек сглаживания к каждому набору линий.

Контуры

Let’s start with the most basic one, straight lines. They require 2 anchor points and nothing more.

[img_assist|nid=899|title=|desc=|link=none|align=middle|width=310|height=60]

rect( x:0, y: 0, width: 310, height: 60, borderColor: 'none', fill: 'white' )
line( x1: 10, y1: 10, x2: 300, y2: 10, borderColor: 'black', borderWidth: 3 )
line( x1: 10, y1: 20, x2: 300, y2: 40, borderColor: 'red', borderWidth: 3 )
antialias on
line( x1: 10, y1: 40, x2: 300, y2: 20, borderColor: 'blue', borderWidth: 3 )
line( x1: 10, y1: 50, x2: 300, y2: 50, borderColor: 'black', borderWidth: 3 )

You will notice that antialias accepts on as a parameter, it is actually an alias for the boolean true, and yes in case you are wondering off is an alias for false, so are yes and no too. These aliases make your code more readable in some situations. The next outline is polyline, which draws a sequence of linear segments connected by each pair of points.

[img_assist|nid=901|title=|desc=|link=none|align=middle|width=200|height=120]

rect( x:0, y: 0, width: 200, height: 120, borderColor: 'none', fill: 'white' )
polyline( points: [30, 100, 80, 100, 70, 10, 50, 10, 50, 30, 60, 30],
borderColor: 'black', borderWidth: 3 )
antialias on
polyline( points: [120, 100, 170, 100, 160, 10, 140, 10, 140, 30, 150, 30],
borderColor: 'darkGreen', borderWidth: 3 )

No surprises here, though you may be wondering what kind of color is ‘none’, well it is actually black with full alpha settings, meaning it is a transparent color. You may also set false, off, no to skip drawing the border. Now to the curve segments, quadCurve perhaps being the easiest to configure as it it requires just 3 anchor points

[img_assist|nid=902|title=|desc=|link=none|align=middle|width=250|height=120]

rect( x:0, y: 0, width: 250, height: 120, borderColor: 'none', fill: 'white' )
quadCurve( x1: 20, y1: 20, ctrlx: 300, ctrly: 60, x2: 20, y2: 100,
borderColor: 'red', borderWidth: 4 )
antialias on
quadCurve( x1: 100, y1: 20, ctrlx: 380, ctrly: 60, x2: 100, y2: 100,
borderColor: 'blue', borderWidth: 4 )

The last outline is cubicCurve, it requires 4 anchor points

[img_assist|nid=903|title=|desc=|link=none|align=middle|width=310|height=160]

rect( x:0, y: 0, width: 310, height: 160, borderColor: 'none', fill: 'white' )
cubicCurve( x1: 10, y1: 10, ctrlx1: 10, ctrly1: 100, ctrlx2: 300, ctrly2: 10,
x2: 300, y2: 100, borderColor: 'blue', borderWidth: 2 )
antialias on
cubicCurve( x1: 10, y1: 50, ctrlx1: 10, ctrly1: 140, ctrlx2: 300, ctrly2: 50,
x2: 300, y2: 150, borderColor: 'blue', borderWidth: 2 )

Granted outlines look pretty dull compared to shapes, but once we visit custom strokes and paints they will look much better.

The Missing Shapes

Even though GraphicsBuilder packs many shapes, more than the basic ones that Java2D provides, not counting the ability to create your own shapes using area operations, there are two other shapes you may use: text and morph. Drawing text requires setting a font (unless you want to use the default settings), fonts require at least one of the following properties: face, size, style. Or you can set any java.awt.Font instance available. The following example shows 4 text shapes, each with a different value for it’s font’s style and size. Notice that the font definition is nested inside the text node, that way it will only be applied to that shape in particular. If for any reason you define a font outside a text node, then all subsequent text nodes will use that font.

[img_assist|nid=904|title=|desc=|link=none|align=middle|width=290|height=170]

def colors = ['red','orange','darkGreen','blue']
def sizes = [24,36,48,60]
def offsets = [48,80,48,60]
def styles = ['plain','bold','italic','bold|italic']
(0..3).each { n ->
text( text: 'GROOVY', x: 10, y: 0 +(sizes[n]*(n+1)/2),
fill: colors[n], borderColor: 'black' ){
font( size: sizes[n], style: styles[n] )
}
}

You can use any combination of the Font.* constants to set the style, or go with the literal option, using a pipe ‘|’ char to separate each token. The second shape comes from the Swingx project, it morphs two shapes into a third one, allowing you to control how much of both shapes contribute to the final one with a morph property; the more the value is close to 0 it will look to the first shape, conversely the more that value is close to 1 it will look like the second shape.

[img_assist|nid=905|title=|desc=|link=none|align=middle|width=330|height=320]

So we have two shapes, a rectangle and start posing as the first and second shapes, then we have a third shape that resembles the rectangle and a fourth one that resembles the star. Let’s see the code

def rect = rect( x: 20, y: 50, width: 100, height: 100, arcWidth: 20, 
arcHeight: 20, asShape: true )
def star = star( cx: 100, cy: 100, ir: 50, or: 80, count: 5, asShape: true )

morph( start: rect, end: star, morph: 0.0, borderWidth: 3,
borderColor: 'orange', fill: 'yellow' ){
transformations { translate(x: 0, y: 0) }
}
morph( start: rect, end: star, morph: 1.0, borderWidth: 3,
borderColor: 'blue', fill: 'cyan' ){
transformations { translate(x: 140, y: 0) }
}
morph( start: rect, end: star, morph: 0.3, borderWidth: 3,
borderColor: 'orange', fill: 'yellow' ){
transformations { translate(x: 20, y: 150) }
}
morph( start: rect, end: star, morph: 0.7, borderWidth: 3,
borderColor: 'blue', fill: 'cyan' ){
transformations { translate(x: 140, y: 150) }
}

 

What is going here? we have clearly 6 shape definitions but only 4 are being drawn. It turns out that shapes (and outlines too) share a property named asShape, when set to true (or on or yes) it will tell GraphicsBuilder not to draw the shape but create the operation only, that way you will be able to reuse the shape or outline definition as many times as you want and in as many places as you need. The code clearly shows that setting the morph value to 0 will draw the first shape unchanged, the same thing happens to the second shape if set to 1. You will notice that all 4 shapes have a transformation/translate node applied, we will look further into transformations at a later installment of this series, but suffices to say that translate moves a shape around to the coordinates you specify.

Clipping

Now that you now shapes can be stored to be later reused we can put them to work. A recurrent question arose in the comments of the last part of this series: do shapes honor the current clip? the answer is yes they do. The default clip on a GraphicsPanel, the component GraphicsPad uses to display your drawings, is set to the panel’s boundaries, but you can set a custom clip if you want, just reuse a shape definition with a clip node.

[img_assist|nid=906|title=|desc=|link=none|align=middle|width=220|height=140] [img_assist|nid=907|title=|desc=|link=none|align=middle|width=220|height=140]
antialias yes
ellipse( cx: 100, cy: 75, radiusx: 90, radiusy: 40, asShape: yes, id: 'c' )
clip( c )
rect( x:10, y:10, width: 200, height: 120, arcWidth: 40,
arcHeight: 20, arcWidth: 20, borderWidth: 2,
borderColor: 'darkGreen', fill: 'green' )
circle( cx:75, cy:75, radius: 50, borderWidth: 2,
borderColor: 'darkRed', fill: 'red' )
star( cx:150, cy:75, or: 50, ir: 30, borderWidth: 2,
borderColor: 'orange', fill: 'yellow' )

Conclusion

Now you now how to draw shapes and outlines, applying antialiasing options to make your drawings smoother and even reusing shapes and setting custom clips. The next part of this series will show you how to control the color options of borders and fills, and the more versatile and subtle paints and gradients.