1 |
#!/usr/bin/perl -w #d |
2 |
|
3 |
# GUI front-end to jpegtran to rotate and flip jpegs without loss |
4 |
# |
5 |
# 2000-06-14 Dobrica Pavlinusic <dpavlin@rot13.org> |
6 |
|
7 |
use Gtk; |
8 |
use strict; |
9 |
|
10 |
init Gtk; |
11 |
set_locale Gtk; |
12 |
|
13 |
my $DEBUG = 1; |
14 |
sub pdebug { print STDERR @_,"\n" if ($DEBUG); } |
15 |
|
16 |
my $false = 0; |
17 |
my $true = 1; |
18 |
|
19 |
# $X[0](dir|filename|w|h|pix) |
20 |
my @X; |
21 |
|
22 |
my $xv_pic_dir=".xvpics"; |
23 |
#$xv_pic_dir="/mnt/f/tmp2/PICS/.xvpics" if ($DEBUG); |
24 |
|
25 |
if (! $ARGV[0] && !-d ".xvpics") { |
26 |
print "Usage: $0 [path to directory with pictures (in which is .xvpics dir)]\n"; |
27 |
exit 1; |
28 |
} |
29 |
|
30 |
$xv_pic_dir = $ARGV[0]."/.xvpics" if ($ARGV[0]); |
31 |
|
32 |
if (! -d $xv_pic_dir) { |
33 |
print "$xv_pic_dir is not directory!"; |
34 |
} |
35 |
|
36 |
#-------------------------------------------------------------------- |
37 |
# scan directory |
38 |
|
39 |
pdebug "reading dir $xv_pic_dir"; |
40 |
|
41 |
opendir(DIR, $xv_pic_dir) || die "can't opendir $xv_pic_dir: $!"; |
42 |
my @pic_filenames = grep { !/^\./ && -f "$xv_pic_dir/$_" } readdir(DIR); |
43 |
closedir(DIR); |
44 |
|
45 |
pdebug "found $#pic_filenames files in $xv_pic_dir"; |
46 |
|
47 |
my $dir=$xv_pic_dir; |
48 |
$dir=~s/\.xvpics//; |
49 |
|
50 |
foreach my $filename (@pic_filenames) { |
51 |
pdebug "gronk $filename"; |
52 |
my ($w,$h,$pix) = xvpic_read("$xv_pic_dir/$filename"); |
53 |
my (%tmp) = ( dir=>$dir, filename=>$filename, w=>$w, h=>$h, pix=>$pix ); |
54 |
push @X,\%tmp; |
55 |
} |
56 |
|
57 |
#-------------------------------------------------------------------- |
58 |
# read xvpic from disk |
59 |
# xvpic_read($filename) |
60 |
# returns: |
61 |
# ($w,$h,$xv_pix) |
62 |
|
63 |
sub xvpic_read { |
64 |
my ($xv_pic) = @_; |
65 |
|
66 |
my ($w,$h,$cols) = (0,0,0); |
67 |
|
68 |
open(XV,$xv_pic) || die "$xv_pic: $!"; |
69 |
my $l=<XV>; chomp $l; |
70 |
if ($l!~m/^P7 332$/) { |
71 |
warn "$xv_pic not XV thumbnail file ($l)"; |
72 |
return; |
73 |
} |
74 |
while(<XV>) { |
75 |
chomp; |
76 |
next if (/^#/); |
77 |
if (/^(\d+) (\d+) (\d+)$/) { |
78 |
($w,$h,$cols) = ($1,$2,$3); |
79 |
last; |
80 |
} |
81 |
warn "expected w,h,cols, got: $_"; |
82 |
} |
83 |
|
84 |
warn "$xv_pic number of colors is not 255 (strange)" if ($cols != 255); |
85 |
|
86 |
my $buf; # tmp buffer for read; |
87 |
|
88 |
# read pixels |
89 |
read XV,$buf,($w*$h); |
90 |
|
91 |
return ($w,$h,$buf); |
92 |
} |
93 |
|
94 |
#-------------------------------------------------------------------- |
95 |
# Generate 332 colour-cube colourmap |
96 |
|
97 |
sub generate_xpm_colmap { |
98 |
my @xpm_colmap; |
99 |
for (my $i=0; $i<256; $i++) { |
100 |
|
101 |
push(@xpm_colmap,sprintf ("%02x c #%02x%02x%02x",$i, |
102 |
(255*(($i&(7<<5))>>5))/7, |
103 |
(255*(($i&(7<<2))>>2))/7, |
104 |
(255*(($i&(3<<0))>>0))/3 |
105 |
)); |
106 |
} |
107 |
return @xpm_colmap; |
108 |
} |
109 |
|
110 |
#-------------------------------------------------------------------- |
111 |
# main |
112 |
|
113 |
my ($w,$h) = (0,0); |
114 |
my $xv_pix; |
115 |
my @xpm_colmap=generate_xpm_colmap(); |
116 |
|
117 |
#($w,$h,@xv_pix) = xvpic_read($xv_pic); |
118 |
my $xv_pic = $X[0]{filename}; |
119 |
$w = $X[0]{w}; |
120 |
$h = $X[0]{h}; |
121 |
$xv_pix = $X[0]{pix}; |
122 |
|
123 |
#-------------------------------------------------------------------- |
124 |
# make rotated pictures |
125 |
|
126 |
#sub make_rotated { |
127 |
# my ($w,$h,$xv_pix) |
128 |
|
129 |
#/* width height ncolors chars_per_pixel */ |
130 |
#my @xpm_0= ( "$w $h 256 2" ); |
131 |
my @xpm_180= ( "$w $h 256 2" ); |
132 |
my @xpm_90= ( "$h $w 256 2" ); |
133 |
my @xpm_270= ( "$h $w 256 2" ); |
134 |
my @xpm_h= ( "$w $h 256 2" ); |
135 |
my @xpm_v= ( "$w $h 256 2" ); |
136 |
|
137 |
#push @xpm_0,@xpm_colmap; |
138 |
push @xpm_180,@xpm_colmap; |
139 |
push @xpm_90,@xpm_colmap; |
140 |
push @xpm_270,@xpm_colmap; |
141 |
push @xpm_h,@xpm_colmap; |
142 |
push @xpm_v,@xpm_colmap; |
143 |
|
144 |
my @pix_0; |
145 |
my @pix_180; |
146 |
my @pix_90; |
147 |
my @pix_270; |
148 |
my @pix_h; |
149 |
my @pix_v; |
150 |
|
151 |
my @xv_pix=unpack("C*",$xv_pix); |
152 |
|
153 |
for(my $y=0; $y<$h; $y++) { |
154 |
for (my $x=0; $x<$w; $x++) { |
155 |
if (defined($xv_pix[$y*$w+$x])) { |
156 |
$pix_0[$y*$w+$x] = sprintf("%02x",$xv_pix[$y*$w+$x]); |
157 |
$pix_180[($h-$y-1)*$w+($w-$x-1)] = sprintf("%02x",$xv_pix[$y*$w+$x]); |
158 |
$pix_270[($w-$x-1)*$h+$y] = sprintf("%02x",$xv_pix[$y*$w+$x]); |
159 |
$pix_90[$x*$h+$y] = sprintf("%02x",$xv_pix[$y*$w+$x]); |
160 |
$pix_h[$y*$w+($w-$x-1)] = sprintf("%02x",$xv_pix[$y*$w+$x]); |
161 |
$pix_v[($h-$y-1)*$w+$x] = sprintf("%02x",$xv_pix[$y*$w+$x]); |
162 |
} else { |
163 |
warn "undef pixel $x,$y!\n"; |
164 |
} |
165 |
} |
166 |
} |
167 |
|
168 |
|
169 |
# fill in pixels now |
170 |
|
171 |
my $tmp; |
172 |
|
173 |
# portrait |
174 |
my $lw=$w*2; |
175 |
#$tmp=join('',@pix_0); while ($tmp=~s/^(.{$lw})//) { push(@xpm_0,$1); } |
176 |
$tmp=join('',@pix_180); while ($tmp=~s/^(.{$lw})//) { push(@xpm_180,$1); } |
177 |
$tmp=join('',@pix_v); while ($tmp=~s/^(.{$lw})//) { push(@xpm_v,$1); } |
178 |
$tmp=join('',@pix_h); while ($tmp=~s/^(.{$lw})//) { push(@xpm_h,$1); } |
179 |
|
180 |
# landscape |
181 |
$lw=$h*2; |
182 |
$tmp=join('',@pix_90); while ($tmp=~s/^(.{$lw})//) { push(@xpm_90,$1); } |
183 |
$tmp=join('',@pix_270); while ($tmp=~s/^(.{$lw})//) { push(@xpm_270,$1); } |
184 |
|
185 |
#-------------------------------------------------------------------- |
186 |
# GTK GUI |
187 |
|
188 |
my $window; |
189 |
my ($pixmapwid_0, $pixmapwid_180, $pixmapwid_90, $pixmapwid_270, $pixmapwid_v, $pixmapwid_h); |
190 |
my ($button_0, $button_180, $button_90, $button_270, $button_v, $button_h); |
191 |
my ($pixmap_0, $pixmap_180, $pixmap_90, $pixmap_270, $pixmap_v, $pixmap_h); |
192 |
my $mask; |
193 |
my $style; |
194 |
|
195 |
$window = new Gtk::Window( "toplevel" ); |
196 |
$window->signal_connect( "delete_event", sub { Gtk->exit( 0 ); } ); |
197 |
$window->border_width( 10 ); |
198 |
$window->set_title( "GTK jpegtran" ); |
199 |
$window->realize(); |
200 |
|
201 |
# now for the pixmap from gdk |
202 |
$style = $window->get_style()->bg( 'normal' ); |
203 |
|
204 |
#( $pixmap_0, $mask ) = Gtk::Gdk::Pixmap->create_from_xpm_d( $window->window, $style, @xpm_0 ); |
205 |
# |
206 |
## a pixmap widget to contain the pixmap |
207 |
#$pixmapwid_0 = new Gtk::Pixmap( $pixmap_0, $mask ); |
208 |
#$pixmapwid_0->show(); |
209 |
# |
210 |
## a button to contain the pixmap widget |
211 |
#$button_0 = new Gtk::Button(); |
212 |
#$button_0->add( $pixmapwid_0 ); |
213 |
#$button_0->show(); |
214 |
##$button_0->signal_connect( "clicked", \&jpegtran, "-optimize" ); |
215 |
## make button_0 inactive |
216 |
#$button_0->set_sensitive( $false ); |
217 |
|
218 |
# rotate 180 |
219 |
( $pixmap_180, $mask ) = Gtk::Gdk::Pixmap->create_from_xpm_d( $window->window, $style, @xpm_180 ); |
220 |
|
221 |
$pixmapwid_180 = new Gtk::Pixmap( $pixmap_180, $mask ); |
222 |
$pixmapwid_180->show(); |
223 |
|
224 |
$button_180 = new Gtk::Button(); |
225 |
$button_180->add( $pixmapwid_180 ); |
226 |
$button_180->show(); |
227 |
$button_180->signal_connect( "clicked", \&jpegtran, "-rotate 180" ); |
228 |
|
229 |
# rotate 90 |
230 |
( $pixmap_90, $mask ) = Gtk::Gdk::Pixmap->create_from_xpm_d( $window->window, $style, @xpm_90 ); |
231 |
$pixmapwid_90 = new Gtk::Pixmap( $pixmap_90, $mask ); |
232 |
$pixmapwid_90->show(); |
233 |
|
234 |
$button_90 = new Gtk::Button(); |
235 |
$button_90->add( $pixmapwid_90 ); |
236 |
$button_90->show(); |
237 |
$button_90->signal_connect( "clicked", \&jpegtran, "-rotate 90" ); |
238 |
|
239 |
# rotate 270 |
240 |
( $pixmap_270, $mask ) = Gtk::Gdk::Pixmap->create_from_xpm_d( $window->window, $style, @xpm_270 ); |
241 |
$pixmapwid_270 = new Gtk::Pixmap( $pixmap_270, $mask ); |
242 |
$pixmapwid_270->show(); |
243 |
|
244 |
$button_270 = new Gtk::Button(); |
245 |
$button_270->add( $pixmapwid_270 ); |
246 |
$button_270->show(); |
247 |
$button_270->signal_connect( "clicked", \&jpegtran, "-rotate 270" ); |
248 |
|
249 |
# rotate v |
250 |
( $pixmap_v, $mask ) = Gtk::Gdk::Pixmap->create_from_xpm_d( $window->window, $style, @xpm_v ); |
251 |
$pixmapwid_v = new Gtk::Pixmap( $pixmap_v, $mask ); |
252 |
$pixmapwid_v->show(); |
253 |
|
254 |
$button_v = new Gtk::Button(); |
255 |
$button_v->add( $pixmapwid_v ); |
256 |
$button_v->show(); |
257 |
$button_v->signal_connect( "clicked", \&jpegtran, "-flip vertical" ); |
258 |
|
259 |
# rotate h |
260 |
( $pixmap_h, $mask ) = Gtk::Gdk::Pixmap->create_from_xpm_d( $window->window, $style, @xpm_h ); |
261 |
$pixmapwid_h = new Gtk::Pixmap( $pixmap_h, $mask ); |
262 |
$pixmapwid_h->show(); |
263 |
|
264 |
$button_h = new Gtk::Button(); |
265 |
$button_h->add( $pixmapwid_h ); |
266 |
$button_h->show(); |
267 |
$button_h->signal_connect( "clicked", \&jpegtran, "-flip horizontal" ); |
268 |
|
269 |
# pack, show, draw |
270 |
|
271 |
my $hbox_top = new Gtk::HBox( $false, 0 ); |
272 |
my $hbox_mid = new Gtk::HBox( $true, 0 ); |
273 |
my $hbox_bot = new Gtk::HBox( $true, 0 ); |
274 |
|
275 |
|
276 |
#-------------------------------------------------------------------- |
277 |
# |
278 |
|
279 |
sub pix2xpm { |
280 |
my ($nr,$w,$h,$xv_pix) = @_; |
281 |
|
282 |
pdebug("pix2xpm w:$w h:$w pix:",length($xv_pix)); |
283 |
|
284 |
my @xpm=( "$w $h 256 2" ); |
285 |
push @xpm,@xpm_colmap; |
286 |
|
287 |
my @xv_pix=unpack("C*",$xv_pix); |
288 |
my $pix; |
289 |
|
290 |
for(my $i=0; $i<$#xv_pix; $i++) { |
291 |
$pix .= sprintf("%02x",$xv_pix[$i]); |
292 |
} |
293 |
my $lw=$w*2; |
294 |
while ($pix=~s/^(.{$lw})//) { push(@xpm,$1); } |
295 |
|
296 |
pdebug "pix2xpm retured ",length(join("",@xpm))," bytes, $#xpm lines"; |
297 |
return (\@xpm); |
298 |
} |
299 |
|
300 |
sub xpm_button { |
301 |
my ($nr) = @_; |
302 |
|
303 |
pdebug "xpm_button($nr): xpm/pix len: ", length $X[$nr]{xpm},"/",length $X[$nr]{pix}; |
304 |
|
305 |
( $X[$nr]{pixmap}, $mask ) = Gtk::Gdk::Pixmap->create_from_xpm_d( $window->window, $style, @{$X[$nr]{xpm}} ); |
306 |
# ( $pixmap, $mask ) = Gtk::Gdk::Pixmap->create_from_xpm( $window->window, $style, "test.xpm" ); |
307 |
$X[$nr]{pixmapwid} = new Gtk::Pixmap( $X[$nr]{pixmap}, $mask ); |
308 |
$X[$nr]{pixmapwid}->show(); |
309 |
|
310 |
$X[$nr]{button} = new Gtk::Button(); |
311 |
$X[$nr]{button}->add( $X[$nr]{pixmapwid} ); |
312 |
$X[$nr]{button}->show(); |
313 |
$X[$nr]{button}->signal_connect( "clicked", \&select_pic, $nr ); |
314 |
|
315 |
return ($X[$nr]{button}); |
316 |
} |
317 |
|
318 |
my $arr_l = create_arrow_button( 'left', 'in' ); |
319 |
$hbox_top->pack_start( $arr_l, $false, $false, 0 ); |
320 |
#$hbox_top->pack_start( $button_0, $false, $false, 0 ); |
321 |
|
322 |
|
323 |
for (my $i=0; $i<$#X; $i++) { |
324 |
#for (my $i=0; $i<0; $i++) { |
325 |
$X[$i]{xpm} = pix2xpm($i,$X[$i]{w},$X[$i]{h},$X[$i]{pix}); |
326 |
$X[$i]{button}=xpm_button($window, $i ); |
327 |
$hbox_top->pack_start( $X[$i]{button}, $false, $false, 0 ); |
328 |
} |
329 |
|
330 |
my $arr_r = create_arrow_button( 'right', 'in' ); |
331 |
$hbox_top->pack_start( $arr_r, $false, $false, 0 ); |
332 |
|
333 |
$hbox_mid->pack_start( $button_90, $false, $false, 0 ); |
334 |
$hbox_mid->pack_start( $button_180, $false, $false, 0 ); |
335 |
$hbox_mid->pack_start( $button_270, $false, $false, 0 ); |
336 |
|
337 |
$hbox_bot->pack_start( $button_v, $false, $false, 0 ); |
338 |
$hbox_bot->pack_start( $button_h, $false, $false, 0 ); |
339 |
|
340 |
$hbox_top->show(); |
341 |
$hbox_mid->show(); |
342 |
$hbox_bot->show(); |
343 |
|
344 |
sub create_arrow_button |
345 |
{ |
346 |
my ( $arrow_type, $shadow_type ) = @_; |
347 |
|
348 |
my $button; |
349 |
my $arrow; |
350 |
|
351 |
$button = new Gtk::Button(); |
352 |
$arrow = new Gtk::Arrow( $arrow_type, $shadow_type ); |
353 |
|
354 |
$button->add( $arrow ); |
355 |
$button->show(); |
356 |
$arrow->show(); |
357 |
|
358 |
return ( $button ); |
359 |
} |
360 |
|
361 |
|
362 |
my $hbox = new Gtk::HBox( $true, 0 ); |
363 |
my $filename_label = new Gtk::Label($xv_pic); |
364 |
$filename_label->show(); |
365 |
$hbox->pack_start( $filename_label, $false, $false, 0 ); |
366 |
$hbox->show(); |
367 |
|
368 |
my $hseparator = new Gtk::HSeparator(); |
369 |
$hseparator->show(); |
370 |
my $hseparator2 = new Gtk::HSeparator(); |
371 |
$hseparator2->show(); |
372 |
|
373 |
my $vbox = new Gtk::VBox( $false, 5 ); |
374 |
$vbox->pack_start( $hbox_top, $false, $false, 0 ); |
375 |
$vbox->pack_start( $hseparator, $false, $false, 0 ); |
376 |
$vbox->pack_start( $hbox, $false, $false, 0 ); |
377 |
$vbox->pack_start( $hseparator2, $false, $false, 0 ); |
378 |
$vbox->pack_start( $hbox_mid, $false, $false, 0 ); |
379 |
$vbox->pack_start( $hbox_bot, $false, $false, 0 ); |
380 |
$vbox->show(); |
381 |
|
382 |
$window->add($vbox); |
383 |
$window->show(); |
384 |
|
385 |
main Gtk; |
386 |
exit( 0 ); |
387 |
|
388 |
#-------------------------------------------------------------------- |
389 |
# call jpegtran command |
390 |
|
391 |
sub jpegtran { |
392 |
my ( $widget, $data ) = @_; |
393 |
print "jpegtran $data\n"; |
394 |
} |
395 |
|
396 |
#-------------------------------------------------------------------- |
397 |
# select picture |
398 |
|
399 |
sub select_pic { |
400 |
my ( $widget, $data ) = @_; |
401 |
my $xvpic=$X[$data]{filename}; |
402 |
print "picture selected: $data ($xvpic)\n"; |
403 |
$filename_label->set_text($xvpic); |
404 |
} |