Skip to content

drag to make gif

  • by

Loads images. Makes thumbnails in top bit. Drag images below line. Makes a gif of images below line in left to right order. Y-axis doesn’t matter. Can load same images more than once. Can quickly check movement. Gif saved to “output.gif”. Image sequence .CSV files saved to ./LISTS/image_list_*.

#!/usr/bin/perl
use strict;
use warnings;
use Tk;
use Tk::Canvas;
use Tk::Scrollbar;
use File::Basename;
use File::Copy;
use File::Path qw(remove_tree);
use Image::Imlib2;
use Tk::PNG;
use Tk::JPEG;
use Tk::Photo;
use feature "say";
use Data::Dumper qw(Dumper);
my ($gif,$canvas2,$frame,$window2,$max_frames,$image_item,$ww,$wh,$counter);
my $number=0;
my @saved;
my $mw = MainWindow->new;
$mw->title("Drag & Re-order Images");
my $button_frame = $mw->Frame(-bg => 'lightgray')->pack(-fill => 'x', -side => 'top');
my @lists;
my $chk=0;
$button_frame->Button(
    -text    => "Load Images",
    -command => \&load_images,
    -font    => ['Arial', 14],
    -width   => 15,
    -height  => 2
)->pack(-side => 'left', -padx => 10);

$button_frame->Button(
    -text    => "NEW GIF",
    -command => sub{&rename_images;&make_gif;&play_gif;&remove;},
    -font    => ['Arial', 14],
    -width   => 15,
    -height  => 2
)->pack(-side => 'left', -padx => 10);

$button_frame->Button(
    -text    => "CLOSE",
    -command => sub{&exit},
    -font    => ['Arial', 14],
    -width   => 15,
    -height  => 2
)->pack(-side => 'left', -padx => 10);

# Main canvas frame (contains both scrollable areas)
my $canvas_frame = $mw->Frame()->pack(-fill => 'both', -expand => 1);
my $canvas = $canvas_frame->Canvas(-width => 2000, -height => 500, -bg => 'white');
my $scroll_x = $canvas_frame->Scrollbar(-orient => 'horizontal', -command => sub { $canvas->xview(@_) });
$canvas->configure(-xscrollcommand => ['set', $scroll_x]);
$canvas->pack(-side => 'top', -fill => 'both', -expand => 1);
$scroll_x->pack(-side => 'bottom', -fill => 'x');


# Separation line
my $separation_y = 220;
my $separation_line = $canvas->createLine(0, $separation_y, 10000, $separation_y, -fill => 'black', -width => 2);

my %image_data;
my $dragging;
my ($drag_offset_x, $drag_offset_y);
MainLoop;

sub load_images {
    my @files = @{$mw->getOpenFile(
        -multiple => 1,
        -filetypes => [['Images', ['.png', '.jpg', '.jpeg', '.gif']]],
    )};
    return unless @files;
 #say Dumper (\@files);   
    my $x = 10;
    my $max_x = 10;
    
    foreach my $file (@files) {
        next unless -f $file;
        
        my $img = create_thumbnail($file);
        my $id = $canvas->createImage($x, 10, -image => $img, -anchor => 'nw');
        
        $image_data{$id} = { file => $file, x => $x, y => 10, image => $img };
        
        # Enable dragging
        $canvas->bind($id, '<ButtonPress-1>' => [\&start_drag, $id]);
        $canvas->bind($id, '<B1-Motion>' => [\&do_drag, $id]);
        $canvas->bind($id, '<ButtonRelease-1>' => [\&stop_drag, $id]);
        
        $x += 100; # check this
        $max_x = $x if $x > $max_x;
    }
    
    # Update scroll region dynamically
    $canvas->configure(-scrollregion => [0, 0, $max_x, 800]); # need this

}


sub create_thumbnail {
    my ($file) = @_;
    my $img = $mw->Photo(-file => $file);
    my ($width, $height) = ($img->width, $img->height);
($ww,$wh)=($width,$height); 
    my $scale = 80 / $width;
    my $thumb = $mw->Photo(-width => 80, -height => int($height * $scale));
    $thumb->copy($img, -subsample => int($width / 80));

    return $thumb;
}

sub start_drag {
    my ($canvas, $id) = @_;
    $dragging = $id;
    my $e = $canvas->XEvent;
    my ($img_x, $img_y) = $canvas->coords($id);
    $drag_offset_x = $e->x - $img_x;
    $drag_offset_y = $e->y - $img_y;
}

