#!/usr/bin/perl -w # # chargen2png.pl # # Convert Apple II CHARGEN (VIDEO) ROM images to a PNG image. # # 20181105 Leeland Heins # use strict; use Gtk2; use constant TRUE => 1; use constant FALSE => 0; # Backing pixmap for drawing area my $pixmap = undef; my %allocated_colors; my $block_size = 4; # Default to 4x4 pixels. my $filename = ''; my $png_filename = ''; my $flip = FALSE; # Default to //e style CHARGEN ROM. # Parse the command line parameters. while (defined $ARGV[0] && $ARGV[0] =~ /^-/) { # Block size for pixels, can be 1 to 7. if (defined $ARGV[0] && $ARGV[0] eq "-b" && defined $ARGV[1] && $ARGV[1] =~ /^[1234567]$/) { $block_size = $ARGV[1]; shift; shift; # -f = Flip for Apple ][+ mode. } elsif (defined $ARGV[0] && $ARGV[0] eq "-f") { # For ][+ style CHARGEN ROMs. Default is for //e. $flip = TRUE; shift; } } # Allow specifying ROM image on the command line. if (defined $ARGV[0] && $ARGV[0] ne '') { $filename = shift; } # Apple II characters are a 7x8 matrix. Block size is the scaling factor for the output image. my $x_size = 7 * 16 * $block_size; my $y_size = 8 * 16 * $block_size; my $x_offset = 0; my $y_offset = 0; # Draw the CHARGEN ROM into the pixmap. sub draw_charset { my ($widget, $filename) = @_; my $fh; my $buffer = ''; # Open the input CHARGEN ROM file. if (open($fh, "<$filename")) { binmode $fh; # Read the input CHARGEN ROM file, 4096 for a //e ROM, 2048 for a ][+ ROM, I think 8192 for an international //e ROM maybe. my $size = read($fh, $buffer, 8192); #print "size=$size\n"; my @raw = unpack "C[8192]", $buffer; # Display all 256 characters in a 16x16 matrix. for (my $chr = 0; $chr < 256; $chr++) { for (my $y = 0; $y < 8; $y++) { for (my $x = 0; $x < 7; $x++) { my $offset = ($chr << 3) + $y; my $bit = 1 << $x; my $byte = $raw[$offset]; if ($byte & $bit) { my $xl; if ($flip) { # For a ][+ style ROM $xl = (112 - (7 * ($chr & 0xf) + $x)) * $block_size; } else { # For a //e style ROM $xl = (7 * ($chr & 0xf) + $x) * $block_size; } $pixmap->draw_rectangle($widget->style->black_gc, TRUE, $xl, ($y + 8 * ($chr >> 4)) * $block_size, $block_size, $block_size); } } } } # Refresh the pixmap. $widget->queue_draw_area(0, 0, $widget->allocation->width, $widget->allocation->height); } else { print "Failed to open $filename\n"; } } # Draw the whole pixmap white sub erase_pixmap { my $widget = shift; # GtkWidget *widget $pixmap->draw_rectangle($widget->style->white_gc, TRUE, 0, 0, $widget->allocation->width, $widget->allocation->height); $widget->queue_draw_area(0, 0, $widget->allocation->width, $widget->allocation->height); } # Create a new backing pixmap of the appropriate size sub configure_event { my $widget = shift; # GtkWidget *widget my $event = shift; # GdkEventConfigure *event $pixmap = Gtk2::Gdk::Pixmap->new($widget->window, $widget->allocation->width, $widget->allocation->height, -1); $pixmap->draw_rectangle($widget->style->white_gc, TRUE, 0, 0, $widget->allocation->width, $widget->allocation->height); return TRUE; } # Redraw the screen from the backing pixmap sub expose_event { my $widget = shift; # GtkWidget *widget my $event = shift; # GdkEventExpose *event $widget->window->draw_drawable( $widget->style->fg_gc($widget->state), $pixmap, $event->area->x, $event->area->y, $event->area->x, $event->area->y, $event->area->width, $event->area->height); return FALSE; } # Allow saving the image as a PNG file. sub save_png { my $png_file = shift; if ($png_file ne "") { #if (-e $png_file) { # print "File $png_file already exists!\n"; #} else { my $pixbuf = Gtk2::Gdk::Pixbuf->new('GDK_COLORSPACE_RGB', TRUE, 8, $x_size, $y_size); $pixbuf->get_from_drawable($pixmap, undef, 0, 0, 0, 0, $x_size, $y_size); if ($pixbuf->save($png_file, 'png')) { print "ERROR!\n"; } else { print "Saved!\n"; } #} } else { print "Must enter filename to save!\n"; } } { Gtk2->init; my $window = Gtk2::Window->new('toplevel'); $window->set_name("Chord Wizard"); my $vbox = Gtk2::VBox->new(FALSE, 0); $window->add($vbox); $vbox->show; $window->signal_connect("destroy", sub { exit(0); }); # Create the drawing area my $drawing_area = Gtk2::DrawingArea->new; $drawing_area->set_size_request($x_size + $x_offset + $x_offset, $y_size + $y_offset + $y_offset); $vbox->pack_start($drawing_area, TRUE, TRUE, 0); $drawing_area->show; # Signals used to handle backing pixmap $drawing_area->signal_connect(expose_event => \&expose_event); $drawing_area->signal_connect(configure_event => \&configure_event); my $hbox = Gtk2::HBox->new(FALSE, 0); $hbox->show; $vbox->pack_start($hbox, FALSE, FALSE, 0); # Allow entry of the CHARGEN (VIDEO) ROM image filename. my $label1 = Gtk2::Label->new(); $label1->set_markup("Chargen File:"); $label1->show; $hbox->pack_start($label1, FALSE, FALSE, 0); my $entry1 = Gtk2::Entry->new_with_max_length(128); $entry1->set_width_chars(32); $entry1->show; $entry1->set_text($filename) if $filename; $hbox->pack_start($entry1, FALSE, FALSE, 0); my $load_button = Gtk2::Button->new("Load"); $load_button->show; $hbox->pack_start($load_button, FALSE, FALSE, 0); $load_button->signal_connect_swapped(clicked => sub { # Get the CHARGEN ROM image filename. $filename = $entry1->get_text(); print "Load $filename\n"; # Load and draw the image. draw_charset($window, $filename) if $filename; }, $window); my $hbox2 = Gtk2::HBox->new(FALSE, 0); $hbox2->show; $vbox->pack_start($hbox2, FALSE, FALSE, 0); # Allow entry of a filename to output the PNG image to. my $label2 = Gtk2::Label->new(); $label2->set_markup("PNG File:"); $label2->show; $hbox2->pack_start($label2, FALSE, FALSE, 0); my $entry2 = Gtk2::Entry->new_with_max_length(128); $entry2->set_width_chars(32); $entry2->show; $hbox2->pack_start($entry2, FALSE, FALSE, 0); # Button to allow saving the image to a PNG file. my $save_button = Gtk2::Button->new("Save"); $save_button->show; $hbox2->pack_start($save_button, FALSE, FALSE, 0); $save_button->signal_connect_swapped(clicked => sub { # Get the filename from the text entry widget. $png_filename = $entry2->get_text(); # Actually save the image to a PNG file. save_png($png_filename); }, $window); my $hbox3 = Gtk2::HBox->new(FALSE, 0); $hbox3->show; $vbox->pack_start($hbox3, FALSE, FALSE, 0); # For //e mode. my $button_key = Gtk2::RadioButton->new(undef, "//e"); $hbox3->pack_start($button_key, TRUE, TRUE, 0); $button_key->set_active(TRUE); $button_key->signal_connect('toggled' => sub { $flip = FALSE; &erase_pixmap($window); &draw_charset($window, $filename) if $filename; }, $window); $button_key->show; my @button_group = $button_key->get_group; # For ][+ mode. $button_key = Gtk2::RadioButton->new_with_label(@button_group, "][+"); $hbox3->pack_start($button_key, TRUE, TRUE, 0); $button_key->signal_connect('toggled' => sub { $flip = TRUE; &erase_pixmap($window); &draw_charset($window, $filename) if $filename; }, $window); $button_key->show; my $blocksize_combobox = Gtk2::ComboBox->new_text; my @blocksizes = ( '1', '2', '3', '4', '5', '6', '7' ); foreach my $blocksize (@blocksizes) { $blocksize_combobox->append_text($blocksize); } $blocksize_combobox->set_active(2); $blocksize_combobox->signal_connect_swapped('changed' => sub { # Get the new block size from the combo box widget. $block_size = $blocksize_combobox->get_active_text; # Re-size the window and re-draw at the new size. This is not quite working correctly. $x_size = 7 * 16 * $block_size; $y_size = 8 * 16 * $block_size; print "block_size=$block_size x_size=$x_size, y_size=$y_size\n"; #$pixmap = Gtk2::Gdk::Pixmap->new($drawing_area->window, $drawing_area->allocation->width, $drawing_area->allocation->height, -1); $pixmap = Gtk2::Gdk::Pixmap->new($drawing_area->window, $x_size, $y_size, -1); $drawing_area->set_size_request($x_size + $x_offset + $x_offset, $y_size + $y_offset + $y_offset); #$pixmap->draw_rectangle($drawing_area->style->white_gc, TRUE, 0, 0, $drawing_area->allocation->width, $drawing_area->allocation->height); $window->resize($x_size, $y_size + 80); $window->set_size_request($x_size + $x_offset + $x_offset, $y_size + $y_offset + $y_offset); #$drawing_area->set_size_request($x_size + $x_offset + $x_offset, $y_size + $y_offset + $y_offset); &erase_pixmap($window); &draw_charset($window, $filename) if $filename; &draw_charset($window, $filename) if $filename; }, $window); $blocksize_combobox->show; $hbox3->pack_start($blocksize_combobox, FALSE, FALSE, 0); my $clear_button = Gtk2::Button->new("Clear"); $hbox3->pack_start($clear_button, FALSE, FALSE, 0); $clear_button->signal_connect_swapped(clicked => sub { &erase_pixmap($window); $filename = ''; $entry1->set_text(''); $entry2->set_text(''); }, $window); $clear_button->show; # .. And a quit button my $quit_button = Gtk2::Button->new("Quit"); $hbox3->pack_start($quit_button, FALSE, FALSE, 0); $quit_button->signal_connect_swapped(clicked => sub { $_[0]->destroy; }, $window); $quit_button->show; $window->show; &erase_pixmap($window); &draw_charset($window, $filename) if $filename; Gtk2->main; }