Originally published in Rebol Forces.

Reb-IT!
Volume 1 Issue 5
Date: 1-Apr-2002
Issues: prev | next

Contents

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<cyphre@seznam.cz>

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

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.

Example1.jpg Example2.jpg Example3.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.

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.

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