App-GUI-Harmonograph

 view release on metacpan or  search on metacpan

README  view on Meta::CPAN

    The second button row is for easy mass production of drawings. The three
    text fields are combined the parts of the file path. The first text
    field is naturally the directory where the files get saved. You can
    change it by pushing the *Dir* in front (left) of the text button and
    use the then opening Dir-Dialog to select another directory. The second
    text field holds the base file name, which has to be inserted by
    clicking on in and typing. The third text field is the file number and
    is readonly. That counter increments automatically when a file is
    generated. The complete file path is <dir>+<base
    name>+'_'+<counter>+<file ending>. The file ending is *.ini* for setting
    files and *.jpg* or *.png* or *.svg* for image files. The exact ending
    depends on what is the current configuration set in the image > format
    menu. Lets say your directory is "/home/user/images/h" and the base file
    name is beauty. If there is already a file
    "/home/user/images/h/beauty_4.png" - the program will detect that and
    set the counter to 5. You can play with the settings and than (no matter
    if there is currently a complete drawing or not) push the *Save* button
    to produce a complete drawing into "/home/user/images/h/beauty_5.png".
    If you push the *INI* button you safe the current settings into
    "/home/user/images/h/beauty_5.ini". This file can later be loaded via
    settings menu to restore the current state of all buttons in the tabs.

  Menu
    The upmost menu bar has only three very simple menus. Please not that
    each menu shows which key combination triggers the same command and
    while hovering over an menu item you see a short help text the left
    status bar field.

lib/App/GUI/Harmonograph.pm  view on Meta::CPAN

store files. You may change it manually or deleted it to reset defaults.


=head1 DESCRIPTION

An Harmonograph is an apparatus with several connected pendula,
creating together spiraling pictures :


=for HTML <p>
<img src="https://raw.githubusercontent.com/lichtkind/App-GUI-Harmonograph/main/examples/POD/baum.png"      alt=""  width="300" height="300">
<img src="https://raw.githubusercontent.com/lichtkind/App-GUI-Harmonograph/main/examples/POD/wirbel.jpg"    alt=""  width="300" height="300">
<img src="https://raw.githubusercontent.com/lichtkind/App-GUI-Harmonograph/main/examples/POD/hose.png"      alt=""  width="300" height="300">
<img src="https://raw.githubusercontent.com/lichtkind/App-GUI-Harmonograph/main/examples/POD/wirbel_4.png"  alt=""  width="300" height="300">
<img src="https://raw.githubusercontent.com/lichtkind/App-GUI-Harmonograph/main/examples/POD/wolke.png"     alt=""  width="300" height="300">
<img src="https://raw.githubusercontent.com/lichtkind/App-GUI-Harmonograph/main/examples/POD/df.png"        alt=""  width="300" height="300">
</p>


This is a cybernetic recreation of an Prof. Blackburns invention with
several enhancements:

=over 4

=item *

lib/App/GUI/Harmonograph.pm  view on Meta::CPAN

When browsing the main menu, help texts about the highlighted item
also appears in the status bar. The Menu can be completely navigated with
the keyboard. Just hold Alt and use the direction keys (up, down, left
and right) or the highlighted letters. When holding the Alt key you can
also see which Alt + letter combinations trigger which button.


=head2 Pendulum

=for HTML <p>
<img src="https://raw.githubusercontent.com/lichtkind/App-GUI-Harmonograph/main/examples/POD/Tab_Pendulum.png"    alt=""  width="85%" height="85%">
</p>

Each of the first three tabs contains the settings of two pendula.
The first tab holds the lateral or linear pendula: X (left right movement)
and Y (up and down). The second tab shows settings of the epicycle pendula
E (left right) and F (up down). They also just move in x or y direction,
but they swing not around the center of the image but around the point,
where the pencil would have been. The third tab allows you to tweak
the pendula W (wobble) and R (rotation). W moves the center of the paper
beneath the pencil in a rotating manner whereas R rotates the paper

lib/App/GUI/Harmonograph.pm  view on Meta::CPAN

doesn't move enough. As with reqency, also the amplitude can be damped
over time and this damping can accelerated.

Row eight and nine are exact copies of row three and four, they just
affect the radius / amplitude.


=head2 Functions

=for HTML <p>
<img src="https://raw.githubusercontent.com/lichtkind/App-GUI-Harmonograph/main/examples/POD/Tab_Functions.png"    alt=""  width="85%" height="85%">
</p>

This tab lets you meddle with the equations that compute the mechanics
of a pendulum. Because all ten rows are built the same I will explain only
one. For instance the X pendulum has only influence on the x coordinate
of a dot, it is computed: C<x = radius * cos (time)>. The first selector
allows you to swap out the cosine function. Instead you could get sine,
tangent, cotangent, secant, cosekcant and the hyperbolic twin of the
already mentioned functions.

