Ολοκληρωτικό έργο: μια εφαρμογή σχεδίασης#

Αυτό το κεφάλαιο συναρμολογεί τον οδηγό σε ένα πρόγραμμα που εκτελείτε: μια μικρή εφαρμογή σχεδίασης. Κάντε κλικ και σύρετε για να ζωγραφίσετε, διαλέξτε ένα χρώμα, καθαρίστε τον καμβά, και αποθηκεύστε το αποτέλεσμα σε ένα PNG. Είναι χτισμένη πάνω στο Prima, τη συντηρούμενη αυτοτελή εργαλειοθήκη από το κεφάλαιο του GUI, γιατί μας δίνει ένα παράθυρο, συμβάντα ποντικιού, και μια επιφάνεια εκτός οθόνης που αποθηκεύει τον εαυτό της, όλα χωρίς μια εξωτερική εργαλειοθήκη να εγκαταστήσουμε.

Ο σχεδιασμός είναι αυτός που χρησιμοποιεί κάθε πρόγραμμα ζωγραφικής. Υπάρχει μια εικόνα εκτός οθόνης που κρατά την πραγματική ζωγραφιά, και ένα παράθυρο που την εμφανίζει. Το ποντίκι σχεδιάζει πάνω στην εικόνα εκτός οθόνης· ο χειριστής βαφής του παραθύρου απλώς αντιγράφει αυτή την εικόνα στην οθόνη. Ο διαχωρισμός των δύο σημαίνει ότι η ζωγραφιά επιβιώνει από τις επανασχεδιάσεις του παραθύρου (αλλαγή μεγέθους, επικάλυψη) δωρεάν, και η αποθήκευση είναι τετριμμένη γιατί το πραγματικό σχέδιο ζει ήδη σε ένα αντικείμενο εικόνας.

Το μοντέλο: μια εικόνα εκτός οθόνης#

Η ίδια η ζωγραφιά είναι ένα Prima::Image, πάνω στο οποίο σχεδιάζουμε με τα ίδια πρωτογενή που θα χρησιμοποιούσατε σε έναν καμβά παραθύρου. Το ξεκινάμε λευκό:

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 και end_paint περικλείουν κάθε σχεδίαση πάνω στην εικόνα εκτός οθόνης. Το bar γεμίζει ένα ορθογώνιο (θυμηθείτε από το κεφάλαιο του GUI ότι το γεμάτο ορθογώνιο του Prima είναι το bar, όχι το rectangle). Κρατάμε το τρέχον χρώμα του πινέλου και το προηγούμενο σημείο συρσίματος ως συνηθισμένες μεταβλητές της Perl.

Το παράθυρο: εμφάνιση και είσοδος#

Η δουλειά του παραθύρου είναι να δείχνει το $canvas και να μεταφράζει την κίνηση του ποντικιού σε πινελιές. Ο χειριστής onPaint του αντιγράφει την εικόνα εκτός οθόνης στην οθόνη με το put_image· οι χειριστές ποντικιού του σχεδιάζουν πάνω στην εικόνα εκτός οθόνης και ζητούν από το παράθυρο να ξαναβαφτεί.

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; },
);

Η μετατόπιση y - 40 κρατά μια λωρίδα στο κάτω μέρος του παραθύρου για τα κουμπιά· η περιοχή σχεδίασης είναι όλα όσα βρίσκονται πάνω από αυτήν. Το onMouseMove πυροδοτείται συνεχώς, οπότε σχεδιάζουμε ένα σύντομο ευθύγραμμο τμήμα από το προηγούμενο σημείο ως το τρέχον και θυμόμαστε το τρέχον σημείο. Το γρήγορο σύρσιμο παράγει και πάλι μια συνεχή πινελιά γιατί πάντα ενώνουμε διαδοχικά σημεία αντί να στίζουμε μεμονωμένα pixel.

Το πινέλο: σχεδίαση πάνω στην εικόνα εκτός οθόνης#

