REBOL/Zine
Volume 1 Issue 5
Date: 1-Apr-2002
Issues: prev | next
===Lit-Word!
That most precious resource of "spare time" has eluded us for too long,
thankfully a few generous REBOLs have found some to share with the rest of us.
It seems oddly fitting to resurrect the Zine on a day that is both Easter Monday and April Fools day.
-Allen Kamp
=== Discovering The forgotten DRAW dialect (PART #1)
by Cyphre
---Manipulating bitmaps using DRAW and generating the dialect
One year has passed since the introduction of the DRAW dialect in Rebol/View.
Although the DRAW dialect is very powerfull tool for handling graphics in Rebol/View
there is probably only one DRAW tutorial document from Holger Kruse which has been
converted into "Easy-DRAW" thanks to Larry Palmiter. You can find this tutorial in the
documentation folder in Rebol/View desktop. If you are unfamiliar with DRAW basics,
I recommend reading that tutorial.
Because it seems that there are still a lot of people who haven't yet discovered the
power of DRAW I decided (thanks to Allen Kamp ;-)) to write this series of articles where
I want to explain possibilities, useful techniques and simple application examples covering
various areas where DRAW could be used.
In the first part I'd like to show you how to use DRAW's bitmap handling capabilities. As you
could read in the official "DRAW tutorial", DRAW dialect has one very useful command:
'image offset image key-value
This command is handle well image operations like bitmap layout building, scrolling, animation etc.
Because I'd rather keep programming than talking about it, I did a tutorial example script
where you can learn how to generate DRAW dialect and maybe other things like how to make simple
loader, event handler etc.
But before you dig into the script below I'd like to explain the basics about the technique of
generating DRAW dialect because this could be useful not only in the DRAW case.
The DRAW dialect is a block of words and other Rebol datatypes, which is placed in the effect
block of a face. So you can generate it like normal series. Let say we want to generate five
scrolling images for example:
draw-blk: copy []
repeat n 5 [
append draw-blk compose [image (to-pair (n - 1) * 25) + scroll logo.gif]
]
then:
>> probe draw-blk
[
image 0x0 + scroll logo.gif
image 25x25 + scroll logo.gif
image 50x50 + scroll logo.gif
image 75x75 + scroll logo.gif
image 100x100 + scroll logo.gif
]
Notice the 'scroll variable inside the block. This variable help us easily scroll with all
DRAW images at once.
Now we put draw-blk into face/effect together with simple gradcol effect:
lay: layout [
origin 0x0
image-box: box 200x100 black with [
effect: compose/deep [draw [(draw-blk)] gradcol 255.0.0 0.0.255]
]
]
Now we can add feel/engage function to the 'image-box for realtime scrolling and set the refresh rate:
image-box/rate: 30
image-box/feel: make image-box/feel [
engage: func [face action event][
if action = 'time [
if (scroll/x: scroll/x + 1) > face/size/x [scroll/x: -170]
show face
]
]
]
For showing the result we just need to initialize 'scroll variable and view the layout:
scroll: 0x0
view lay
The result should be 5 scrolling "Rebol-logo" images mixed with color gradient. All this is
just rendering inside one simple face. Amazing, isn't it? ;-)
So that was brief console session and finally here is the more advanced example script using
the technique explained above. It shows a complete application with image loader and detailed
comments.
Try it and don't forget to play with the code...try to replace the example images with your
favorite ones, add several effects to generated images, change the algorithm which generates
the DRAW block to achieve other image matrix shapes etc.
That's all from the first part about the DRAW. Next time I'd like to show you how to use DRAW
dialect for rendering simple 2d vector objects with another, this time, little game example.
So watch the next issue of Rebol/Zine !!!
If you have any comments, suggestions just contact me at: cyphre@seznam.cz
=url http://www.rebolforces.com/zine/resources/zine1-05/draw-tutor-1.r draw-tutor-1.r
===More than one way to...
By Chris RG
---Skinning
Skinning is a great way to customise our applications so they have a deeply personal look and feel.
Since IOS will increasingly be taking over our screen real-estate, it's good to know there is ample
provision to enforce our own ideas of eye-pleasing colour schemes.
Customisation of the Link client desktop can all be handled within the %skins.r file. This is
located in the %desktop/ folder at the top of the Link sandbox - for Windows users on the
'Evaluation' server, this may well be at 'C:\rebol\link\evaluation\'.
Two words are set in the %skins.r file: 'back-image and 'back-effect and sure enough,
they set the 'image and 'effect facets of the main desktop face.
The 'back-image is relative to the sandbox, so if you have a shared image within your folder,
this would be %users/yourname/skin.png. Careful composition of this image will ensure how well
it sits behind the buttons and icons of the desktop. More on this later.
---Effects
The 'back-effect block is a regular View effect block. By far the most useful keywords for skins
are 'extend and 'tint. To meet a specific end, image processing keywords like 'blur, 'sharpen,
'grayscale, 'colorize, 'emboss, etc. can come in handy. For simple photographic backdrops,
'fit will stretch the image to cover the desktop.
Although the View users guide states that 'extend takes two pair! arguments, if only one pair!
is provided, the image will be stretched to fit the containing face. Unlike 'fit which scales
every pixel in the image, 'extend only stretches the pixels that match the x value
(they stretch horizontally) or the y value (they stretch vertically) contained within the pair!,
thus the corners of the image are maintained.
One easy way to adjust skins without creating new images is to use 'tint. This adjusts the hue
values of an image, leaving brightness values - and therefore, grayscale components - intact.
A blue background becomes a green or a pink background while the black and white logo stays black
and white. Use Effect Lab to try out the 'tint effect.
---Composing Images
Skin images are not the easiest to plan. While it is easy to concentrate on the four corners
where the majority of the detail goes, you have to be careful how each relates to the other
three. The top two share the same height as do the bottom two while the two left corners share
the same width as do the right two. Your freedom is to set these widths and heights to your own
content. In between, it's best (but not necessary) to stick with simple boxes, lines and plain
colours, otherwise be prepared for some unsightly stretches.
Dimensions that are important: The area behind the buttons at the top is 32 pixels high,
followed by a gap of 24 pixels until the 25 pixel area behind the desktop buttons.
At the bottom there is a 27 pixel panel for information displays - this neatly puts the panel
under the desktop scrollbar when it appears.
---Setting the Skin Files
To set the 'extend value in %skin.r, the x value is the width of the left corners and the y
value is the height of the top corners. Thus the file content should read:
back-image: %users/yourname/skin.png
back-effect: [extend 52x86]
Distribution of the skin can be wrapped in a script that overwrites the current %skin.r file:
REBOL [type: 'link-app]
write link-root/desktop/skin.r {
back-image: %users/yourname/skin.png
back-effect: [extend 52x86]
}
Ensure your image path is correct and the image is shared - or else anyone trying out your
skin will be locked out of Link. Share this file, run it and restart Link. If all goes
wrong, delete %skin.r manually and Link will resync with the default file.
=url http://www.rebolforces.com/zine/resources/zine1-05/skin1.jpg Example1.jpg
=url http://www.rebolforces.com/zine/resources/zine1-05/skin2.jpg Example2.jpg
=url http://www.rebolforces.com/zine/resources/zine1-05/skin3.jpg Example3.jpg
=url http://www.rebolforces.com/zine/resources/zine1-05/skin4.jpg Example4.jpg
===Bigger than a pixel - working with sections of an image!
By Gregg Irwin
---Cutting and pasting, the old fashioned-way
If you have an image, and you want to chop it up into little pieces,
or paint part of another image over a section of it, there are no
native functions to help you out. Rolling your own routines isn't
difficult, so that's what we'll do. This approach isn't very efficient,
but it's easy to code and can sure be handy.
---The basics
The first thing we need to address is the fact that images are series!
values, which are linear, but they appear in rectangular areas which
we see of as using pair! values for coordinates. Obviously, we need to
convert from pair! values, which are good for humans, to a linear index
in the image! data if we want to read and write values there.
\note What? No error handling?
To keep things as clear and concise as possible, there is no boundary
checking logic, or error handling, which obviously you'd want to include
if you use this anywhere important.
/note
Converting from an x-y coordinate to a linear index is easy enough. The cols
parameter tells us the x extent of the data, so we know where each logical
row ends.
to-index: func [pos [pair!] cols [integer!] ] [
pos/y - 1 * cols + pos/x
]
Reading and writing data to any series type works with this approach, so you
don't have to limit yourself to images.
pick-xy: func [data [series!] pos [pair!] cols [integer!] ] [
pick data to-index pos cols
]
poke-xy: func [data [series!] pos [pair!] cols [integer!] value ] [
poke data to-index pos cols value
]
These wrappers just reduce the number of parameters we have to pass,
and make it clear exactly what we're doing. I.e. working with images
and pixels.
pick-pixel: func [img [image!] pos [pair!]] [
pick-xy img pos img/size/x
]
poke-pixel: func [img [image!] pos [pair!] value [tuple!]] [
poke-xy img pos img/size/x value
]
---The good stuff
Next we'll add a couple routines that can simplify things quite a bit if
you want to deal with more than one pixel at a time.
The clip function copies a rectangular section of an image and returns it as
a new image, of the size specified.
clip: func [img [image!] offset [pair!] size [pair!] /local result] [
result: make image! size
repeat row size/y [
repeat col size/x [
poke-pixel
result
to-pair reduce [col row]
pick-pixel img add offset to-pair reduce [col row]
]
]
result
]
The paint-clip function takes two images and paints the contents of the first
(img) onto the second (dest) at an optionally specified location (/at pos).
You can also tell it to paint only part of the source image using the /part
refinement.
paint-clip: func [
{The mirror of clip to poke a clip into an image at a given offset.}
img [image!]
dest [image!]
/at pos [pair!]
/part sz [pair!]
/local offset size
][
offset: either at [pos][0x0]
size: either part [min sz img/size][img/size]
repeat row size/y [
repeat col size/x [
poke-pixel
dest
add offset to-pair reduce [col row]
pick-pixel img to-pair reduce [col row]
]
]
dest
]
---The result
This isn't rocket science, and I'm sure you can see now why this approach
isn't very efficient. We're copying sections pixel by pixel! By using
copy/part and change/part, you could speed things up dramatically (as you will
see in the next Zine issue) but this gives us a functional starting point to
compare future results against.
Is there an actual use for this kind of thing? You bet! It's very handy for
compositing images and, one of my favorite uses, taking a single image you
deploy and chopping it up at runtime into various pieces. For example, if you
have a grid-like style with lots of images in it, you can deploy dozens of
individual image files or create them individually, crunch them into one image
for deployment, and cut it apart when you load it.
REBOL gives you lots of other alternatives for resource management as well,
so consider this as just another tool for the toolbox, not the only one.
=== Bits & Pieces
by Allen Kamp
---How can I change the title text of a window?
Use /changes facet
lay: layout [
size 400x100
button "Change" [
lay/text: join "Changed " now
lay/changes: 'text
show lay
]
]
view/title lay "Title Text"
/changes is yet to be fully documented, however here are few other uses for it.
To enable View to take advantage of hardware scrolling add the following after you
have changed the offset value.
face/changes: 'offset
show face
In IOS to cause the window to become active, (depending on the OS) the window will pop to
the front as the active window and/or it may flash in the system tray. Use
lay/changes: 'activate
show lay
---Did you know?
*date datatype /julian returns the number of days since the beginning of the year.
today: now
today/julian
==91
*to-idate, returns a standard Internet date string, but how to convert it back?
idate: to-idate now
=="Mon, 1 Apr 2002 20:25:50 +1000"
date: to-date skip idate 5
==1-Apr-2002/20:25:50+10:00
*email datatype can return the user or host part for you?
address: zine@rebolforces.com
address/host
== rebolforces.com
address/user
== zine
=== Pick of the List
The pick of the list for this issue is this explanatory gem posted to the rebol-list by Holger.
--- Everything is a Value
A little background information:
In REBOL everything is a value. That includes words. So internally, something
like 'foo or first [foo] is a value just like any other value (integers,
blocks etc.).
Values have properties that distinguish them from other values of the same
type. For instance the (only) property of an integer! value is the number
itself. block! values have two properties: a reference to the block series
(which can be shared with other block! values), and an index.
With this point of view, values of type word! have properties, too, exactly
two of them: one is the spelling of the word, the other one is the context the
word is bound into. Contexts are name-value lookup tables which are used to
hold "variables" for functions, objects and use []. A context consists of two
tables: a table of words and a table of corresponding values. For object!s
you can actually look at those two tables:
>> a: make object! [b: 1 c: 2]
>> first a
== [self b c] ; The table of all words in the context
>> second a
== [ ; The table of all corresponding values in the context
make object! [ ; the first one being the object itself (self)
b: 1
c: 2
] 1 2]
There is also one "global context" in REBOL that is used for globally defined
variables. Almost all words used in REBOL are automatically bound into the
global context (usually during 'load). Some words are later rebound into a
different context, but only after they were first bound into the global
context. For instance the words that appear inside of the body of a function
are first bound into the global context when your script is loaded. Later, when
REBOL executes the function definition, the words in the body of the function
are rebound into the context created for the function.
As a result, all words in a normally loaded REBOL script are always bound into
some context, either the global context or some other context.
Binding a word into a context does not assign a value to it. It only associates
a word with a context. The value of a word in a context is defined separately,
e.g. using 'set or the set-word notation.
It IS possible to create words which are not bound into any context at all, and
that is what Ladislav's undefined? function detects. A better name would be
unbound?. It is not possible to assign a value to unbound words, because there
is no context to hold the value.
Unbound words are relatively rare in normal use (which is why you rarely see
undefined? return true), but they do occur, usually in one of three ways:
- Some natives that return words as symbols return unbound words.
An example is "type?". It returns a word describing the datatype of
the argument. That word is an unbound word:
>> undefined? type? 1
== true
- "first" applied to an object! returns a block of words (the "left"
half of the name-value lookup table used by the context of the
object!). The words in that table are unbound. This also includes
first system/words (because system/words is an object representing
the global context).
>> a: make object! [b: 1]
>> undefined? second first a
== true
- to-block applied to a string! is similar to "load", but, unlike
"load", does not bind the resulting words into the global context.
All words remain unbound.
>> a: to-block "b"
>> undefined? first a
== true
In almost every other situation undefined? returns false, because the word
has been bound into a context. In contrast, value? only returns true if
a word is bound into a context AND in that context the value of the word
has been defined (i.e. is different from unset!). The effect of the
unset native is only to set the value of a word in its context to unset!
(so value? subsequently returns false). It does not "unbind" a word from
a context, i.e. the value returned by undefined? is not affected by it.
The part that, at first, may make these concepts a little confusing, is
that many users intuitively associate a word only with its spelling, and
ignore the context. For instance:
>> type? 1
== integer!
>> undefined? type? 1
== true
>> undefined? integer!
== false
In this example two different values representing words are used, which are
both spelled "integer!": the first is the one returned by "type? 1". It is not
bound into a context, so undefined? returns true. The second is the one typed
into the console. When REBOL interprets the sequence of characters you type,
it first loads it, which binds all words into the global block, so for THAT
instance of the word "integer!" undefined? returns false.
--
Holger Kruse
===Get-Word!
It seems a tradition at the end of each zine to ask for contributions, so I won't break with
tradition.
Send in the contributions!!!!! zine@rebolforces.com
If you have ideas for articles or topics you would like covered, send those in too.
And Lastly, THANK YOU Jeff for starting the Zine in the first place and guiding it
through its first 4 issues, the reins are yours whenever you want them back ;-)
Keep REBOLing..
halt
###