lib/App/GUI/Harmonograph.pm  view on Meta::CPAN

But if you not sure just hover with the mouse and get the hints.

The very last row is different and contains only one switch that will
determine if W or R pendulum is applied first. Default and what is also
more comprehensible is that R is apllied first. But the arstist in you
might can choose here differently.

=head2 Visual Settings

=for HTML <p>
<img src="https://raw.githubusercontent.com/lichtkind/App-GUI-Harmonograph/main/examples/POD/Tab_Visuals.png"   alt=""  width="85%" height="85%">
</p>

Due to the section headins, this tab is self explanatory. First choos if
you want to paint dots or connect them. Please not that pen thickness of
one is very thin and you might not see any dots in that setting. The pen
style is more of an gimmick. Most useful are solid and dotted lines.
The dot density allows you to juggle two extremes. Low density makes for
fast drawn lines and dots but also pointy curves. So you might want to
raise the value for smooth curves. The fine tuning of dot density makes
only sense if you draw dots, becasue even a slight change can produce

lib/App/GUI/Harmonograph.pm  view on Meta::CPAN

then again forward as long as your chosen painting time permits.
Regulate the  color change speed with the C<Speed> slider. For extra
slow color changes hit the C<Invert> checkbox. Than high speed values
will make it extra slow. The last color flow type is I<"circular">. Here
you go again from color one to the selected last color and from there
directly to color one. This round will repeated as time permits.

=head2 Colors

=for HTML <p>
<img src="https://raw.githubusercontent.com/lichtkind/App-GUI-Harmonograph/main/examples/POD/Tab_Colors.png"   alt=""  width="85%" height="85%">
</p>

This tab is just for choosing the available colors. There are alway ten
colors visible, but colors with a "x" below are currently not in use.
This tab page has five sections which will be explained from top to bottom.

The first section is about loading saving sets of colors, so you don't
have to dial in your favorit arrangements every time. Just choose your
set via the drop down menu or browse there with the arrow buttons.
A preview will be shown below th e selector. Then press load and the

lib/App/GUI/Harmonograph.pm  view on Meta::CPAN


The second button row is for easy mass production of drawings.
The three text fields are combined the parts of the file path.
The first text field is naturally the directory where the files get saved.
You can change it by pushing the I<Dir> in front (left) of the text button
and use the then opening  Dir-Dialog to select another directory.
The second text field holds the base file name, which has to be inserted
by clicking on in and typing. The third text field is the file number and
is readonly. That counter increments automatically when a file is generated.
The complete file path is <dir>+<base name>+'_'+<counter>+<file ending>.
The file ending is I<.ini> for setting files and I<.jpg> or I<.png> or I<.svg>
for image files. The exact ending depends on what is the current configuration
set in the image > format menu. Lets say your directory is
"/home/user/images/h" and the base file name is beauty. If there is already
a file "/home/user/images/h/beauty_4.png" - the program will detect that
and set the counter to 5. You can play with the settings and than (no matter
if there is currently a complete drawing or not) push the I<Save> button
to produce a complete drawing into "/home/user/images/h/beauty_5.png".
If you push the I<INI> button you safe the current settings into
"/home/user/images/h/beauty_5.ini". This file can later be loaded via
settings menu to restore the current state of all buttons in the tabs.


=head2 Menu

The upmost menu bar has only three very simple menus.
Please not that each menu shows which key combination triggers the same
command and while hovering over an menu item you see a short help text

lib/App/GUI/Harmonograph/Config/Default.pm  view on Meta::CPAN

#

package App::GUI::Harmonograph::Config::Default;
use v5.12;
use warnings;