Το stroke σχεδιάζει ένα τμήμα του πινέλου πάνω στο εκτός οθόνης $canvas. Επειδή η ζωγραφιά είναι ένα κανονικό Prima::Image, χρησιμοποιούμε τις ίδιες κλήσεις σχεδίασης όπως οπουδήποτε αλλού, περικλεισμένες από τα begin_paint και end_paint:

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;
}

Ο ορισμός του lineWidth πριν από το line δίνει μια παχύτερη πινελιά· το μικρό fill_ellipse στο άκρο στρογγυλεύει τις ενώσεις ώστε ένα γρήγορο ζιγκ-ζαγκ να μη δείχνει κενά στις γωνίες. Αυτή είναι η λεπτομέρεια που γειτονεύει με την εξομάλυνση από το κεφάλαιο των αλγορίθμων να εμφανίζεται στην πράξη: οι παχιές γραμμές που συναντιούνται σε γωνία αφήνουν μια εγκοπή εκτός αν στρογγυλέψετε την ένωση.

Χειριστήρια: χρώμα, καθαρισμός και αποθήκευση#

Μια σειρά κουμπιών κατά μήκος του κάτω μέρους δίνει στο εργαλείο τα ρήματά του. Το καθένα είναι ένα Prima::Button με ένα onClick. Τα κουμπιά χρώματος ορίζουν το $brush_color· το clear ξαναβάφει την εικόνα εκτός οθόνης λευκή· το save τη γράφει στον δίσκο.

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 => ...) προσθέτει ένα widget-παιδί στο παράθυρο. Το $canvas->save γράφει την εικόνα εκτός οθόνης σε ένα PNG, με τη μορφή να συμπεραίνεται από την επέκταση, επιστρέφοντας αληθές σε επιτυχία. Επειδή το σχέδιο ζει σε ένα αντικείμενο εικόνας όλη την ώρα, η αποθήκευση είναι μία μόνο κλήση χωρίς τίποτα να ανακατασκευαστεί.

Εκτελώντας την#

Συναρμολογήστε τα παραπάνω κομμάτια σε ένα αρχείο και εκτελέστε το με το pperl. Ανοίγει ένα παράθυρο με έναν λευκό καμβά και μια λωρίδα κουμπιών· σύρετε για να ζωγραφίσετε, κάντε κλικ σε ένα χρώμα για να αλλάξετε, Clear για επαναφορά, Save για να γράψετε το painting.png. Κάθε κομμάτι ανάγεται πίσω στον οδηγό: η εικόνα εκτός οθόνης και τα πρωτογενή της από το κεφάλαιο του GUI, η ένωση πινελιών από το κεφάλαιο των αλγορίθμων, το μοντέλο χρώματος από τα θεμέλια, και η διαδρομή αποθήκευσης που είναι μία μέθοδος γιατί η ζωγραφιά ήταν πάντα μια πραγματική εικόνα.

Πού θα έμπαινε το νήμα του FFI#

Αυτό το ολοκληρωτικό έργο παραμένει στη συντηρούμενη στοίβα του CPAN, και επίτηδες: προορίζεται να τρέξει σήμερα, χωρίς τροποποιήσεις. Το νήμα του FFI μπαίνει όταν ξεπερνάτε αυτό που προσφέρει η στοίβα, για παράδειγμα όταν θέλετε μια βιβλιοθήκη μηχανής πινέλου σε C χωρίς σύνδεση Perl για πινελιές ευαίσθητες στην πίεση. Αυτό προσεγγίζεται ακριβώς όπως έδειξε η επίδειξη του Cairo: μια επιμελημένη σύνδεση που παρουσιάζει τη βιβλιοθήκη C ως αντικείμενα της Perl. Για ένα πρόγραμμα ζωγραφικής, τα ίδια τα πρωτογενή του Prima είναι υπεραρκετά, και η ειλικρινής μηχανική επιλογή είναι να τα χρησιμοποιήσετε. Το επόμενο ολοκληρωτικό έργο είναι εκεί όπου το σύνορο του FFI έρχεται πραγματικά στο προσκήνιο, γιατί τα παράθυρα και τα συμβάντα πραγματικού χρόνου σπρώχνουν πέρα από αυτά που προσεγγίζουν οι τρέχουσες συνδέσεις.