Written by Nick Gammon - July 2008. Updated September 2010.
On this page:
See also:
Examples on this page
Most of the examples on this page are drawn in a 200 x 200 pixel window, with a grid drawn every 20 pixels to make it clearer the effect of the example code. The code for producing the grid is described in Creating miniwindows.
The MUSHclient miniwindows let you load bitmap images (or create small ones yourself) and then display them in various ways. You can also turn other miniwindows into images to be used in further miniwindows.
Before images can be drawn to the miniwindow, the image must be "loaded". This specifies that this miniwindow is going to use this image. Later on you simply refer to the "image id" you gave when loading the image.
WindowLoadImage function prototype:
long WindowLoadImage(BSTR Name, BSTR ImageId, BSTR FileName);
This loads the specified image into the miniwindow, and remembers it by the nominated "image id". The image id is later used for drawing this image. Loading does not draw it, it simply reads it from disk ready for drawing later.
WindowLoadImage (win, "im1", "C:/program files/mushclient/leaf.bmp")
In Lua you can also use WindowLoadImageMemory which loads an image from a memory buffer. Only PNG file types are supported for WindowLoadImageMemory. For example:
local f = assert (io.open ("sword.png", "rb")) -- open read-only, binary mode
local image_data = f:read ("*a") -- read all of it
f:close () -- close it
WindowLoadImageMemory (win, "im", image_data) -- load image from memory
WindowDrawImage (win, "im", 20, 20, 0, 0, miniwin.image_copy) -- draw it
WindowImageList function prototype:
VARIANT WindowImageList(BSTR Name);
This returns a list of all images loaded into this miniwindow. You could use this to find which images have been loaded, and then use WindowImageInfo to find information about each one.
-- show all images
images = WindowImageList(win)
if images then
for _, v in ipairs (images) do
Note (v)
end
end -- if any
WindowImageInfo function prototype:
VARIANT WindowImageInfo(BSTR Name, BSTR ImageId, long InfoType);
This returns information about a loaded image. You need to specify the name of the miniwindow, and the image id you used when loading the image.
WindowDrawImage function prototype:
long WindowDrawImage(BSTR Name, BSTR ImageId, long Left, long Top, long Right, long Bottom, short Mode, long SrcLeft, long SrcTop, long SrcRight, long SrcBottom);
This copies an image to the miniwindow. Also see WindowMergeImageAlpha which lets you copy an image to a miniwindow based on a second image which is the alpha mask. Also see WindowDrawImageAlpha which lets you copy an image to a miniwindow, respecting the alpha channel of theimage.
Value | Purpose | Lua symbol |
---|---|---|
1 | Copy without stretching to the destination position. The image is not clipped, so only the Left and Top parameters are used - the full image is copied to that position. | |
2 | Stretch or shrink the image appropriately to fit into the rectangle: Left, Top, Right, Bottom. | |
3 | Copy without stretching to the position Left, Top. However this is a transparent copy, where the pixel at the left,top corner (pixel position 0,0) is considered the transparent colour. Any pixels that exactly match that colour are not copied. |
The intention of allowing sub-images was so that you could make up a map by having lots of small images (eg. houses, trees, rocks). Now instead of having to load hundreds of tiny image files into memory, which would doubtless take a while to open all those files, you open a single, larger, file in which the various sub-images are "tiled". For example, you might allocate a 32 x 32 pixel square for each image. Now by selecting the appropriate tile you can copy the sub-image from the main image.
WindowLoadImage (win, "im", "/Windows/Zapotec.bmp")
WindowDrawImage (win, "im", 20, 20, 0, 0, miniwin.image_copy) -- straight copy
WindowLoadImage (win, "im", "/Windows/Zapotec.bmp")
WindowDrawImage (win, "im", 20, 20, -20, -20, miniwin.image_stretch) -- stretch
WindowLoadImage (win, "im", "/Windows/Zapotec.bmp")
WindowDrawImage (win, "im", 20, 20, -20, -20, miniwin.image_transparent_copy) -- transparent
The transparent image doesn't look particularly exciting here, but in practice you would use transparency to copy odd-shaped things (like an image of a tree) onto a landscape.
WindowDrawImageAlpha function prototype:
long WindowDrawImageAlpha(BSTR Name, BSTR ImageId, long Left, long Top, long Right, long Bottom, double Opacity, long SrcLeft, long SrcTop);
This copies an image to the miniwindow, respecting the alpha channel in the image. At present only PNG images can have alpha channels. The alpha channel effectively is a 4th byte per pixel (the others being the red, green and blue components of the pixel). This 4th byte specifies the transparency of the pixel, from fully transparent (0) to fully opaque (255), in the range 0 to 255. As you can see from the mask in the image below the black parts (which have a value of 0) are transparent, so the background shows through. The white parts (which have a value of 255) are opaque, so you do not see the background. The grey parts (somewhere between 1 and 254) let the background image show through partially.
The intention of allowing sub-images was so that you could make up a map by having lots of small images (eg. houses, trees, rocks). Now instead of having to load hundreds of tiny image files into memory, which would doubtless take a while to open all those files, you open a single, larger, file in which the various sub-images are "tiled". For example, you might allocate a 32 x 32 pixel square for each image. Now by selecting the appropriate tile you can copy the sub-image from the main image.
Example:
win = "A" .. GetPluginID () -- get a unique name
WindowCreate (win, 0, 0, 365, 285, miniwin.pos_center_all, 0, ColourNameToRGB("white")) -- create window
-- Grid
for i = 1, math.max (WindowInfo (win, 3), WindowInfo (win, 4)) / 20 do
WindowLine (win, i * 20, 0, i * 20, WindowInfo (win, 4), 0xC0C0C0, miniwin.pen_solid, 1)
WindowLine (win, 0, i * 20, WindowInfo (win, 3), i * 20, 0xC0C0C0, miniwin.pen_solid, 1)
end -- for
WindowLoadImage (win, "im", "C:/alpha_test.png")
WindowDrawImageAlpha (win, "im", 20, 20, 0, 0, 1) -- opacity 1 (full)
WindowShow (win, true) -- show it
Original image:
Alpha mask in Photoshop:
Resulting image in Photoshop (checkerboard shows transparent area):
Resulting miniwindow in MUSHclient drawn using code above:
WindowGetImageAlpha function prototype:
long WindowGetImageAlpha(BSTR Name, BSTR ImageId, long Left, long Top, long Right, long Bottom, long SrcLeft, long SrcTop);
This copies the alpha channel of an image to the miniwindow, showing the alpha information as grayscale.
The alpha channel could be used when copying an image onto the miniwindow using the WindowMergeImageAlpha function. Only PNG images potentially have alpha channels.
Thus you could use WindowGetImageAlpha to create a scratch window (one not visible to the player) which holds the alpha information. This could then be turned into another image by using WindowImageFromWindow. This image is now available for use with WindowMergeImageAlpha to selectively copy the image to another miniwindow (this image would be used as the MaskId).
WindowImageOp function prototype:
long WindowImageOp(BSTR Name, short Action, long Left, long Top, long Right, long Bottom, long PenColour, long PenStyle, long PenWidth, long BrushColour, BSTR ImageId, long EllipseWidth, long EllipseHeight);
This draws an ellipse, rectangle, round rectangle, controlled by the Action parameter. The specified shape is filled with a previously-loaded image.
Value | Purpose | Lua symbol |
---|---|---|
1 | Ellipse | |
2 | Rectangle | |
3 | Round Rectangle |
Resulting image is under the corresponding code.
WindowLoadImage (win, "im", "/Windows/Zapotec.bmp")
WindowImageOp (win, miniwin.image_fill_ellipse, -- circle
20, 20, 150, 150, -- Left, Top, Right, Bottom
ColourNameToRGB("blue"), miniwin.pen_solid, 2, -- pen width 2
ColourNameToRGB("cyan"), "im")
WindowLoadImage (win, "im", "/Windows/Zapotec.bmp")
WindowImageOp (win, miniwin.image_fill_rectangle, -- rectangle
0, 0, 0, 0, -- Left, Top, Right, Bottom
ColourNameToRGB("blue"), miniwin.pen_null, 1, -- no pen
ColourNameToRGB("cyan"), "im")
WindowLoadImage (win, "im", "/Windows/Zapotec.bmp")
WindowImageOp (win, miniwin.image_fill_round_fill_rectangle, -- round rectangle
20, 20, 160, 160, -- Left, Top, Right, Bottom
ColourNameToRGB("blue"), miniwin.pen_solid, 3, -- pen width 3
ColourNameToRGB("cyan"), "im",
20, 20) -- amount of roundness
WindowCreateImage function prototype:
long WindowCreateImage(BSTR Name, BSTR ImageId, long Row1, long Row2, long Row3, long Row4, long Row5, long Row6, long Row7, long Row8);
This creates a small image (bitmap) by specifying the bit pattern for an 8 x 8 bit image. This is primarily intended for making patterns for use with WindowImageOp, described above. By designing suitable patterns you could make grass, waves, or other landscape effects for use in maps.
The easiest way of making your own pattern is to use Lua's tonumber function and work in binary. When doing that, each '1' represents a bit that is set (and is thus in the PenColour) and each '0' represents a bit that is clear (and is thus in the BrushColour).
Resulting image is under the corresponding code.
WindowCreateImage (win, "im2",
tonumber ("11001100", 2), -- row 1
tonumber ("11001100", 2), -- row 2
tonumber ("00110011", 2), -- row 3
tonumber ("00110011", 2), -- row 4
tonumber ("11001100", 2), -- row 5
tonumber ("11001100", 2), -- row 6
tonumber ("00110011", 2), -- row 7
tonumber ("00110011", 2)) -- row 8
WindowImageOp (win, 2, 20, 20, 180, 180, -- rectangle
ColourNameToRGB("blue"), miniwin.pen_null, 0, -- pen
ColourNameToRGB("cyan"), "im2")
WindowCreateImage (win, "im3",
tonumber ("00000100", 2),
tonumber ("00000100", 2),
tonumber ("00000100", 2),
tonumber ("11111111", 2),
tonumber ("00000001", 2),
tonumber ("00000001", 2),
tonumber ("00000001", 2),
tonumber ("11111111", 2))
WindowImageOp (win, miniwin.image_fill_rectangle, -- rectangle
20, 20, 180, 180, -- Left, Top, Right, Bottom
ColourNameToRGB("sienna"), miniwin.pen_null, 0, -- pen
ColourNameToRGB("moccasin"), "im3")
WindowImageFromWindow function prototype:
long WindowImageFromWindow(BSTR Name, BSTR ImageId, BSTR SourceWindow);
This uses an existing miniwindow's offscreen bitmap, to make an image that can be loaded into another miniwindow. This lets you set up offscreen windows (that you may never plan to actually show with WindowShow) as "work areas" for creating text or images, that can be loaded or blended into other windows.
-- create another window
WindowCreate ("map2window", 0, 0, 150, 150, miniwin.pos_top_left, 0, 0x000000) ) -- top left corner
-- pull in map window as "map" from mapping plugin
WindowImageFromWindow("map2window", "map", "86810b755a33169f0f1d9585")
-- draw our small map into "map2" window
WindowDrawImage ("map2window", "map", 0, 0, 0, 0, miniwin.image_stretch);
-- show smaller map
WindowShow ("map2window", true)
In this example the original map from the mapping plugin was turned into "map" image for the second window "map2window", and displayed by compressing it, into 150 x 150 pixels in the top-left corner.
Note that the image is a static copy, if the map changed you would need to re-obtain a copy of it.
WindowFilter function prototype:
long WindowFilter(BSTR Name, long Left, long Top, long Right, long Bottom, short Operation, double Options);
This takes a copy of the specified rectangle in a miniwindow, filters it according to the operation specified, and replaces the filtered version back in place. Also see Blending images for other things you can do with images.
Value | Purpose | Lua symbol |
---|---|---|
1 | Apply colour noise | |
2 | Apply monochrome noise | |
3 | Blur | |
4 | Sharpen | |
5 | Find edges | |
6 | Emboss | |
7 | Adjust Brightness (additive) | |
8 | Adjust Contrast | |
9 | Adjust Gamma | |
10 | Adjust Brightness for red channel only (additive) | |
11 | Adjust Contrast for red channel only | |
12 | Adjust Gamma for red channel only | |
13 | Adjust Brightness for green channel only (additive) | |
14 | Adjust Contrast for green channel only | |
15 | Adjust Gamma for green channel only | |
16 | Adjust Brightness for blue channel only (additive) | |
17 | Adjust Contrast for blue channel only | |
18 | Adjust Gamma for blue channel only | |
19 | Convert to grayscale - mix red/green/blue equally | |
20 | Convert to grayscale - mix 30% red + 59% green + 11% blue for normal perception | |
21 | Adjust Brightness (multiply) | |
22 | Adjust Brightness for red channel only (multiply) | |
23 | Adjust Brightness for green channel only (multiply) | |
24 | Adjust Brightness for blue channel only (multiply) | |
25 | Blur (less) | |
26 | Blur (minor) | |
27 | Average - calculates the average colour of all the pixels in the selected area |
Single channel operations - the modes which specify a single channel (10 to 18) operate on that colour channel only (red, green or blue) leaving the others untouched. Thus by, say, increasing brightness on the red channel you add to the red pixel values, but leave green and blue alone, making the image look redder. However decreasing brightness on the red channel would make the image look more cyan (as cyan is the complementary colour to red).
The complementary colours are:
For the Noise filter:
For the Blur, Sharpen, Find Edges and Emboss filters:
For the Brightness (additive) filters:
For the Brightness (multiply) filters:
For the Contrast filters:
Technically the pixel value first has 128 subtracted from it (to "center" it), then the multiplication is done, then 128 is added back. This has the effect of altering contrast, because if you multiply by a number > 1, then bright colours become brighter (because they are > 128) and darker colours become darker (because they are < 128 and are negative when the multiply is done). This "stretches" the brighness away from the center. However a contrast multiplier of < 1 tends to squash the colours towards 128, making them have lower contrast.
For the Gamma filters:
The difference between gamma and brightness is that increasing brightness simply adds to the value of each pixel, so that black becomes gray, and bright pixel become clipped. Similarly, decreasing brightness means that white pixels become grey, and dark ones are clipped.
However because gamma is a power function, black stays black, and white stays white, with the biggest change in the mid range. A higher gamma value results in a darker image with more contrast. A lower gamma value (between 0 and 1) results in a lighter image with less contrast.
Warning - filtering is quite computation-expensive. For example, for a 600 x 600 pixel image, the program has to apply a calculation to 600 x 600 x 3 bytes (one for each of red, green and blue), which would be 1,080,000 calculations. In addition, most of the filtering operations (except noise) need to do a calculation on a "window" of adjoining pixels (2 on each side of each pixel), effectively multiplying the number of calculations by a further 5.
If possible, apply a filter once, and use the filtered image many times. As you can use miniwindows as image sources for other miniwindows, consider setting up a filtered image once, and then just copying it in when needed (eg. draw an elaborate background once, and then just copy in the result when needed).
Some examples below use a picture to illustrate the various modes, this is the original image:
Resulting image is under the corresponding code.
WindowFilter (win, 0, 0, 0, 0, miniwin.filter_noise, 100) -- colour noise in whole window - level 100
WindowFilter (win, 0, 0, 0, 0, miniwin.filter_noise, 50) -- colour noise in whole window - level 50
WindowFilter (win, 0, 0, 0, 0, miniwin.filter_monochrome_noise, 100) -- monochrome noise in whole window - level 100
WindowFilter (win, 10, 10, 90, 90, miniwin.filter_blur, 0) -- blur both directions
WindowFilter (win, 10, 10, 90, 90, miniwin.filter_blur, 1) -- blur horizontal
WindowFilter (win, 10, 10, 90, 90, miniwin.filter_blur, 2) -- blur vertical
WindowFilter (win, 10, 10, 90, 90, miniwin.filter_sharpen, 0) -- sharpen in both directions
WindowFilter (win, 10, 10, 90, 90, miniwin.filter_find_edges, 0) -- edge detect
WindowFilter (win, 10, 10, 90, 90, miniwin.filter_emboss, 0) -- emboss
WindowFilter (win, 10, 10, 90, 90, miniwin.filter_brightness, -100) -- lower by 100
WindowFilter (win, 10, 10, 90, 90, miniwin.filter_brightness, 100) -- raise by 100
WindowFilter (win, 10, 10, 90, 90, miniwin.filter_contrast, 0.5) -- reduce
WindowFilter (win, 10, 10, 90, 90, miniwin.filter_contrast, 2) -- increase
WindowFilter (win, 10, 10, 90, 90, miniwin.filter_contrast, 10) -- increase a lot
WindowFilter (win, 10, 10, 90, 90, miniwin.filter_contrast, -2) -- make negative
WindowFilter (win, 10, 10, 90, 90, miniwin.filter_gamma, 0.3) -- brighter, lower contrast
WindowFilter (win, 10, 10, 90, 90, miniwin.filter_gamma, 1.5) -- darker, higher contrast
WindowFilter (win, 10, 10, 90, 90, miniwin.filter_gamma, 5) -- very dark and contrasty
WindowTransformImage function prototype:
long WindowTransformImage(WindowName, ImageId, Left, Top, Mode, Mxx, Mxy, Myx, Myy);
This lets you copy a loaded image onto any miniwindow (similar to WindowDrawImage) however it lets you:
For details on matrix multiplication: Matrix_multiplication (Wikipedia)
The translation is done by applying a mathematical operation to the location of each pixel during the copy operation. For each original x and y pixel, the new location x' and y' is determined as follows:
x' = (x * Mxx) + (y * Mxy) + Left
y' = (x * Myx) + (y * Myy) + Top
Value | Purpose | Lua symbol |
---|---|---|
1 | Copy without stretching to the destination position. The image is not clipped, so only the Left and Top parameters are used - the full image is copied to that position. | |
3 | Copy without stretching to the position Left, Top. However this is a transparent copy, where the pixel at the left,top corner (pixel position 0,0) is considered the transparent colour. Any pixels that exactly match that colour are not copied. |
For a lengthy discussion about this, with screenshots, see Forum posting: WindowTransformImage
Summary of transformations:
Action | Mxx | Mxy | Myx | Myy |
---|---|---|---|---|
Identity (copy) | 1 | 0 | 0 | 1 |
Reflection on X | -1 | 0 | 0 | 1 |
Reflection on Y | 1 | 0 | 0 | -1 |
Reflection on both | -1 | 0 | 0 | -1 |
Rotation clockwise | cosine | -sine | sine | cosine |
Rotation counter clockwise | cosine | sine | -sine | cosine |
Shear vertically RH side down | 1 | 0 | 1 | 1 |
Shear vertically RH side up | 1 | 0 | -1 | 1 |
Shear horizontally bottom to right | 1 | 1 | 0 | 1 |
Shear horizontally bottom to left | 1 | -1 | 0 | 1 |
Scale by 1.5 (larger) | 1.5 | 0 | 0 | 1.5 |
Scale by 0.5 (smaller) | 0.5 | 0 | 0 | 0.5 |
Note: Some actions may cause the image to move sideways (eg. reflection) in which case you may need to compensate by modifying the Top and Left parameters to return it back to where you want it. In the case of the rotation actions you need to take the sine and cosine of the desired angle of rotation (eg. using math.sin and math.cos in Lua). You may also need to use math.rad to convert degrees to radians.
For a lengthier treatment on matrices (and indeed, why I am using the word) see Matrix (mathematics) (Wikipedia).
A little way down the page, in the section "Linear transformations", it demonstrates the various ways that the matrix elements accomplish the various transformations described above.
In particular, the arguments would be represented as a matrix like this:
[ Mxx Myx 0 ]
[ Mxy Myy 0 ]
[ Left Top 1 ]
If you want to combine actions (eg. scale and rotate) then you would need to do a matrix multiplication, based on the suggested values above, to arrive at the 6 values for the combined action.
This image used above is Thanks to solea by jam343. It is licensed for royalty-free use under the Attribution 2.5 Generic License. It was obtained from the web site http://www.everystockphoto.com/.
Comments to:
Gammon Software support
Forum RSS feed ( http://www.gammon.com.au/rss/forum.xml )