our $data = {
    file_base_dir => '~',
    file_base_name => 'good',
    file_base_counter => 0,
    file_base_ending => 'png',
    image_size => 600,
    precision => 4,
    open_dir => '~',
    save_dir => '~',
    write_dir => '~',
    last_settings => [],
    tips => 1,
    color_set => {
        grey    => [  '#FFF',    '#DDD',    '#BBB', '#999', '#777','#555', '#333', '#111' ],
        default => [  '#FFF', '#f9e595', '#a1680c', '#b63a3e','#777777', '#555555', 'gray20', '#111111' ],

lib/App/GUI/Harmonograph/Frame.pm  view on Meta::CPAN

            $self->{'config'}->set_value('image_size', $size);
        });
    }
    $image_size_menu->Check( 12100 +($self->{'config'}->get_value('image_size') / 100), 1);

    my $image_format_menu = Wx::Menu->new();
    $image_format_menu->AppendRadioItem(12201, 'PNG', "set default image format to PNG");
    $image_format_menu->AppendRadioItem(12202, 'JPEG', "set default image format to JPEG");
    $image_format_menu->AppendRadioItem(12203, 'SVG', "set default image format to SVG");

    Wx::Event::EVT_MENU( $self, 12201, sub { $self->{'config'}->set_value('file_base_ending', 'png') });
    Wx::Event::EVT_MENU( $self, 12202, sub { $self->{'config'}->set_value('file_base_ending', 'jpg') });
    Wx::Event::EVT_MENU( $self, 12203, sub { $self->{'config'}->set_value('file_base_ending', 'svg') });

    $image_format_menu->Check( 12201, 1 ) if $self->{'config'}->get_value('file_base_ending') eq 'png';
    $image_format_menu->Check( 12202, 1 ) if $self->{'config'}->get_value('file_base_ending') eq 'jpg';
    $image_format_menu->Check( 12203, 1 ) if $self->{'config'}->get_value('file_base_ending') eq 'svg';

    my $image_menu = Wx::Menu->new();
    $image_menu->Append( 12300, "&Draw\tCtrl+D", "complete a sketch drawing" );
    $image_menu->Append( 12100, "S&ize",  $image_size_menu,   "set image size" );
    $image_menu->Append( 12200, "&Format",  $image_format_menu, "set default image formate" );
    $image_menu->Append( 12400, "&Save\tCtrl+S", "save currently displayed image" );

    my $help_menu = Wx::Menu->new();

lib/App/GUI/Harmonograph/Frame.pm  view on Meta::CPAN

}

sub inc_base_counter {
    my ($self, $type) = @_;
    my $dir = $self->{'config'}->get_value('file_base_dir');
    $dir = App::GUI::Harmonograph::Settings::expand_path( $dir );
    my $base = File::Spec->catfile( $dir, $self->{'config'}->get_value('file_base_name') );
    my $cc = $self->{'config'}->get_value('file_base_counter');
    while (1){
        last unless -e $base.'_'.$cc.'.svg'
                 or -e $base.'_'.$cc.'.png'
                 or -e $base.'_'.$cc.'.jpg'
                 or -e $base.'_'.$cc.'.gif'
                 or -e $base.'_'.$cc.'.ini';
        $cc++;
    }
    $self->{'txt'}{'file_bnr'}->SetValue( $cc );
    $self->{'config'}->set_value('file_base_counter', $cc);
}


lib/App/GUI/Harmonograph/Frame.pm  view on Meta::CPAN

    return if -e $path and
              Wx::MessageDialog->new( $self, "\n\nReally overwrite the settings file?", 'Confirmation Question',
                                      &Wx::wxYES_NO | &Wx::wxICON_QUESTION )->ShowModal() != &Wx::wxID_YES;
    $self->write_settings_file( $path );
    my $dir = App::GUI::Harmonograph::Settings::extract_dir( $path );
    $self->{'config'}->set_value('write_dir', $dir);
}

sub save_image_dialog {
    my ($self) = @_;
    my @wildcard = ( 'SVG files (*.svg)|*.svg', 'PNG files (*.png)|*.png', 'JPEG files (*.jpg)|*.jpg');
    my $wildcard = '|All files (*.*)|*.*';
    my $default_ending = $self->{'config'}->get_value('file_base_ending');
    $wildcard = ($default_ending eq 'jpg') ? ( join '|', @wildcard[2,1,0]) . $wildcard :
                ($default_ending eq 'png') ? ( join '|', @wildcard[1,0,2]) . $wildcard :
                                             ( join '|', @wildcard[0,1,2]) . $wildcard ;
    my @wildcard_ending = ($default_ending eq 'jpg') ? (qw/jpg png svg/) :
                          ($default_ending eq 'png') ? (qw/png svg jpg/) :
                                                       (qw/svg jpg png/) ;

    my $dialog = Wx::FileDialog->new ( $self, "select a file name to save image", $self->{'config'}->get_value('save_dir'), '', $wildcard, &Wx::wxFD_SAVE );
    return if $dialog->ShowModal == &Wx::wxID_CANCEL;
    my $path = $dialog->GetPath;
    return if -e $path and
              Wx::MessageDialog->new( $self, "\n\nReally overwrite the image file?", 'Confirmation Question',
                                      &Wx::wxYES_NO | &Wx::wxICON_QUESTION )->ShowModal() != &Wx::wxID_YES;
    my $file_ending = lc substr ($path, -4);
    unless ($dialog->GetFilterIndex == 3 or # filter set to all endings
            ($file_ending eq '.jpg' or $file_ending eq '.png' or $file_ending eq '.svg')){
            $path .= '.' . $wildcard_ending[$dialog->GetFilterIndex];
    }
    my $ret = $self->write_image( $path );
    if ($ret){ $self->SetStatusText( $ret, 0 ) }
    else     { $self->{'config'}->set_value('save_dir', App::GUI::Harmonograph::Settings::extract_dir( $path )) }
}

