Glimmr Canvas-Based Drawing

version 2/101030 by Erik Temple

  • Home page
  • Beginning
  • Previous
  • Next

  • Section: Image-maps

    An image-map is similar to a bitmap in that the author defines a regular grid for graphical display. However, rather than each cell of the grid displaying a rectangular area of color, it draws an image from a PNG or JPEG file. This could be used for drawing tile-based maps (as in RPG games), sliding-block puzzles, graphical user interfaces, or a number of other things.

    There are two types of image-maps:

        Tileset image-map - Specified using a list of lists of numbers called the "tile-array", where each row is given as a list of numbers. These numbers are keyed to an "associated tileset", basically a translation-table that converts the digits to figure names. The tileset allows for one set of images to be changed out for another with a single line; for example, a map could change from day to night simply by changing the image-map's associated tileset. Tilesets also allow image-map lists to be relatively human-readable. For example, the following might represent a long hallway with a door on the left:

                { 11, 11, 11, 11, 11, 11, 11 },
                { 05, 00, 00, 00, 00, 00, 00 },
                { 05, 00, 00, 00, 00, 00, 00 },
                { 11, 11, 11, 11, 11, 11, 11 }

        Direct image-map - Specified using a list of lists of figure names called the "figure-array", where each row is given as a list of numbers. When typed out, the figure-array will be more difficult to read than a tileset-array, at least when there are more than a few cells in the grid. The figure-array of a simple six-cell grid might read like this:

                { Figure of Red, Figure of Blue },
                { Figure of Blue, Figure of Red },
                { Figure of Red, Figure of Blue }

        When adding graphic hyperlinks to individual cells, we will need to include a separate tile that keys commands to figure names (in a tileset image-map, this is handled by the tileset's translation-table).

    There are times when we may not want to include any graphic in a given cell. For a tileset image-map, we can simply provide 0 as the value for that cell, while for a direct image-map, we specify Figure of Null. If we specify a "background tint" (a glulx color value) for the image-map element, a rectangular zone of this color will be drawn behind the image-map, and will "show through" any empty cells, as well as through the transparent areas of PNG graphics included in the image-map.

    Remember that the tile- or figure-array is a list of lists. We supply the standard set of list braces, and then we supply one list for each row within those braces, each row's list also having its own braces, e.g. here's a simple tile-array with 4 rows:

        { {1, 0, 1}, {0, 1, 0}, {1, 0, 1}, {0, 1, 0} }.

    We must also specify the width and height of the tiles in an image-map. For a direct image-map, we set the "tile-width" and "tile-height" properties to specify these dimensions. The associated tileset should define these for tileset image-maps, but we can also override the tileset settings by specifying the "tile-width-override" and "tile-height-override" properties of the image-map element.

    If we know the total width or total height we want an image-map to fit into, we can also automate the specification of these properties using these phrases:

        fit my image-map to a total width of 120 canvas units;
        fit my image-map to a total height of 150 canvas units;

    Be sure to invoke these before drawing the image-map for the first time. The extension will attempt to get as close as possible to these measurements, but may not be able to match exactly; for best results, make your final measurement evenly divisible by the number of tiles in the row or column (120 canvas units divided by 10 cells = 12 units each). Use Glimmr debugging (see below) to see the final dimension selected.

    Like other graphic elements, image-maps can be "hyperlinked" to respond to mouse input. However, unlike other elements, there are two ways in which they can be so linked. As with other elements, the entire rectangular area of the image-map can be given a single link, by assigning the "linked replacement-command" property and changing the "graphlink status" to g-active.

    But we can also link each tile individually. This is done by making the "tiled graphlink status" of the image-map g-active. We must also specify an array of commands the same size and shape as our image array, the "linked command array" (again, this is a list of lists, this time a list of lists of texts). Luckily, this can be done automatically if we first assign a command to each tile or figure used in the image-map. With a tileset image-map, we can do this by adding a text column called "linked command" to the tileset's translation-table, e.g.:

        Table of My Tileset
      Char  Tile  Linked Command  
      number  figure-name  text  

    For direct image-maps, which do not have a translation table, we will need to supply a new table, with figure names in one column and commands in a second column. For example:

        Table of My Image Maps Commands
      Figure  Linked Command  
      figure name  text  

    After we have set up these correspondences between image tiles and commands, we can use one of the following phrases to build the hyperlink list:

        For tileset image-maps:
            construct graphic hyperlinks array for my image-map.

        For direct image-maps:
            construct graphic hyperlinks array for my image-map using the Table of My Image Maps Commands.

    These phrases should be deployed immediately after changing the image array of an image-map, but before drawing it. This will guarantee that the hyperlink zones applied to the screen correspond to the figures shown in the image-map. (We can also write our own routines to populate the linked command array.)

    Individual links can work alongside the global hyperlink for the entire image-map: wherever an individual tile is not linked--that is, where no tile is drawn to the screen, or when the linked command specified is empty ("")--the image-map's global hyperlink will be triggered. Otherwise, the individual links override the global link.

    We can refer to image-maps by their internal coordinates in limited ways. The internal coordinates are expressed using curly braces like canvas coordinates, but they refer to the column and row of a cell in the map. For example, refer back to our "hallway" map:

        { 11, 11, 11, 11, 11, 11, 11,
         05, 00, 00, 00, 00, 00, 00,
         05, 00, 00, 00, 00, 00, 00,
         11, 11, 11, 11, 11, 11, 11 }

    If we want to refer to the 05 on the left side of the second row, we would point to {1, 2}. We can change the image array of an image-map using internal coordinates like so:

        place tile 17 at coordinate {1, 2} of my image-map;
        place Figure of Open Door at coordinate {1, 2} of my image-map;

    (The former is for tileset image-maps, the latter for direct image-maps.) Changes, of course, are not visible until the next time we update the window.

    We can also convert between the internal coordinates of an image-map and the coordinates of the canvas. This allows us to find out where on the canvas a given cell of an image-map is located (the coordinate returned represents the upper left corner of the tile). To get the canvas coordinates of an internal coordinate pair, we refer to:

        the canvas coordinate equivalent of {5, 5} in the internal coordinates of my image-map;

    To get the internal coordinates corresponding to a given canvas coordinate:

        the equivalent of canvas coordinates {150, 200} in the internal coordinates of my image-map;

    So, for example, we can place other graphic elements (e.g. sprites) as if they were part of the image-map:

        Element display rule for Mario:
            now the origin of the Mario is the canvas coordinate equivalent of {5, 5} in the internal coordinates of my image-map;

    We can also convert between the internal coordinates of an image-map and screen coordinates. This can only be done after the element scaling rules for the image-map have been followed (otherwise the extension will not know what the conversion factor should be). We use the following phrases:

        screen coordinate equivalent of {5, 5} in the internal coordinates of my image-map;
        equivalent of screen coordinates {150, 200} in the coordinates of my image-map;

    There are a few special debugging capabilities available for image-maps. To see which tiles of an image-map have individual graphic hyperlinks, declare this use option:

        Use image-map graphlink preview.

    Now any image-map tile that has an individual hyperlink (see above) will be outlined in gray. The preview color can be changed like so:

        The graphlink preview color is g-Blue.

    If the graphlink preview color is set to null (that is, g-PlaceNullCol), no box will be visible.

    We can "dump" the data in an image-map to the screen by typing "image-map <the name of the image-map to dump>" in-game. If we are running the game in the IDE, this will print the tile-array or figure-array of the image-map to the main window. Note that if the command indicates that there is no such thing as the image-map you're trying to dump, you will need to declare your image-map to be "publically-named". (GCD automatically tries to mark all g-elements as publically-named for a debugging build, but it is possible to evade this control.) Example:

        My image-map is a tileset image-map. The origin is {10, 10}. The tile-array is {1, 2, 1, 2}.

        Section - Debugging (not for release)

        My image-map is publically-named.

    Note that image-maps may run particularly slowly in the IDE. Test them in an outside interpreter to gauge true performance.