Working with Vector Graphics
Livecode provides the user with the ability to draw a couple of different ways: Raster and Vector. For those of you who don't know, here's the difference:
Raster: when you change individual pixels to form an image. So the information of an image will be a series of pixel-by-pixel information that is fixed in place when done, as with any standard jpeg-like image.
Vector: A line drawn between two defined points. The information of this kind of image will be start (x,y) and finish (x,y) and a line drawn in between. These graphics can be manipulated by simply changing the start and finish points.
The benefits behind one or the other?
Rasters are fixed, simple and have an infinite number of ways they can be represented. They can be changed pixel-by-pixel using filters like photoshop would use.
Vectors are infintely scalable and changeable without loss. That means that no matter how you zoom or scale the image, the image will always be as clear as it started, without any jagged pixelisation. Vectors are also low in size due to the decreased amount of information. Rather than accounting for each pixel of the entire image or line, you just account for the start and finish of any individual line.
So...if you want to deal with vector graphics, LiveCode provides you with 2 ways.
1. Draw on the screen as you make the card, which can then stay as it is, or be scripted to change, or;
2. Script a drawing tool to allow the user to create graphics as they go, which can stay the same or be scripted to be changeable.
I tried to make a vector graphic section to a standalone app and found it very difficult to find everything I needed to make it work. Bits and pieces of the sccript were here and there, and what was scripted either didn't do what I wanted it to, or didn't fully work. So, I worked it up, and will present it all here in one place for the future newbies to get in one place, with explanations of what the script does, so you can learn as you go.
We will look at creating an app that allows the user to draw the following graphics: Line, Rectangle, Oval.
Each has their own intricacies and issues, and we will unravel the trip-up points as we go.
First up: LINE
One issue with the line command is the fact that LiveCode uses the term "line" to refer to lines of code. If you search for LINE in the dictionary, you tend o end up elsewhere, and if you use line (note no quotation marks) and not "line", you get bugs, because LiveCode is looking for something else.
To put together a line drawing app, start a new stack. Add a button. Add a rectangle. Drag and drop these from the tool pallette, and arrange them on screen so the button is at an edge, and the rectangle fills most of the card. Rename the button to be called Line.
You now have a button to make a line, and drawing area (rectangle). Please note that rather than using a rectangle, we could use the card itself. The script that would go on to the rectangle would then go into the card script.
What we need to do now is script the button to note that we will be drawing lines, and script the rectangle to create lines when we click on it.
Button script:
Code: Select all
global dType
on mousedown
put "line" into dType
end mousedown
Copy this script into the code for the button (click on the button, click on the code tool, and a box with numbered lines should appear)
What we just did:
Firstly, we defined a global variable, called "dType"- it could be called anything, but I have made it short for "drawing type" = dType. This will hold the type of drawing we are doing. If all we are doing is lines, you wouldn't need this, but as we will add rectangles and ovals later, this will come in handy.
Secondly, we stated that when the user clicks the mouse (mousedown command), the word "line" will go into the global variable dType. And we ended the mousedown command to say not to do anything else.
At this point, we have a little variable that is holding the word "line", to be used by the drawing area to define what we are drawing. In this case, lines.
Drawing area script:
Code: Select all
global dType
local tPointsList
on mousedown
put the mouseloc into tPointsList
end mousedown
on mouseup
put CR & the mouseloc after tPointsList
create graphic "myNewLine"
set the style of graphic "myNewLine" to dType
set the lineSize of graphic "myNewLine" to 3
set the points of graphic "myNewLine" to tPointsList
set the antialiased of graphic "myNewLine" to true
end mouseup
Copy the script into the code for the rectangle. (click on the rectangle, click on the code tool as per the button)
What we just did:
We called up the global variable dType, which will find that variable in the app.
We created the local variable tPointsList (temporary points list), which will house the list of points used to define our line. When the points are entered into it, it will look something like this:
x1, y1
x2,y2
Where x1 and y1 are the x and y co-ordinates of the start of the line, and x2 and y2 the end.
This variable is LOCAL and only used in this script so it can't be called up elsewhere. You don't actually need to create this variable, if you just say it later it will create itself.
We then said that when the mouse is clicked (in particular when the mouse goes down), we want to know the mouse location (mouseLoc), and we want to put that into our tPointsList variable so we can use it when we draw the line. That's all we want when the mouse goes down, so we closed out that command.
When the mouse is released (mouseup), we want the location of that too, then we want to draw the line.
HOWEVER... we need to use the code "CR & the mouseloc after" because we want to put this AFTER the pervious set of data (the mousedown mouse location), and we need to hit the return key first, to get it to a new line. So, we write put CR (hit the return key) & (and then) the mouseloc (mouse location) after the stuff that's already in tPointsList. That will give us the exact format of information as shown above with x1,y1 and x2, y2. It is this format only that is readable by the line graphic to write a line...
Now we have everything we need to define a line. we just need to draw it.
Then we create a line, and call it "myNewLine" so it ahs a name, and we can manipulate it via this name.
We set the type of vector graphic to be what it says in the global variable dType. In our case, "line". Livecode now knows we're drawing a line, and will look for the information required to draw a line.
We then define how wide that line is, in this case, 3 pixels wide.
We then say that the start point and end point will be the points we defined with our mousedown and mouseup commands, by calling up the local variable tPointsList. Livecode will look for 2 lines of 2 numbers, separated by commas, as we have set up.
Livecode will now draw the line.
Then we define the line as being antialiased (smooth at the sides with grey tones).
And we're done!
This script is simple and will just draw lines. No undoing, no erasing, no selecting or altering. We will add other functions later. You will also note when testing, that the line you drew will disappear when you draw another one. We'll fix this next...
Next up: RECTANGLES and OVALS
We will combine the two, as these have the same issues and can be addressed as we go.
Issues that arise with these graphics are: Rectangle is also used elsewhere in LiveCode, so the same applies here as to lines when it comes to using quotation marks. Use them to define a vector graphic, and not the outside of an object. Also, when defining, we can't define points, because a rectangle has a convention of points that create right angles. Defining each point makes a polygon, not a rectangle. Same goes for ovals, as we're defining the rectangle that goes around the oval. Consequently, we need to do a shuffle of the x's and y's to make sure they line up with the way we draw it, or else, we require the user to draw in one direction (i.e. top left to bottom right in our example). As this is not viable and frustrating to users, I have added a section to allow drawing in any direction.
The way I have decided to get the drawing done is as per the previous example for lines. Click, slide, release. Draw a rectangle between the click and release points.
Same as before, we need 2 new buttons, one for rectangles, one for ovals. Drag these next to the line button, and rename as Rectangle and Oval in Properties Inspector.
Script the buttons:
Code: Select all
Rectangle:
global dType
on mousedown
put "rectangle" into dType
end mousedown
Code: Select all
global dType
on mousedown
put "oval" into dType
end mousedown
Open up the code for the drawing area (click on rectangle, click on code tool), and paste the following in to it:
Code: Select all
global dType
global GraphicNumber
local tPointsList
on mousedown
put the mouseloc into tPointsList
end mousedown
on mouseup
put CR & the mouseloc after tPointsList
if dType is "Line" then
create graphic "myNewLine"
set the style of graphic "myNewLine" to "line"
set the lineSize of graphic "myNewLine" to 1
set the points of graphic "myNewLine" to tPointsList
set the antialiased of graphic "myNewLine" to true
set the colors of graphic "myNewGrc" to red
set the colorOverlay of graphic "myNewGrc" to red
else if dType is "Rectangle" then
put item 1 of line 1 of tPointsList & comma & item 1 of line 2 of tPointsList into tXList
put item 2 of line 1 of tPointsList & comma & item 2 of line 2 of tPointsList into tYList
sort items of tXList ascending numeric
sort items of tYList ascending numeric
put empty into tPointsList
put item 1 of tXList & comma & item 1 of tYList into tPointsList
put cr & item 2 of tXList & comma & item 2 of tYList after tPointsList
create graphic "myNewGrc"
set the style of graphic "myNewGrc" to dType
set the lineSize of graphic "myNewGrc" to 3
set the width of graphic "myNewGrc" to item 1 of line 2 of tPointslist - item 1 of line 1 of tPointsList
set the height of graphic "myNewGrc" to item 2 of line 2 of tPointsList - item 2 of line 1 of tPointsList
set the left of graphic "myNewGrc" to item 1 of line 1 of tPointsList
set the top of graphic "myNewGrc" to item 2 of line 1 of tPointsList
set the antialiased of graphic "myNewGrc" to true
set the colors of graphic "myNewGrc" to red
set the colorOverlay of graphic "myNewGrc" to red
else if dType is "Oval" then
put item 1 of line 1 of tPointsList & comma & item 1 of line 2 of tPointsList into tXList
put item 2 of line 1 of tPointsList & comma & item 2 of line 2 of tPointsList into tYList
sort items of tXList ascending numeric
sort items of tYList ascending numeric
put empty into tPointsList
put item 1 of tXList & comma & item 1 of tYList into tPointsList
put cr & item 2 of tXList & comma & item 2 of tYList after tPointsList
create graphic "myNewGrc"
set the style of graphic "myNewGrc" to dType
set the lineSize of graphic "myNewGrc" to 3
set the width of graphic "myNewGrc" to item 1 of line 2 of tPointslist - item 1 of line 1 of tPointsList
set the height of graphic "myNewGrc" to item 2 of line 2 of tPointsList - item 2 of line 1 of tPointsList
set the left of graphic "myNewGrc" to item 1 of line 1 of tPointsList
set the top of graphic "myNewGrc" to item 2 of line 1 of tPointsList
set the antialiased of graphic "myNewGrc" to true
set the colors of graphic "myNewGrc" to red
set the colorOverlay of graphic "myNewGrc" to red
end if
set the name of last graphic to "Line"&GraphicNumber
put GraphicNumber +1 into GraphicNumber
end mouseup
Firstly, we have added a new global variable called GraphicNumber. This will be used to keep track of what graphic we are drawing. Essentially it will be used to rename graphics sequentially at the end of the script so tat they don't get erased every time we draw something.
The mousedown and start of the mouseup commands are exactly as previous. Just a way of getting the raw data of the location of the mouse at click and release.
Then, we create 3 section, one for line, one for rectangle, and one for oval. (I am sure there's a way of combining the rectangle and oval sections, but the code I used failed, so I went long-hand on it.) Each section is part of an if... then, else if... then command.
Firstly, we ask if the global variable dType is the word "Line", if it is, we continue in this section, if not, it will continue on to the next section. If it says line, it will execute the same script as before, except it will make the line red, using the last 2 lines of code. For some reason, when I simply put "set the colours ... to red", it put an overlay on it, and made that overlay green, meaning that the colours didn't show correctly. the last line rectifies this.
Secondly, if the global variable wasn't "Line", we ask it if it was "Rectangle". If so, it executes the remaining code in the section, if not, it passes on to the next section. If it wasn't "Oval" either, then it would end the code and do nothing. The rest of the rectangle code is the same as the oval code, so I shall explain only once.
At this point, we have a local variable that contains numbers that define the 2 mouse clicks. If we used them as-is, we would require the user to click top left to bottom right, as we define the graphic by the top and left, and width (right) and height (bottom). If the numbers don't line up and a negative number results, then a rectangle can't be drawn. So, the next few lines re-order the numbers to get them into a different order so that the first line of numbers in the tPointsList variable contain the lowest of the two numbers (x and y of either point) and the second line contains the highest. The first two lines of code separate the x value of the first and second click point into a variable called tXList and the y's into tYList. It does this by grabbing the first number (item 1) of the first line (x1,y1 in the example above) and the first number (item 1) of the second line (x2,y2), and writes them out with a comma (& comma &) in the variable. That variable would read (x1, x2). The same happens for the y value. We can now re-order the x's and y's separately. The next 2 lines do this and it should be self-evident how they do it. The tXList and tYList variables should read as something like 1,2 where the first number is smaller than the second. We then make sure the tPointsList variable is empty by "put empty into tPointsList" and then reinsert the smallest x and y value (item 1 of tXList and item 1 of tYList) into the tPoints List, and the largest on to the second line with the return (CR &) and item 2's. This way, we have a list that would read:
1,1
2,2
So, regardless of where the user clicks on screen, the numbers will mimic a top-left to bottom right click. i.e. 2,1 and 1,2 (Top Right to Bottom Left) will be re-ordered to be Top Left- Bottom Right 1,1 and 2,2. Confused? Well, don't consider the values as locations, consider them as top, left, width (x-shift) and height (y-shift). So it doesn't matter that the points are different, they are just different points on the SAME rectangle (points being 1,1 & 2,1 & 1,2 & 2,2- note that 1,1 and 2,2 are there are well as 2,1 and 1,2).
Next we need to make the rectangle. Remember I said you can't define a rectangle by points? We need to do 2 things now, and in a specific order. 1. Create the SIZE then 2. Create the location. Doing it in the other order will locate the rectangle, then make it bigger around the centre, throwing it off of the points we clicked to create it. To create the width, we take the largest of the 2 x locations and remove the smallest, as with the example above, it would be 2-1 = 1 unit wide, the actual width of the rectangle. Do the same with the y values to get the height. We have now created a rectangle that is 1 unit high, 1 unit wide. We then locate the left side using the x value of the first line, and the top using the y value of the first line. Set the antialiased and colour, and we're done drawing.
We then end out the "if" command that has drawn our graphics. We then need to rename the graphic so it doesn't get over-written, so we tell the app to name the last graphic we drew ("last graphic"), Line and whatever number is in the global variable GraphicNumber. If that variable was 0, then it would be called Line0. We then need to add a number to the variable GraphicNumber, making it 1, so that the next line will be called Line1, and so on. As they are now called different things, they won't keep getting over-written every time we create a new graphic.
Now is a good time to go into the Card Script (in the Object dropdown menus at the top) and write a little script that says this:
Code: Select all
global GraphicNumber
on cardopen
put 0 into GraphicNumber
end cardopen
And you should be done! Draw rectangles, lines and ovals.
I will add some script to change colours, line widths etc. soon. They are easy add-ons using global variables, and just changing things in the current script to be variable rather than fixed.
If anyone has any refinements to the scripts, I am more than happy to take them on board!