sub open_setting_file {
    my ($self, $file ) = @_;
    my $settings = App::GUI::Harmonograph::Settings::load( $file );

lib/App/GUI/Harmonograph/Frame/Panel/Board.pm  view on Meta::CPAN

    $code_ref->( $dc, $Cx, $Cy ) if ref $code_ref;
    delete $self->{'draw_args'};
    $dc;
}

sub save_file {
    my( $self, $file_name, $settings, $progress_bar, $width, $height ) = @_;
    $self->{'temp'} = {settings => $settings, progress_bar => $progress_bar};
    my $file_end = lc substr( $file_name, -3 );
    if ($file_end eq 'svg') { $self->save_svg_file( $file_name, $width, $height ) }
    elsif ($file_end eq 'png' or $file_end eq 'jpg') { $self->save_bmp_file( $file_name, $file_end, $width, $height ) }
    else { return "unknown file ending: '$file_end'" }
}

sub save_svg_file {
    my( $self, $file_name, $width, $height ) = @_;
    $width  //= $self->GetParent->{'config'}->get_value('image_size');
    $height //= $self->GetParent->{'config'}->get_value('image_size');
    $width  //= $self->{'size'}{'x'};
    $height //= $self->{'size'}{'y'};
    my $dc = Wx::SVGFileDC->new( $file_name, $width, $height, 250 );  #  250 dpi

lib/App/GUI/Harmonograph/Frame/Panel/Board.pm  view on Meta::CPAN

    my( $self, $file_name, $file_end, $width, $height ) = @_;
    $width  //= $self->GetParent->{'config'}->get_value('image_size');
    $height //= $self->GetParent->{'config'}->get_value('image_size');
    $width  //= $self->{'size'}{'x'};
    $height //= $self->{'size'}{'y'};
    #~ my $bmp = Wx::Bitmap->new( $width, $height, 24); # bit depth
    #~ my $dc = Wx::MemoryDC->new( );
    #~ $self->paint( $dc, { width => $width, height => $height });
    #~ $dc->SelectObject( $bmp );
    #~ $dc->SelectObject( &Wx::wxNullBitmap );
    # $dc->SaveFile( $file_name, $file_end eq 'png' ? &Wx::wxBITMAP_TYPE_PNG : &Wx::wxBITMAP_TYPE_JPEG );
}

1;

lib/App/GUI/Wx/Widget/Custom/Canvas.pm  view on Meta::CPAN

    $dc->DrawBitmap( Wx::Bitmap->new( $img ), 0, 0, 0 ); # at point (0, 0) with no mask
    $self->{'image'} = $img unless $self->{'flag'}{'sketch'};
    delete $self->{'flag'};
    $dc;
}

sub save_file {
    my( $self, $file_name, $width, $height ) = @_;
    my $file_end = lc substr( $file_name, -3 );
    if ($file_end eq 'svg') { $self->save_svg_file( $file_name, $width, $height ) }
    elsif ($file_end eq 'png' or $file_end eq 'jpg') { $self->save_bmp_file( $file_name, $file_end, $width, $height ) }
    else { return "unknown file ending: '$file_end'" }
}

sub save_svg_file {
    my( $self, $file_name, $width, $height ) = @_;
    $width  //= $self->GetParent->{'config'}->get_value('image_size');
    $height //= $self->GetParent->{'config'}->get_value('image_size');
    $width  //= $self->{'size'}{'x'};
    $height //= $self->{'size'}{'y'};
    my $dc = Wx::SVGFileDC->new( $file_name, $width, $height, 250 );  #  250 dpi

lib/App/GUI/Wx/Widget/Custom/Canvas.pm  view on Meta::CPAN

    $height //= $self->GetParent->{'config'}->get_value('image_size');
    $width  //= $self->{'size'}{'x'};
    $height //= $self->{'size'}{'y'};
    # reuse $set->{'image'}
    my $bmp = Wx::Bitmap->new( $width, $height, 24); # bit depth
    my $dc = Wx::MemoryDC->new( );
    $dc->SelectObject( $bmp );
    $self->paint( $dc, $width, $height);
    # $dc->Blit (0, 0, $width, $height, $self->{'dc'}, 10, 10 + $self->{'menu_size'});
    $dc->SelectObject( &Wx::wxNullBitmap );
    $bmp->SaveFile( $file_name, $file_end eq 'png' ? &Wx::wxBITMAP_TYPE_PNG : &Wx::wxBITMAP_TYPE_JPEG );
}

1;



( run in 2.253 seconds using v1.01-cache-2.11-cpan-df04353d9ac )