# Capstone: a drawing application This chapter assembles the guide into a program you run: a small drawing application. Click and drag to paint, pick a color, clear the canvas, and save the result to a PNG. It is built on [Prima](gui-toolkits.md), the maintained self-contained toolkit from the GUI chapter, because it gives us a window, mouse events, and an offscreen surface that saves itself, all without an external toolkit to install. The design is the one every paint program uses. There is an **offscreen image** that holds the actual painting, and a **window** that displays it. The mouse draws onto the offscreen image; the window’s paint handler just copies that image to the screen. Separating the two means the painting survives window redraws (resize, occlusion) for free, and saving is trivial because the real drawing already lives in an image object. ## The model: an offscreen image The painting itself is a `Prima::Image`, drawn on with the same primitives you would use on a window canvas. We start it white: ```perl use strict; use warnings; use Prima qw(Application Buttons); my $W = 640; my $H = 480; # the real drawing lives here, offscreen my $canvas = Prima::Image->new(width => $W, height => $H, type => im::RGB); $canvas->begin_paint; $canvas->color(cl::White); $canvas->bar(0, 0, $W, $H); # bar is Prima's filled rectangle $canvas->end_paint; my $brush_color = cl::Black; # current paint color my $last; # last mouse point while dragging ``` `begin_paint` and `end_paint` bracket any drawing onto the offscreen image. `bar` fills a rectangle (recall from the [GUI chapter](gui-toolkits.md) that Prima’s filled rectangle is `bar`, not `rectangle`). We hold the current brush color and the previous drag point as ordinary Perl variables. ## The window: display and input The window’s job is to show `$canvas` and to translate mouse motion into paint strokes. Its `onPaint` handler copies the offscreen image to the screen with `put_image`; its mouse handlers draw onto the offscreen image and ask the window to repaint. ```perl my $window = Prima::MainWindow->new( text => 'pperl paint', size => [$W, $H + 40], # extra height for the buttons onPaint => sub { my ($self, $canvas_widget) = @_; $canvas_widget->put_image(0, 40, $canvas); # blit the drawing }, onMouseDown => sub { my ($self, $btn, $mod, $x, $y) = @_; $last = [$x, $y - 40]; # translate for the button strip }, onMouseMove => sub { my ($self, $mod, $x, $y) = @_; return unless $last; # only while a button is held my @pt = ($x, $y - 40); stroke($last, \@pt); $last = \@pt; $self->repaint; }, onMouseUp => sub { $last = undef; }, ); ``` The `y - 40` offset keeps a strip at the bottom of the window for the buttons; the drawing area is everything above it. `onMouseMove` fires continuously, so we draw a short line segment from the previous point to the current one and remember the current point. Dragging fast still produces a connected stroke because we always join consecutive points rather than dotting individual pixels. ## The brush: drawing onto the offscreen image `stroke` draws one segment of the brush onto the offscreen `$canvas`. Because the painting is a normal `Prima::Image`, we use the same drawing calls as anywhere else, bracketed by `begin_paint` and `end_paint`: ```perl sub stroke { my ($from, $to) = @_; $canvas->begin_paint; $canvas->color($brush_color); $canvas->lineWidth(3); $canvas->line($from->[0], $from->[1], $to->[0], $to->[1]); # round the joints so fast strokes stay smooth $canvas->fill_ellipse($to->[0], $to->[1], 3, 3); $canvas->end_paint; } ``` Setting `lineWidth` before the `line` gives a thicker stroke; the small `fill_ellipse` at the endpoint rounds off the joins so a fast zig-zag does not show gaps at the corners. This is the anti-aliasing-adjacent detail from the [algorithms chapter](raster-algorithms.md) showing up in practice: thick lines meeting at an angle leave a notch unless you round the joint. ## Controls: color, clear, and save A row of buttons along the bottom gives the tool its verbs. Each is a `Prima::Button` with an `onClick`. Color buttons set `$brush_color`; clear repaints the offscreen image white; save writes it to disk. ```perl my @palette = ( ['Black', cl::Black], ['Red', cl::Red], ['Green', cl::Green], ['Blue', cl::Blue], ); my $x = 5; for my $entry (@palette) { my ($label, $color) = @$entry; $window->insert(Button => text => $label, origin => [$x, 5], size => [60, 30], onClick => sub { $brush_color = $color }, ); $x += 65; } $window->insert(Button => text => 'Clear', origin => [$x, 5], size => [60, 30], onClick => sub { $canvas->begin_paint; $canvas->color(cl::White); $canvas->bar(0, 0, $W, $H); $canvas->end_paint; $window->repaint; }, ); $x += 65; $window->insert(Button => text => 'Save', origin => [$x, 5], size => [60, 30], onClick => sub { $canvas->save('painting.png') and print "saved painting.png\n"; }, ); Prima->run; ``` `insert(Button => ...)` adds a child widget to the window. `$canvas->save` writes the offscreen image to a PNG, format inferred from the extension, returning true on success. Because the drawing has lived in an image object the whole time, saving is a single call with nothing to reconstruct. ## Running it Assemble the pieces above into one file and run it with `pperl`. A window opens with a white canvas and a button strip; drag to paint, click a color to switch, Clear to reset, Save to write `painting.png`. Every piece traces back through the guide: the offscreen image and its primitives from the [GUI chapter](gui-toolkits.md), the stroke-joining from the [algorithms chapter](raster-algorithms.md), the color model from [foundations](foundations.md), and the save path that is one method because the painting was always a real image. ## Where the FFI thread would enter This capstone stays on the maintained CPAN stack, and deliberately so: it is meant to run today, unmodified. The [FFI thread](ffi-graphics-primer.md) enters when you outgrow what the stack offers, for instance wanting a C brush-engine library with no Perl binding for pressure-sensitive strokes. That is reached exactly as the [Cairo showcase](ffi-cairo-showcase.md) showed: a curated binding presenting the C library as Perl objects. For a paint program, Prima’s own primitives are more than enough, and the honest engineering choice is to use them. The [next capstone](capstone-2d-game.md) is where the FFI frontier genuinely comes into play, because realtime windowing and events push past what the current bindings reach.