sub do_drag {
    my ($canvas, $id) = @_;
    return unless $dragging;
    
    my $e = $canvas->XEvent;
    my $new_x = $e->x - $drag_offset_x;
    my $new_y = $e->y - $drag_offset_y;
    
    $canvas->coords($id, $new_x, $new_y);
    
    # Ensure items can move across the separation line
    $image_data{$id}{x} = $new_x;
    $image_data{$id}{y} = $new_y;
}

sub stop_drag {
    $dragging = undef;
}


sub rename_images {
$chk++;
if($window2){$window2->destroy;}
my $dir = "re-ordered";  #added
    my @sorted_images = sort { 
        my $a_x = $image_data{$a}{x} // 0; 
        my $b_x = $image_data{$b}{x} // 0;
        $a_x <=> $b_x
    }
    grep { 
        my $y = $image_data{$_}{y} // 0; 
        $y > $separation_y
    } keys %image_data;
   
    return unless @sorted_images;

  $counter =0;
    for my $id (@sorted_images) {
        
        my ($old_path, $ext) = ($image_data{$id}{file}, '.png');
#say $old_path;
$lists[$chk][$counter]{file}=$old_path;
#say Dumper(\@lists);
my $new_path = sprintf("re-ordered/image_%04d"."$ext",$counter);
        copy($old_path, $new_path);
       # print "Renamed: $old_path -> $new_path\n";
$counter++;
    }

}



sub play_gif{
    my ($ww, $wh) = ($ww + 60, $wh + 60);
    $window2 = $mw->Toplevel;
    $window2->geometry("${ww}x${wh}+1500+500");
    $canvas2 = $window2->Canvas(-width => $ww, -height => $wh);
    $canvas2->pack;

    my $myfile = 'output.gif';
    $gif = $mw->Photo(-file => $myfile, -format => 'gif');

    # Auto-detect number of frames
    my $frames = 0;
    while (eval { $gif->configure(-format => "gif -index $frames"); 1 }) {
        $frames++;
    }

    if ($frames == 0) {
        print "Error: No frames found in GIF!\n";
        return;
    }
    
    $frame = 0;
    $max_frames = $frames;  # Use detected frame count

    $image_item = $canvas2->createImage(0, 0, -anchor => 'nw', -image => $gif);

    sub update_frame {
        $gif->configure(-format => "gif -index $frame");
        $canvas2->itemconfigure($image_item, -image => $gif);
        $frame = ($frame + 1) % $max_frames;
        $window2->after(100, \&update_frame);
    }

    update_frame();
}	
	
sub remove {
my %temp;
my $dir = "LISTS";
if (! -d $dir){mkdir "LISTS"}

my $file_name = sprintf("image_list_%04d",$chk);
open(my $fh, '>', "$dir/$file_name") or die "Cannot open filelist: $!";
for (my $i=0;$i<=$counter-1;$i++){
$temp{file} = $lists[$chk][$i]{file}; 
#say " temp = $temp";	
print $fh "image,$temp{file}\n";
	}
close $fh;	
    $dir = "re-ordered";
    if (-d $dir) {
        remove_tree($dir, { error => \my $err });
        if (@$err) {
            warn "Error removing directory: @$err\n";
        } else {
            #print "Removed directory: $dir\n";
        }
    }
    mkdir $dir;  # Recreate it for new images

}

sub numerically {
    my ($a_num) = $a =~ /(\d+)/;
    my ($b_num) = $b =~ /(\d+)/;
$a_num //= 0;  # Set $a_num to 0 if it's uninitialized
$b_num //= 0;  # Set $b_num to 0 if it's uninitialized
return $a_num <=> $b_num;
}



sub make_gif{ 
# add check for directories and files.
unless (-e "output.gif") {
    open my $fh, '>', "output.gif" or die "Cannot create output.gif: $!";
    close $fh;
}

system('ffmpeg -y -v quiet -i re-ordered/image_%04d.png -vf "palettegen" palette.png');
system('ffmpeg -y -v quiet -framerate 10 -i re-ordered/image_%04d.png -i palette.png -filter_complex "paletteuse" -gifflags -offsetting -loop 0 output.gif');
}

sub close{
$mw->destroy;	
	}