Using the camera

Almost every modern computing device has a camera of some sort. In this tutorial, we’ll write new application that is able to request access to this camera, take a photograph, and then display that photograph in the app. new application that uses your device’s camera.

This tutorial won’t work on all platforms!

Unfortunately, at present, this tutorial will only work on macOS and Android.

Although iPhones all have cameras, the iOS Simulator doesn’t have a working camera. Windows and Linux devices also have cameras, but Toga doesn’t currently have the ability to access the camera on these platforms.

The code presented here will run on Windows or Linux; but it will raise an error when you try to take a photograph.

The code will work if it is run on an actual iOS device, but will fail to take a photograph if deployed to the iOS simulator.

Start a new project

For this tutorial, we’re not going to build onto the application from the core tutorial - we’re going to start a fresh project. You can use the same virtual environment you used in the first project; but we need to re-run the briefcase new wizard.

Change back to the directory that contains the helloworld project folder, and start a new project named “Hello Camera”:

(beeware-venv) $ cd ..
(beeware-venv) $ briefcase new
...
[hellocamera] Generated new application 'Hello Camera'

To run your application, type:

    $ cd hellocamera
    $ briefcase dev

(beeware-venv) $ cd hellocamera

Add code to take a photo

The wizard has generated a new empty Toga project. We can now add the code to take and display a photograph. Edit the app.py for the new application so that it has the following content:

import toga
from toga.style import Pack
from toga.style.pack import COLUMN, ROW


class HelloCamera(toga.App):
    def startup(self):
        main_box = toga.Box()

        self.photo = toga.ImageView(style=Pack(height=300, padding=5))
        camera_button = toga.Button(
            "Take photo",
            on_press=self.take_photo,
            style=Pack(padding=5)
        )

        main_box.add(self.photo)
        main_box.add(camera_button)

        self.main_window = toga.MainWindow(title=self.formal_name)
        self.main_window.content = main_box
        self.main_window.show()

    async def take_photo(self, widget, **kwargs):
        try:
            if not self.camera.has_permission:
                await self.camera.request_permission()

            image = await self.camera.take_photo()
            if image:
                self.photo.image = image
        except NotImplementedError:
            await self.main_window.info_dialog(
                "Oh no!",
                "The Camera API is not implemented on this platform",
            )
        except PermissionError:
            await self.main_window.info_dialog(
                "Oh no!",
                "You have not granted permission to take photos",
            )


def main():
    return HelloCamera()

This code has two changes over the default app that is generated by Briefcase. These additions are highlighted in yellow:

  1. The first highlighted code block (in the startup() method) adds the two widgets needed to control the camera: an ImageView to display a photo; and a Button to trigger the camera.

  2. The second highlighted code block (the take_photo() method) defines the event handler when the button is pressed. This handler first confirms if the application has permission to take a photo; if permission doesn’t exist, it is requested. Then, a photo is taken. The request for permission and the request to take a photo are both asynchronous requests, so they require the use of await; while the app is waiting for the user to confirm permissions or take the photo, the app’s event loop can continue in the background.

If the camera successfully takes a photo, it will return an Image object that can be assigned as the content of the ImageView. If the photo request was canceled by the user, the self.camera.take_photo() call will return None, and the result can be ignored. If the user doesn’t grant permission to use the camera, or the camera isn’t implemented on the current platform, an error will be raised, and a dialog will be shown to the user.

Adding device permissions

Part of this code we’ve just added asks for permission to use the camera. This is a common feature of modern app platforms - you can’t access hardware features without explicitly asking the user’s permission first.

However, this request comes in two parts. The first is in the code we’ve just seen; but before the app can ask for permissions, it needs to declare the permissions it is going to ask for.

The permissions required by each platform are slightly different, but Briefcase has a cross-platform representation for many common hardware permissions. In the [tool.briefcase.app.helloworld] configuration section of your app’s pyproject.toml file, add the following (just above the sources declaration):

permission.camera = "App will take mugshots."

This declares that your app needs to access the camera, and provides a short description why the camera is required. This description is needed on some platforms (most notably macOS and iOS) and will be displayed to the user as a additional information when the permission dialog is presented.

We can now generate and run the app:

(beeware-venv)$ briefcase create
(beeware-venv)$ briefcase build
(beeware-venv)$ briefcase run

When the app runs, you’ll be presented with a button. Press the button, and the platform’s default camera dialog will be displayed. Take a photo; the camera dialog will disappear, and the photo will be displayed on in the app, just above the button. You could then take another photo; this will replace the first photo.

Adding more permissions

Permissions are declared in the files that are generated during the original call to briefcase create. Unfortunately, Briefcase can’t update these files once they’ve been initially generated; so if you want to add a new permission to your app, or modify existing permissions, you’ll need to re-create the app. You can do this by re-running briefcase create; this will warn you that the existing app will be overwritten, and then regenerate the application with the new permissions.