Chapter 3:
Draw images

In this chapter, we’ll look at how to draw images on the Canvas.

Step 1 - Adding images to assets

Before we can draw images on the Canvas, we need to add those images to our app’s assets. We’ll use these images (available from the free mining icon set):

In the Assets section of the IDE, click the three dots and choose Add Asset. Do this for each of the four icons.Here’s a demonstration for one of them:

Uploading the icons to the app as asset files

Uploading the icons to the app as asset files

When you’re done, all four icons should show in the Assets section:

Screenshot of a list of the app's asset files

Step 2 - Loading images

Now we need to load the images so we can draw them.

Images in Anvil are handled as Media objects. We only need to load each Media object once in order to be able to draw it on the Canvas in as many locations as we want. We’ll build a dictionary of these objects in the __init__ function:

    def __init__(self, **properties):
        # Set Form properties and Data Bindings.
        self.init_components(**properties)

        self.images = {
            'gold': URLMedia('_/theme/gold_bar.png'),
            'silver': URLMedia('_/theme/silver_bar.png'),
            'iron': URLMedia('_/theme/iron_bar.png'),
            'rock': URLMedia('_/theme/rock.png'),
        }

        ...     

To get the URL to pass into the URLMedia object, use the Copy Asset Path option next to the image in the Assets section:

Copying and pasting the path for an asset file

Copying and pasting the path for an asset file

Step 3 - Drawing images in the reset handler

Since we’ll now be drawing the new images to the Canvas, we can remove the functionality for drawing squares and circles. We should remove the add_square and add_circle buttons from the UI and change self.model in the init to be an empty list for now.

Now we must change both our model and our reset handler to draw the images on the Canvas. For the model, we need to both know that we are drawing an image, and which image we are drawing. We’ll use the type field of the model to tell us which image needs to be drawn:

        self.model = [
            {'type': 'gold', 'x': 100, 'y': 100}
        ]

Now the reset handler needs to handle drawing the image. We use the Canvas’s draw_image function to draw the image at the desired coordinates when we find that the type is one of our images. We use continue after drawing the image since none of the code following that is needed for images.

    def canvas_1_reset(self, **event_args):
        # Adjust these coordinates if you want the drawing area to not be centered
        self.canvas_offset = (self.canvas_1.get_width() - self.canvas_size)/2
        self.canvas_1.translate(self.canvas_offset, 0)

        # Restrict drawing to the section that we want visible
        self.canvas_1.begin_path()
        self.canvas_1.move_to(0, 0)
        self.canvas_1.line_to(self.canvas_size, 0)
        self.canvas_1.line_to(self.canvas_size,self.canvas_size)
        self.canvas_1.line_to(0, self.canvas_size)
        self.canvas_1.close_path()
        self.canvas_1.clip()
        
        # Draw the shapes. Some may be out of the clipping area and won't be visible.
        for shape in self.model:
            if shape['type'] in self.images:
                self.canvas_1.draw_image(self.images[shape['type']], shape['x'], shape['y'])

Many times when you are drawing images, you want to draw them in fixed grid positions. This requires converting row and column coordinates to x and y coordinates. A key point to remember is that rows correspond to y coordinates, and columns correspond to x coordinates.

We’ll change our model to draw images on row and column boundaries. We’ll also remember that our image sizes are 32x32. Here’s an example of creating an image at row 5 and column 10.

        self.IMAGE_SIZE = 32
        self.model = [
            {'type': 'gold', 'x': 10 * self.IMAGE_SIZE, 'y': 5 * self.IMAGE_SIZE},
        ]

You can now run the app and see the image drawn at the correct row and column. Changing the row and column in the model will change where the image is drawn:

The gold icon is drawn on the Canvas

The gold icon is drawn on the Canvas

Now that we have the ability to draw image objects, let’s modify our code to randomly create a grid of images in our model.

    def __init__(self, **properties):
        # Set Form properties and Data Bindings.
        self.init_components(**properties)

        self.images = {
            'gold': URLMedia('_/theme/gold_bar.png'),
            'silver': URLMedia('_/theme/silver_bar.png'),
            'iron': URLMedia('_/theme/iron_bar.png'),
            'rock': URLMedia('_/theme/rock.png'),
        }
        
        self.IMAGE_SIZE = 32
        # Start with an empty model
        self.model = []

        self.GRID_COLS = 10
        self.GRID_ROWS = 10
        self.image_types = list(self.images.keys())
        self.num_images = len(self.image_types)
        
        # Create a random object at each grid location
        for row in range(self.GRID_ROWS):
            for col in range(self.GRID_COLS):
                self.model.append({
                    'type': self.image_types[random.randint(0, self.num_images-1)],
                    'y': row * self.IMAGE_SIZE,
                    'x': col * self.IMAGE_SIZE
                })

        self.canvas_size = self.GRID_COLS * self.IMAGE_SIZE
        self.canvas_1.height = self.canvas_size
        self.canvas_1.reset_context() 

Some important changes from the old version of the __init__ function are that we’re now basing the size of the Canvas on the desired number of rows and columns. We also create an empty model list and then append a random image to each location in the grid.

Since the reset handler function already draws based on the model, when we fill out the model the images automatically get drawn in the right spots. When you run this you’ll see a small delay the first time each image is drawn. That’s the delay from Anvil fetching the image from the server. You can avoid this delay by pre-drawing each image once outside the clipping area.

The mining icons randomly drawn on the Canvas

The mining icons randomly drawn on the Canvas

We’ve now learned how to draw images on the Canvas. Next, we’ll look at how to handle mouse clicks on the Canvas.

Chapter complete

Congratulations, you've completed this chapter!