|Line 17:||Line 17:|
[//1cyjknyddcx62agyb002-web-assets/ddg_mgraphics1/1101BasicStrokes.zip Download the patch]
Revision as of 21:54, 5 December 2012
These postings originally appeared on the Cycling '74 forum as JSUI-MGraphics patch-a-day.
I'm going to produce and post one new JSUI patch that uses the new MGraphics system every day for a month. I'm currently working on MGraphics documentation, and thought that people would like a jump-start on this as part of learning this new drawing system.
The MGraphics system is based on the same coding engine used for C-based coding, so it is high-performance and ultra-capable. I hope that you enjoy these patches.
As my colleague Andrew Pask would say: "Happy Patching!"
Darwin D. Grosse (firstname.lastname@example.org)
BasicStrokes shows how to set up the Mgraphics system and do basic line drawing - called paths drawn with strokes. This is about the simplest useful patch I could come up with, but it hopefully helps you see how easy it is to create a basic UI.
BezStrokes is a bit different: it uses the Bezier curve drawing function to create a simple loop from the top, toward the bottom, then back up to the top. I also use "relative coordinate" mode, and have to calculate the aspect ratio in order to fill the display area. Relative coordinates can be easier to work with for arbitrary object sizes, but you have somewhat less control over the actual draw positions.
This is a simple example of drawing circles within the JSUI space, but uses relative coordinate mode so that it automatically scales properly. It also determines the aspect ratio in the paint() routine so that it will scale properly in both patching and presentation mode.
This isn't "art", but it is something that is a great foundation for shape drawing work.
This shows some basic uses of the pattern generation functions within Mgraphics. The creation of the pattern (pattern_create_linear and pattern_create_radial) builds a gradient for use during shape fills. In these originating calls, you define the influence points - the points on a plane that describe the location of gradient startup. If these points are close together, the edge between segments will be tightly defined; if the points are farther apart, the edge will be more of a gradient.
Once you define the two points, you have to give each a color (using the add_color_stop_rgba function, which needs an index - either 0 or 1 - followed by an rgba color set) and tell the next drawing routine to use this pattern for drawing. The next time you do a fill() or stroke() call, the routine will use the pattern rather than a single color.
So, in this case, I create a black background (using a rectangle), build up a color, then have a ball displayed/drawn using a background color. You'll notice that the moving ball shows an unchanging background, since the pattern is defined by absolute location rather than relative (to the object) location. Thus, we perform the sleight-of-hand that makes it look like we have a "porthole" into the background, when in fact we are simply drawing the shape using a pattern.
I wanted to do something that showed management of line width. I started playing with something, and it ended up like this. I'm not sure that it is that useful for learning line width handling, but it does make a cool little op-art display.
I wanted to do something with the mouse, with timers and in non-relative mode. So this is what you get: a cool little drawer that does some cool animation on the tail of the draw. Later this week I'll be doing a patch with a little easier means for persistent drawing, but for the moment you can have a little fun with this.
One fun part of this is to see what happens when Mgraphics tries to draw really short, but really wide, lines. You get funky little stars because minute (1- or 2-pixel) lengths cause significant changes in the drawing function.
Time to write some text. Since I don't have anything particular to say for myself, I'll let the fonts speak for themselves.
Doing a lot of text printing can be a bit of a processing hog. You can see this by increasing the number of fonts to some Really Large Number. Or change the metro to run really fast. These drawn text lines will start to drag on the system...
But this is a cool introduction to text drawing, and how you can be a little creative with whatever you have on your system.
Fun with fonts, again - this time with a little rotation. Can you really make a flower that says "I am a flower"? Easy enough with Max.
Sometimes, when you are working with relative coordinates, you have to do a little monkey work in order to deal with transformations (translation, rotation and scaling). In this case, we draw our spiral at the top-left, then let the translation functions to their thing.
If nothing else, it helps us communicate with Dr. Mesmer.
Up to now, all of our drawing has occurred in the paint() method, which is called every time that the component needs to refresh itself. This is fine in many cases, but troubling in some cases:
- If the paint method contains some generative function, you cannot know when the visual is going to be recalculated.
- If your drawing function does *a lot* of stuff, you can find performance starting to suffer.
So what to do? This sketch shows how to draw to an alternative MGraphics instance, save that visual as an Image object, then use that image to redraw the component during a refresh. The performance is very high (in this case, drawing 1000 semi-transparent rectangles) because the drawing is only done when you ask the drawing to be redone.
Very important stuff here. Thanks to JKC for the help with this!
When you create a surface for drawing (using the separate MGraphics instance we saw last time), you don't have to make an opaque surface - it can be alpha channel reduced, allowing us to make semi-transparent figures that can be reused. In this case, I make a Big Red Square, scale the user space when drawing it, then draw a blue circle over the top. Everything is half-alpha'd, so you get a little see-thru no matter what happens.
When you look at the code, you'll see what's happening - sorta. Some of the actions of the "stamp" are not obvious when you see the code. We are going to be exploring this over the next few days.
In the meantime, enjoy!
In this patch, we are going to investigate what happens when we use the translate function to change the drawing location within Mgraphics. You select different menu options to see what happens when you shift the location around. The last menu entry uses the translate function to draw the oscillator in the middle of the box.
When you use the translate function, you are moving user space around. After drawing/rendering is complete, the drawing is clipped to the display area. So, translate calls in a positive direction move the origin (0,0) to the right and down, while translate calls in a negative direction move the origin up and to the left.
Working with translate() provides a lot of functionality in placing and altering our draw routines.
This is an investigation of the scale function. It actually acts the way that you'd expect, but the ability to change both X and Y scaling separately gives you some interesting ways to warp your display. In this example, I use the same box and image, but I also included a line of text so you can see how text paths react to the effort.
Just like the translate and scale functions, we also need to play with the rotate function. However, unlike the previous investigations, this one shows a bit of funky action. Select the last menu item and adjust the number box. The rectangle and the text rotate cleanly, but the image has some issues...
Therein lies a limitation of the rotation function - image drawing doesn't really survive the rotation process like you'd hope...
Taking a break from the function exploration, I made a little patch that uses the rel_line_to function to build a bit of piping. There's a bit of extra logic to make sure that we aren't going out of bounds, using the get_current_point function.
One of the problems with the previous example is that we only get one line width for all segments, and only one color (which we didn't even bother to set). You could change these in the main loop, but you'd be disappointed; since the stroke() is not called until after the loop has drawn all the segments, only the last call will be used for the stroke() function.
In this updated version, we set the colors and locations (and call stroke) at each iteration. However, we also need to save the last position just before the stroke call. Once stroke is called, the path is lost - and therefore, the current location is 0,0. So we find the current location just before the stroke(), use that for the next iteration, and are rewarded with the ability to change the line width and color for each segment.
Back to playing with transformative function - in this case the most arbitrary of all: the transform() function. The transform function takes six arguments, for which I've given the labels xx, xy, yx, yy, x0 and y0. (Note: These are taken from deep within the Max Mgraphics code, but you'll see how they relate to the real world in a second...)
All of the rotate(), translate() and scale() functions can be built into a single transformation using these size values. The xx and yy values control the x and y scaling. The xy and yx values are the basis of the rotate() function. The x0 and y0 values are offsets as provided by the translate() function. But you can see that presenting this information in a matrix format gives you the ability to play with arbitrary scaling and movement options that would be difficult to do with the normal functions.
It is also important to note the effect of a transform matrix on the lines that are drawn. If you change the line width to 10 or more inside the code, then do more extensive transforms, the line size is directly affected.
A great place to learn more about the transform function is to look at the Cairo tutorial about transforms, found at http://cairographics.org/tutorial/#L1transforms
Hopefully, this little tester can help you get on top of the transform function and the transformation matrix. Enjoy!
What's the use of knowing about the transform() function without actually having any fun with it? This patch uses the XY and YX segments of the transform to give a seasick, psuedo-3D movement to the drawn rectangles. Pretty basic, but also pretty interesting.
I make a special note of drawing outside the visible area, and letting the transform expose part of the drawn space that you otherwise wouldn't see. If you are doing crazy animations, remember to draw out stuff that may not initially be visible, but will show up after transformation, translation or rotation.
So digging into cairographics site, I ran across some information about dealing with the distortion of a warped transform matrix. In this example, I use the save() and restore() functions to reset the transformation before the stroke() call is made, thereby making the lines work better.
With the toggle box in the patch unchecked, it works identically to yesterday's patch. If you turn on the toggle, though, the drawing routine is changed so that stroke() is not called until we restore the transform to its normal state.
The save() and restore() functions are perfect for this sort of application: a situation where you alter the environment considerably, but don't want to pay a penalty in graphic output quality. I hope this makes sense, but the (somewhat subtle) visible end result should speak for itself.
This is an example of saving a path and reusing it for alternate versions of the path. The "red" part of the display is what I originally draw, while the black (and warped) images are based on reusing the saved path with a variety of rotations, translations and scalings.
I just also happen to really like this display...
By request, here is an example of using images with acceptable rotation. In order to do it, you have to actually create an image background for a surface, then use that as a rectangle fill. A little convoluted, but it allows you to use images in a warped environment. Handy stuff...
Continuing the exploration of images, in this one we use the set_source_rgba to determine the alpha (transparancy) that will be used for drawing the image to the display area. This allows use to custom fade the output. Again, notice that we also have to translate the location (rather than just move_to()) in order to get the display to position correctly, and that we save() and restore() to return to the normal display layout.
This is a simple one, leaving me some time to eat turkey and watch The World Champion Green Bay Packer do whatever they are going to do. Yeah, I'm one of those pathetic knucklehead football fans...
I'm in the process of putting together a sprite-equivalent; in order to do so, I needed to find out how transparancy is handled. The good news: if you have an image with built-in transparancy, it just works. In this example, I created a really simple "O" with a transparent middle and built a simple overlay automation. Sure enough - the transparancy is maintained through the image_surface_draw call.
After a little turkey coma and basking in a Packers win, I decided to open the door to the push and pop routines. Basically, the push_group() routine pushes a drawing activity onto the drawing stack, and pop_group() pulls it off - creating a pattern-surface as a result. This can be dropped into an image and used over and over.
In this case, you see that when the system calls the paint() routine, it will reuse the existing image, but if I bang the JSUI object, it will recreate a new image. This is the sort of thing that you will have to do if you choose to reuse a display.
Back home from the Wisconsin trip, and time to put the kids to work. "Here's Photoshop. Please make me a spaceship..."
This is a very simple implementation of a layered sprite system. It has a GenericSprite object, and a use-specific ship object that augments the GenericSprite with a move function. The result is that bangs move the ship back-and-forth among some trees, properly layered within the other sprites.
Nothing too Mgraphics-specific here, but useful for seeing how to perform some basic sprite-like operations within the JSUI/MGraphics system.
Just like working with path and image drawing, you can also transform the user space for pattern creation. Rather than stretching this out over several examples, I just created a simple "Pattern Transform Explorer" for you to see how each of the functions works with a simple linear pattern creation.
Sometimes it is more convenient to do your drawing with another program, then use JSUI to do the display. The problem is that scaling can get to be sort of grainy. Not the case if you use the SVG file format for the drawing - and use the svg_render() function to render it into the current user space.
This patch includes a little SVG I created in Illustrator; I use it within JSUI to create a cool little animation with almost no code.
Using the transform(), translate(), rotate() and scale() functions give us the ability to do some complex graphical drawing that would otherwise require altogether too much math to consider. In this one, we use these function to create a tilted three-planet orbit display on a pretty blue background.
Oh, and we also pipe the output to a Jitter matrix! In order to do this, we use the push_group() and pop_group() functions to create a working area for the drawing, produce an image from the result, then display the output in both the JSUI drawing area and output it as a matrix. The translation (from Mgraphics to Jitter matrix) isn't perfect, but it gives us the ability to do a lot with the result.
Not the most elegant wordwrap function ever written, but you get the idea.
I hope you've enjoyed the series, and find the Mgraphics system to your liking!