In order to represent images, we'll need to handle colors. We keep a 0-255 value for the red, green, and blue component of the current color.
implement colors +≡variable red variable green variable blue
We'll often use it to setup colors specified as a triple. Often this triple will be floating point in the 0-1 range.
implement colors +≡: rgb ( r g b -- ) blue ! green ! red ! ; : f>primary ( f -- n ) 255e f* f>s 0 max 255 min ; : rgbf ( rf gf bf -- ) f>primary f>primary f>primary rgb ;
We can then define some important common colors.
implement colors +≡: black ( -- ) 0 0 0 rgb ; : white ( -- ) 255 255 255 rgb ;
And some common color sets.
implement colors +≡: gray ( n -- ) dup dup rgb ;
We will need to implement images. For now, a single global image will suffice. We'll store it in 4 byte per pixel form (with the alpha component unused for now). These fields will be needed.
implement images +≡variable image-width variable image-height variable image-data
In manipulating images we'll want to be able to reference the total size of the image data.
implement images +≡: image-data-size ( -- n ) image-width @ image-height @ * 4 * ;
We will (re-)allocate image data on setup.
implement images +≡: image-pick-size ( w h -- ) image-height ! image-width ! ; : image-free-old image-data @ dup if free 0= assert else drop then ; : image-allocate image-data-size allocate 0= assert image-data ! ; : image-clear image-data @ image-data-size 0 fill ; : image-setup ( w h -- ) image-pick-size image-free-old image-allocate image-clear ;
The most basic operation will be to plot to a given (x, y) coordinate with the current color.
implement images +≡implement colors : image-xy ( x y -- a ) image-width @ * + 4 * image-data @ + ; : plot ( x y -- ) image-xy red @ over c! green @ over 1+ c! blue @ over 2 + c! 0 swap 3 + c! ;
We will want to output the current image to a windows BMP file. We will assume we're only writing one BMP at time and keep a global to its file handle.
writing bmp files +≡variable bmp-file
Opening and closing the file based on an atom name will be useful.
writing bmp files +≡: bmp-begin ( A -- ) atom-string@ w/o bin create-file 0= assert bmp-file ! ; : bmp-end ( -- ) bmp-file @ close-file 0= assert ;
We will halt on failures to write, so we should centralize that.
writing bmp files +≡: bmp-write ( $ -- ) bmp-file @ write-file 0= assert ;
Additionally writing out little endian bytes, words, and double words will be needed.
writing bmp files +≡: bmp-byte ( b -- ) here c! here 1 bmp-write ; : bmp-word ( w -- ) dup 255 and bmp-byte 8 rshift 255 and bmp-byte ; : bmp-dword ( d -- ) dup 65535 and bmp-word 16 rshift 65535 and bmp-word ;
BMP files have two headers, the main BMP header and a DIB (device independent header. We'll need to talk about the size of them.
writing bmp files +≡3 2 * 2 4 * + constant bmp-header-size 10 4 * constant dib-header-size
We can then implement writing the bmp file. Starting with the BMP header.
writing bmp files +≡: bmp-save ( A -- ) bmp-begin \ BMP header s" BM" bmp-write bmp-header-size dib-header-size + image-data-size + bmp-dword \ size of bmp file in bytes 0 bmp-word \ unused 0 bmp-word \ unused bmp-header-size dib-header-size + bmp-dword \ offset to start of bitmap image data
Then the DIB header.
writing bmp files +≡\ DIB header dib-header-size bmp-dword \ size of header in bytes image-width @ bmp-dword \ width image-height @ bmp-dword \ height 1 bmp-word \ color planes 32 bmp-word \ bits per pixel 0 bmp-dword \ BI_RGB (uncompressed) image-data-size bmp-dword \ pixel data size 0 bmp-dword \ horizontal pixels per meter 0 bmp-dword \ vertical pixels per meter 0 bmp-dword \ colors in color palette 0 bmp-dword \ important colors in palette
Then the image data and done.
writing bmp files +≡\ Image data image-data @ image-data-size bmp-write bmp-end ;