Objects LW Motions 3D Programming Home

Creating Images

Obviously there are lots of ways to make a digital image that don't require writing your own program. But if you're visualizing 2D data, or converting terrain elevations into displacement or bump maps, or you need to create a gradient that follows an unusual path through RGB space, writing a program can be easier than trying to trick off-the-shelf software into creating the image.

This article introduces a set of three qbasic programs that create images in the TGA format originated by Truevision, Inc.. These programs write uncompressed 8-bit grayscale, 8-bit indexed color, and 24-bit images that can be loaded directly into most graphics software.

A BASIC Program that Creates a TGA Image

The original TGA format is remarkably simple, an 18-byte header (in Windows little-endian byte order) followed by pixel bytes, along with a palette for indexed color images. The pixel data can be compressed using run-length encoding, but it doesn't have to be. The following qbasic program creates a small TGA image containing a rainbow gradient. qbasic keywords appear in red, comments in green.

   ' tga.bas
   ' Ernie Wright 24 Aug 97

qbasic allows you to implicitly declare variable types according to the first letter of every variable's name. This is an idea borrowed from FORTRAN, and we'll use it here to tell qbasic that by default we want 16-bit integers. We could also choose to explicitly declare each variable, or use type suffix characters (%, &, !, #, $) in variable names to specify their types.

   DEFINT A-Z

TGA files begin with an 18-byte header that describes the pixel information in the file. qbasic supports user-defined record types, which we'll use here to define both the header and a 24-bit RGB color specifier.

   TYPE TGAHDR
      idlen     AS STRING * 1          'size of image id field
      cmaptype  AS STRING * 1          'palette type
      imtype    AS STRING * 1          'image type
      cmapstart AS INTEGER             'lowest color index
      cmaplen   AS INTEGER             'number of palette entries
      cmapdepth AS STRING * 1          'bit depth of palette RGB
      xorigin   AS INTEGER             'image origin x
      yorigin   AS INTEGER             'image origin y
      imwidth   AS INTEGER             'image width in pixels
      imheight  AS INTEGER             'image height in pixels
      pixdepth  AS STRING * 1          'bits per pixel
      imdescrip AS STRING * 1          'image descriptor
   END TYPE

   TYPE TGARGB
      blue  AS STRING * 1
      green AS STRING * 1
      red   AS STRING * 1
   END TYPE

qbasic also supports subroutines. This is a forward declaration for a routine that converts between real-valued HSV and byte-valued RGB color spaces.


   DECLARE SUB hsv2rgb (h AS SINGLE, s AS SINGLE, v AS SINGLE, c AS TGARGB)
   DIM hdr  AS TGAHDR
   DIM c    AS TGARGB
   DIM byte AS STRING * 1

   w = 256
   h = 24
   filename$ = "index.tga"

Up to this point, the programs for writing grayscale, indexed color, and 24-bit TGA files are the same. Now we have to fill in the TGA header with information that describes the image, so the three programs naturally must now diverge. We'll follow the indexed color example, since it's a bit more complicated than the other two. The qbasic CHR$ function is used to convert an integer to a single-byte value. The &H prefix denotes that the digits are hexadecimal (base 16).

   hdr.imwidth = w                     'image width
   hdr.imheight = h                    'image height
   hdr.imdescrip = CHR$(&H20)          'origin is upper left
   hdr.cmaptype = CHR$(1)              'has palette
   hdr.imtype = CHR$(1)                'indexed color
   hdr.cmaplen = 256                   '256 palette entries
   hdr.cmapdepth = CHR$(24)            '24-bit palette
   hdr.pixdepth = CHR$(8)              '8 bits per pixel

This line opens an output file in binary mode. qbasic uses the PUT statement to write to binary files, rather than PRINT.

   OPEN filename$ FOR BINARY ACCESS WRITE AS 1

All TGA files begin with the 18-byte header. In grayscale and 24-bit TGAs, the header is ordinarily followed immediately by pixel values. Indexed color TGAs add a palette, the format and size of which is described in the cmaplen and cmaptype fields of the header. Here we create a rainbow palette with 256 entries.

   PUT #1, , hdr

   FOR i = 0 TO 255
      CALL hsv2rgb(359! * i / 255!, 1!, 1!, c)
      PUT #1, , c
   NEXT

All that's left to do is to write the pixel data. For indexed color images, each pixel is represented by a 1-byte palette index. In grayscale images, the pixel data is a byte containing the gray level. 24-bit color images use a TGARGB record for each pixel.

   FOR j = 0 TO 23
      FOR i = 0 TO 255
         byte = CHR$(i)
         PUT #1, , byte
      NEXT
   NEXT

We're done.

CLOSE 1

© Ernie Wright