1package Gscan2pdf::Frontend::Image_Sane; 2 3use strict; 4use warnings; 5use feature 'switch'; 6no if $] >= 5.018, warnings => 'experimental::smartmatch'; 7 8use threads; 9use threads::shared; 10use Thread::Queue; 11use Storable qw(freeze thaw); # For cloning the options cache 12use Try::Tiny; 13use Data::Dumper; 14$Data::Dumper::Sortkeys = 1; 15use Glib qw(TRUE FALSE); 16use Image::Sane ':all'; 17use Data::UUID; 18use File::Temp; # To create temporary files 19use Readonly; 20Readonly my $BUFFER_SIZE => ( 32 * 1024 ); # default size 21Readonly my $_POLL_INTERVAL => 100; # ms 22Readonly my $_8_BIT => 8; 23Readonly my $MAXVAL_8_BIT => 2**$_8_BIT - 1; 24Readonly my $_16_BIT => 16; 25Readonly my $MAXVAL_16_BIT => 2**$_16_BIT - 1; 26Readonly my $LARGE_STATUS => 99; 27Readonly my $NOT_FOUND => -1; 28my $uuid_object = Data::UUID->new; 29my $EMPTY = q{}; 30 31our $VERSION = '2.12.4'; 32 33my ( $prog_name, $logger, %callback, $_self ); 34 35sub setup { 36 ( my $class, $logger ) = @_; 37 $_self = {}; 38 $prog_name = Glib::get_application_name; 39 40 $_self->{requests} = Thread::Queue->new; 41 $_self->{return} = Thread::Queue->new; 42 43 # $_self->{device_handle} explicitly not shared 44 share $_self->{abort_scan}; 45 share $_self->{scan_progress}; 46 47 $_self->{thread} = threads->new( \&_thread_main, $_self ); 48 return; 49} 50 51sub _enqueue_request { 52 my ( $action, $data ) = @_; 53 my $sentinel : shared = 0; 54 $_self->{requests}->enqueue( 55 { 56 action => $action, 57 sentinel => \$sentinel, 58 ( $data ? %{$data} : () ) 59 } 60 ); 61 return \$sentinel; 62} 63 64sub _monitor_process { 65 my ( $sentinel, $uuid ) = @_; 66 67 my $started; 68 Glib::Timeout->add( 69 $_POLL_INTERVAL, 70 sub { 71 if ( ${$sentinel} == 2 ) { 72 if ( not $started ) { 73 if ( defined $callback{$uuid}{started} ) { 74 $callback{$uuid}{started}->(); 75 delete $callback{$uuid}{started}; 76 } 77 $started = 1; 78 } 79 check_return_queue(); 80 return Glib::SOURCE_REMOVE; 81 } 82 elsif ( ${$sentinel} == 1 ) { 83 if ( not $started ) { 84 if ( defined $callback{$uuid}{started} ) { 85 $callback{$uuid}{started}->(); 86 delete $callback{$uuid}{started}; 87 } 88 $started = 1; 89 } 90 if ( defined $callback{$uuid}{running} ) { 91 $callback{$uuid}{running}->(); 92 } 93 return Glib::SOURCE_CONTINUE; 94 } 95 } 96 ); 97 return; 98} 99 100sub quit { 101 _enqueue_request('quit'); 102 if ( defined $_self->{thread} ) { 103 $_self->{thread}->join(); 104 $_self->{thread} = undef; 105 Image::Sane::_exit(); ## no critic (ProtectPrivateSubs) 106 } 107 return; 108} 109 110sub get_devices { 111 my ( $class, $started_callback, $running_callback, $finished_callback ) = 112 @_; 113 114 my $uuid = $uuid_object->create_str; 115 $callback{$uuid}{started} = $started_callback; 116 $callback{$uuid}{running} = $running_callback; 117 $callback{$uuid}{finished} = $finished_callback; 118 my $sentinel = _enqueue_request( 'get-devices', { uuid => $uuid } ); 119 _monitor_process( $sentinel, $uuid ); 120 return; 121} 122 123sub is_connected { 124 return defined $_self->{device_name}; 125} 126 127sub device { 128 return $_self->{device_name}; 129} 130 131sub open_device { 132 my ( $class, %options ) = @_; 133 134 my $uuid = $uuid_object->create_str; 135 $callback{$uuid}{started} = $options{started_callback}; 136 $callback{$uuid}{running} = $options{running_callback}; 137 $callback{$uuid}{finished} = sub { 138 $_self->{device_name} = $options{device_name}; 139 $options{finished_callback}->(); 140 }; 141 $callback{$uuid}{error} = $options{error_callback}; 142 my $sentinel = 143 _enqueue_request( 'open', 144 { uuid => $uuid, device_name => $options{device_name} } ); 145 _monitor_process( $sentinel, $uuid ); 146 return; 147} 148 149sub close_device { 150 my ( $class, %options ) = @_; 151 152 my $uuid = $uuid_object->create_str; 153 $callback{$uuid}{started} = $options{started_callback}; 154 $callback{$uuid}{running} = $options{running_callback}; 155 $callback{$uuid}{finished} = sub { 156 $_self->{device_name} = $options{device_name}; 157 $options{finished_callback}->(); 158 }; 159 $callback{$uuid}{error} = $options{error_callback}; 160 my $sentinel = 161 _enqueue_request( 'close', 162 { uuid => $uuid, device_name => $options{device_name} } ); 163 _monitor_process( $sentinel, $uuid ); 164 return; 165} 166 167sub find_scan_options { 168 my ( 169 $class, $started_callback, $running_callback, 170 $finished_callback, $error_callback 171 ) = @_; 172 173 my $uuid = $uuid_object->create_str; 174 $callback{$uuid}{started} = $started_callback; 175 $callback{$uuid}{running} = $running_callback; 176 $callback{$uuid}{finished} = $finished_callback; 177 $callback{$uuid}{error} = $error_callback; 178 my $sentinel = _enqueue_request( 'get-options', { uuid => $uuid } ); 179 _monitor_process( $sentinel, $uuid ); 180 return; 181} 182 183sub set_option { 184 my ( $class, %options ) = @_; 185 186 my $uuid = $uuid_object->create_str; 187 $callback{$uuid}{started} = $options{started_callback}; 188 $callback{$uuid}{running} = $options{running_callback}; 189 $callback{$uuid}{finished} = $options{finished_callback}; 190 $callback{$uuid}{error} = $options{error_callback}; 191 my $sentinel = _enqueue_request( 192 'set-option', 193 { 194 index => $options{index}, 195 value => $options{value}, 196 uuid => $uuid, 197 } 198 ); 199 _monitor_process( $sentinel, $uuid ); 200 return; 201} 202 203sub scan_page { 204 my ( $class, %options ) = @_; 205 206 $_self->{abort_scan} = 0; 207 $_self->{scan_progress} = 0; 208 my $uuid = $uuid_object->create_str; 209 $callback{$uuid}{started} = $options{started_callback}; 210 $callback{$uuid}{running} = $options{running_callback}; 211 $callback{$uuid}{error} = $options{error_callback}; 212 $callback{$uuid}{finished} = $options{finished_callback}; 213 my $sentinel = _enqueue_request( 'scan-page', 214 { uuid => $uuid, path => "$options{path}" } ); 215 _monitor_process( $sentinel, $uuid ); 216 return; 217} 218 219sub scan_page_finished_callback { 220 my ( $status, $path, $n_scanned, %options ) = @_; 221 if ( defined $options{new_page_callback} 222 and not $_self->{abort_scan} 223 and ( $status == SANE_STATUS_GOOD or $status == SANE_STATUS_EOF ) ) 224 { 225 $options{new_page_callback}->( $status, $path, $options{start} ); 226 } 227 228 # Stop the process unless everything OK and more scans required 229 if ( 230 $_self->{abort_scan} 231 or ( $options{npages} and $n_scanned >= $options{npages} ) 232 or ( $status != SANE_STATUS_GOOD 233 and $status != SANE_STATUS_EOF ) 234 ) 235 { 236 if ( $_self->{abort_scan} ) { unlink $path } 237 _enqueue_request( 'cancel', { uuid => $uuid_object->create_str } ); 238 if ( _scanned_enough_pages( $status, $options{npages}, $n_scanned ) ) { 239 if ( defined $options{finished_callback} ) { 240 $options{finished_callback}->(); 241 } 242 } 243 else { 244 if ( defined $options{error_callback} ) { 245 $options{error_callback}->( Image::Sane::strstatus($status) ); 246 } 247 } 248 return; 249 } 250 elsif ( $options{cancel_between_pages} ) { 251 _enqueue_request( 'cancel', { uuid => $uuid_object->create_str } ); 252 } 253 254 if ( not defined $options{step} ) { $options{step} = 1 } 255 $options{start} += $options{step}; 256 Gscan2pdf::Frontend::Image_Sane->scan_page( 257 path => File::Temp->new( 258 DIR => $options{dir}, 259 SUFFIX => '.pnm', 260 UNLINK => FALSE, 261 ), 262 started_callback => $options{started_callback}, 263 running_callback => sub { 264 $options{running_callback}->( $_self->{scan_progress} ); 265 }, 266 error_callback => $options{error_callback}, 267 finished_callback => sub { 268 my ( $new_path, $new_status ) = @_; 269 scan_page_finished_callback( $new_status, $new_path, ++$n_scanned, 270 %options ); 271 }, 272 ); 273 return; 274} 275 276sub scan_pages { 277 my ( $class, %options ) = @_; 278 279 my $num_pages_scanned = 0; 280 Gscan2pdf::Frontend::Image_Sane->scan_page( 281 path => File::Temp->new( 282 DIR => $options{dir}, 283 SUFFIX => '.pnm', 284 UNLINK => FALSE, 285 ), 286 started_callback => $options{started_callback}, 287 running_callback => sub { 288 if ( defined $options{running_callback} ) { 289 $options{running_callback}->( $_self->{scan_progress} ); 290 } 291 }, 292 error_callback => $options{error_callback}, 293 finished_callback => sub { 294 my ( $path, $status ) = @_; 295 scan_page_finished_callback( $status, $path, ++$num_pages_scanned, 296 %options ); 297 }, 298 ); 299 return; 300} 301 302sub _scanned_enough_pages { 303 my ( $status, $nrequired, $ndone ) = @_; 304 return ( 305 $status == SANE_STATUS_GOOD 306 or $status == SANE_STATUS_EOF 307 or ( $status == SANE_STATUS_NO_DOCS 308 and ( $nrequired == 0 or $nrequired < $ndone ) ) 309 ); 310} 311 312# Flag the scan routine to abort 313 314sub cancel_scan { 315 my ( $self, $callback ) = @_; 316 317 # Empty process queue first to stop any new process from starting 318 $logger->info('Emptying process queue'); 319 while ( $_self->{requests}->dequeue_nb ) { } 320 321 # Then send the thread a cancel signal 322 $_self->{abort_scan} = 1; 323 324 my $uuid = $uuid_object->create_str; 325 $callback{$uuid}{cancelled} = $callback; 326 327 # Add a cancel request to ensure the reply is not blocked 328 $logger->info('Requesting cancel'); 329 my $sentinel = _enqueue_request( 'cancel', { uuid => $uuid } ); 330 _monitor_process( $sentinel, $uuid ); 331 return; 332} 333 334sub _thaw_deref { 335 my ($ref) = @_; 336 if ( defined $ref ) { 337 $ref = thaw($ref); 338 if ( ref($ref) eq 'SCALAR' ) { $ref = ${$ref} } 339 } 340 return $ref; 341} 342 343sub check_return_queue { 344 while ( defined( my $data = $_self->{return}->dequeue_nb() ) ) { 345 if ( not defined $data->{type} ) { 346 $logger->error("Bad data bundle $data in return queue."); 347 next; 348 } 349 if ( not defined $data->{uuid} ) { 350 $logger->error('Bad uuid in return queue.'); 351 next; 352 } 353 354 # if we have pressed the cancel button, ignore everything in the returns 355 # queue until it flags 'cancelled'. 356 if ( $_self->{cancel} ) { 357 if ( $data->{type} eq 'cancelled' ) { 358 $_self->{cancel} = FALSE; 359 if ( defined $callback{ $data->{uuid} }{cancelled} ) { 360 $callback{ $data->{uuid} }{cancelled} 361 ->( _thaw_deref( $data->{info} ) ); 362 delete $callback{ $data->{uuid} }; 363 } 364 } 365 else { 366 next; 367 } 368 } 369 370 if ( $data->{type} eq 'error' ) { 371 if ( $data->{status} == SANE_STATUS_NO_DOCS ) { 372 $data->{type} = 'finished'; 373 } 374 else { 375 if ( defined $callback{ $data->{uuid} }{error} ) { 376 $callback{ $data->{uuid} }{error} 377 ->( $data->{message}, $data->{status} ); 378 delete $callback{ $data->{uuid} }; 379 } 380 return Glib::SOURCE_CONTINUE; 381 } 382 } 383 if ( $data->{type} eq 'finished' ) { 384 if ( defined $callback{ $data->{uuid} }{started} ) { 385 $callback{ $data->{uuid} }{started}->(); 386 } 387 if ( defined $callback{ $data->{uuid} }{finished} ) { 388 if ( $data->{process} eq 'set-option' ) { 389 $callback{ $data->{uuid} }{finished} 390 ->( $data->{info}, $data->{status} ); 391 } 392 else { 393 $callback{ $data->{uuid} }{finished} 394 ->( _thaw_deref( $data->{info} ), $data->{status} ); 395 } 396 delete $callback{ $data->{uuid} }; 397 } 398 } 399 } 400 return Glib::SOURCE_CONTINUE; 401} 402 403sub _log2 { 404 my ($n) = @_; 405 return log($n) / log 2; 406} 407 408sub decode_info { 409 my ($info) = @_; 410 if ( $info == 0 ) { return 'none' } 411 my @opts = 412 qw(SANE_INFO_INEXACT SANE_INFO_RELOAD_OPTIONS SANE_INFO_RELOAD_PARAMS); 413 my @this; 414 my $n = _log2($info); 415 if ( $n > int $n ) { 416 $n = int($n) + 1; 417 } 418 my $i = @opts; 419 while ( $n > $i ) { 420 if ( $info >= 2**( $n - 1 ) ) { 421 push @this, q{?}; 422 $info -= 2**( $n - 1 ); 423 } 424 --$n; 425 } 426 while ( $n > $NOT_FOUND ) { 427 if ( $info >= 2**$n ) { 428 push @this, $opts[$n]; 429 $info -= 2**$n; 430 } 431 --$n; 432 } 433 return join ' + ', @this; 434} 435 436sub _thread_main { 437 my ($self) = @_; 438 439 while ( my $request = $self->{requests}->dequeue ) { 440 441 # Signal the sentinel that the request was started. 442 ${ $request->{sentinel} }++; 443 444 given ( $request->{action} ) { 445 when ('quit') { last } 446 when ('get-devices') { 447 _thread_get_devices( $self, $request->{uuid} ) 448 } 449 when ('open') { 450 _thread_open_device( $self, $request->{uuid}, 451 $request->{device_name} ) 452 } 453 when ('close') { 454 if ( defined( $self->{device_handle} ) ) { 455 $logger->debug("closing device '$self->{device_name}'"); 456 undef $self->{device_handle}; 457 } 458 else { 459 $logger->debug( 460 'Ignoring close_device() call - no device open.'); 461 } 462 } 463 when ('get-options') { 464 _thread_get_options( $self, $request->{uuid} ) 465 } 466 when ('set-option') { 467 _thread_set_option( $self, $request->{uuid}, $request->{index}, 468 $request->{value} ) 469 } 470 when ('scan-page') { 471 _thread_scan_page( $self, $request->{uuid}, $request->{path} ) 472 } 473 when ('cancel') { _thread_cancel( $self, $request->{uuid} ) } 474 default { 475 $logger->info("Ignoring unknown request $_"); 476 next; 477 } 478 } 479 480 # Signal the sentinel that the request was completed. 481 ${ $request->{sentinel} }++; 482 } 483 return; 484} 485 486sub _thread_get_devices { 487 my ( $self, $uuid ) = @_; 488 my @devices; 489 my $status = SANE_STATUS_GOOD; 490 try { 491 @devices = Image::Sane->get_devices; 492 } 493 catch { 494 $status = $_->status; 495 }; 496 $self->{return}->enqueue( 497 { 498 type => 'finished', 499 process => 'get-devices', 500 uuid => $uuid, 501 info => freeze( \@devices ), 502 status => $status, 503 } 504 ); 505 return; 506} 507 508sub _thread_throw_error { 509 my ( $self, $uuid, $process, $status, $message ) = @_; 510 $logger->info($message); 511 $self->{return}->enqueue( 512 { 513 type => 'error', 514 uuid => $uuid, 515 status => $status, 516 message => $message, 517 process => $process, 518 } 519 ); 520 return; 521} 522 523sub _thread_open_device { 524 my ( $self, $uuid, $device_name ) = @_; 525 526 if ( not defined $device_name or $device_name eq $EMPTY ) { 527 _thread_throw_error( $self, $uuid, 'open-device', 528 SANE_STATUS_ACCESS_DENIED, 'Cannot open undefined device' ); 529 return; 530 } 531 532 # close the handle 533 if ( defined( $self->{device_handle} ) ) { 534 undef $self->{device_handle}; 535 Image::Sane::_exit(); ## no critic (ProtectPrivateSubs) 536 } 537 538 my $status = SANE_STATUS_GOOD; 539 try { 540 $self->{device_handle} = Image::Sane::Device->open($device_name); 541 $self->{device_name} = $device_name; 542 $logger->debug("opened device '$self->{device_name}'"); 543 } 544 catch { 545 $status = $_->status; 546 _thread_throw_error( $self, $uuid, 'open-device', $status, 547 "opening device '$device_name': " . $_->error ); 548 }; 549 $self->{return}->enqueue( 550 { 551 type => 'finished', 552 process => 'open-device', 553 uuid => $uuid, 554 info => freeze( \$device_name ), 555 status => $status, 556 } 557 ); 558 return; 559} 560 561sub _thread_get_options { 562 my ( $self, $uuid ) = @_; 563 my @options; 564 565 # We got a device, find out how many options it has: 566 my $status = SANE_STATUS_GOOD; 567 my $num_dev_options; 568 try { 569 $num_dev_options = $self->{device_handle}->get_option(0); 570 } 571 catch { 572 $status = $_->status; 573 _thread_throw_error( $self, $uuid, 'get-options', $status, 574 'unable to determine option count: ' . $_->error ); 575 }; 576 $logger->debug("Backend reports $num_dev_options options"); 577 578 if ( $status == SANE_STATUS_GOOD ) { 579 580 # if we can retrieve at least one good option, then do so. 581 # otherwise return the error message 582 $status = $LARGE_STATUS; 583 for my $i ( 1 .. $num_dev_options - 1 ) { 584 my $opt; 585 try { 586 $opt = $self->{device_handle}->get_option_descriptor($i); 587 } 588 catch { 589 $status = $_->status; 590 _thread_throw_error( $self, $uuid, 'get_option_descriptor', 591 $status, 592 "error getting option descriptor $i: " . $_->error ); 593 }; 594 $options[$i] = $opt; 595 if ( 596 $opt->{cap} & SANE_CAP_SOFT_DETECT 597 and not( ( $opt->{cap} & SANE_CAP_INACTIVE ) 598 or ( $opt->{type} == SANE_TYPE_BUTTON ) 599 or ( $opt->{type} == SANE_TYPE_GROUP ) ) 600 ) 601 { 602 try { 603 $opt->{val} = $self->{device_handle}->get_option($i); 604 $status = SANE_STATUS_GOOD; 605 } 606 catch { 607 if ( $_->status < $status ) { $status = $_->status } 608 $opt->{cap} = 0; 609 $logger->warn( "Error getting option $i. ", Dumper($opt) ); 610 }; 611 } 612 } 613 if ( $status != SANE_STATUS_GOOD ) { 614 _thread_throw_error( $self, $uuid, 'get_option', $status, 615 'no options fetched: ' . $_->error ); 616 } 617 } 618 $self->{return}->enqueue( 619 { 620 type => 'finished', 621 process => 'get-options', 622 uuid => $uuid, 623 info => freeze( \@options ), 624 status => $status, 625 } 626 ); 627 return $status; 628} 629 630sub _thread_set_option { 631 my ( $self, $uuid, $index, $value ) = @_; 632 my $opt = $self->{device_handle}->get_option_descriptor($index); 633 if ( $opt->{type} == SANE_TYPE_BOOL and $value eq $EMPTY ) { $value = 0 } 634 635 # FIXME: Stringification to force this SV to have a PV slot. This seems to 636 # be necessary to get through Sane.pm's value checks. 637 if ( $opt->{type} != SANE_TYPE_BUTTON ) { $value = "$value" } 638 639 my $status = SANE_STATUS_GOOD; 640 my $info; 641 try { 642 $info = $self->{device_handle}->set_option( $index, $value ); 643 } 644 catch { 645 $status = $_->status; 646 }; 647 if ( $logger->is_info ) { 648 $logger->info( 649 "sane_set_option $index ($opt->{name})" 650 . ( $opt->{type} == SANE_TYPE_BUTTON ? $EMPTY : " to $value" ) 651 . " returned status $status (" 652 . Image::Sane::strstatus($status) 653 . ') with info ' 654 . ( 655 defined $info 656 ? sprintf( '%d (%s)', 657 $info, Gscan2pdf::Frontend::Image_Sane::decode_info($info) ) 658 : 'undefined' 659 ) 660 ); 661 } 662 $self->{return}->enqueue( 663 { 664 type => 'finished', 665 process => 'set-option', 666 uuid => $uuid, 667 status => $status, 668 info => $info, 669 } 670 ); 671 return; 672} 673 674sub _thread_write_pnm_header { 675 my ( $fh, $format, $width, $height, $depth ) = @_; 676 677 # The netpbm-package does not define raw image data with maxval > 255. 678 # But writing maxval 65535 for 16bit data gives at least a chance 679 # to read the image. 680 681 if ( $format == SANE_FRAME_RED 682 or $format == SANE_FRAME_GREEN 683 or $format == SANE_FRAME_BLUE 684 or $format == SANE_FRAME_RGB ) 685 { 686 printf {$fh} "P6\n# SANE data follows\n%d %d\n%d\n", $width, $height, 687 ( $depth > $_8_BIT ) ? $MAXVAL_16_BIT : $MAXVAL_8_BIT; 688 } 689 else { 690 if ( $depth == 1 ) { 691 printf {$fh} "P4\n# SANE data follows\n%d %d\n", $width, $height; 692 } 693 else { 694 printf {$fh} "P5\n# SANE data follows\n%d %d\n%d\n", $width, 695 $height, 696 ( $depth > $_8_BIT ) ? $MAXVAL_16_BIT : $MAXVAL_8_BIT; 697 } 698 } 699 return; 700} 701 702sub _thread_scan_page_to_fh { 703 my ( $device, $fh ) = @_; 704 my $first_frame = 1; 705 my $offset = 0; 706 my $must_buffer = 0; 707 my ( %image, $status ); 708 my @format_name = qw( gray RGB red green blue ); 709 my $total_bytes = 0; 710 711 my ( $parm, $last_frame ); 712 while ( not $last_frame ) { 713 $status = SANE_STATUS_GOOD; 714 if ( not $first_frame ) { 715 try { 716 $device->start; 717 } 718 catch { 719 $status = $_->status; 720 $logger->info( "$prog_name: sane_start: " . $_->error ); 721 }; 722 if ( $status != SANE_STATUS_GOOD ) { goto CLEANUP } 723 } 724 725 try { 726 $parm = $device->get_parameters; 727 } 728 catch { 729 $status = $_->status; 730 $logger->info( "$prog_name: sane_get_parameters: " . $_->error ); 731 }; 732 if ( $status != SANE_STATUS_GOOD ) { goto CLEANUP } 733 734 _log_frame_info( $first_frame, $parm, \@format_name ); 735 ( $must_buffer, $offset ) = 736 _initialise_scan( $fh, $first_frame, $parm ); 737 my $hundred_percent = _scan_data_size($parm); 738 739 while (1) { 740 741 # Pick up flag from cancel_scan() 742 if ( $_self->{abort_scan} ) { 743 $device->cancel; 744 $logger->info('Scan cancelled'); 745 return SANE_STATUS_CANCELLED; 746 } 747 748 my ( $buffer, $len ); 749 try { 750 ( $buffer, $len ) = $device->read($BUFFER_SIZE); 751 $total_bytes += $len; 752 } 753 catch { 754 $status = $_->status; 755 $logger->info( "$prog_name: sane_read: " . $_->error ); 756 }; 757 my $progr = $total_bytes / $hundred_percent; 758 if ( $progr > 1 ) { $progr = 1 } 759 $_self->{scan_progress} = $progr; 760 761 if ( $status != SANE_STATUS_GOOD ) { 762 if ( $parm->{depth} == $_8_BIT ) { 763 $logger->info( 764 sprintf "$prog_name: min/max graylevel value = %d/%d", 765 $MAXVAL_8_BIT, 0 ); 766 } 767 if ( $status != SANE_STATUS_EOF ) { return $status } 768 last; 769 } 770 771 if ($must_buffer) { 772 $offset = 773 _buffer_scan( $offset, $parm, \%image, $len, $buffer ); 774 } 775 else { 776 goto CLEANUP if not print {$fh} $buffer; 777 } 778 } 779 $first_frame = 0; 780 $last_frame = $parm->{last_frame}; 781 } 782 783 if ($must_buffer) { _write_buffer_to_fh( $fh, $parm, \%image ) } 784 785 CLEANUP: 786 my $expected_bytes = 787 $parm->{bytes_per_line} * $parm->{lines} * _number_frames($parm); 788 if ( $parm->{lines} < 0 ) { $expected_bytes = 0 } 789 if ( $total_bytes > $expected_bytes and $expected_bytes != 0 ) { 790 $logger->info( 791 sprintf '%s: WARNING: read more data than announced by backend ' 792 . '(%u/%u)', 793 $prog_name, $total_bytes, $expected_bytes ); 794 } 795 else { 796 $logger->info( sprintf '%s: read %u bytes in total', 797 $prog_name, $total_bytes ); 798 } 799 return $status; 800} 801 802sub _thread_scan_page { 803 my ( $self, $uuid, $path ) = @_; 804 805 if ( not defined( $self->{device_handle} ) ) { 806 _thread_throw_error( $self, $uuid, 'scan-page', 807 SANE_STATUS_ACCESS_DENIED, 808 "$prog_name: must open device before starting scan" ); 809 return; 810 } 811 my $status = SANE_STATUS_GOOD; 812 try { 813 $self->{device_handle}->start; 814 } 815 catch { 816 $status = $_->status; 817 _thread_throw_error( $self, $uuid, 'scan-page', $status, 818 "$prog_name: sane_start: " . $_->error ); 819 unlink $path; 820 }; 821 if ( $status != SANE_STATUS_GOOD ) { return } 822 823 my $fh; 824 if ( not open $fh, '>', $path ) { 825 $self->{device_handle}->cancel; 826 _thread_throw_error( $self, $uuid, 'scan-page', 827 SANE_STATUS_ACCESS_DENIED, "Error writing to $path" ); 828 return; 829 } 830 831 $status = _thread_scan_page_to_fh( $self->{device_handle}, $fh ); 832 833 if ( not close $fh ) { 834 $self->{device_handle}->cancel; 835 _thread_throw_error( $self, $uuid, 'scan-page', 836 SANE_STATUS_ACCESS_DENIED, "Error closing $path" ); 837 return; 838 } 839 840 $logger->info( sprintf 'Scanned page %s. (scanner status = %d)', 841 $path, $status ); 842 843 if ( $status != SANE_STATUS_GOOD and $status != SANE_STATUS_EOF ) { 844 unlink $path; 845 } 846 847 $self->{return}->enqueue( 848 { 849 type => 'finished', 850 process => 'scan-page', 851 uuid => $uuid, 852 status => $status, 853 info => freeze( \$path ), 854 } 855 ); 856 return; 857} 858 859sub _thread_cancel { 860 my ( $self, $uuid ) = @_; 861 if ( defined $self->{device_handle} ) { $self->{device_handle}->cancel } 862 $self->{return}->enqueue( { type => 'cancelled', uuid => $uuid } ); 863 return; 864} 865 866sub _log_frame_info { 867 my ( $first_frame, $parm, $format_name ) = @_; 868 if ($first_frame) { 869 if ( $parm->{lines} >= 0 ) { 870 $logger->info( 871 sprintf "$prog_name: scanning image of size %dx%d pixels at " 872 . '%d bits/pixel', 873 $parm->{pixels_per_line}, 874 $parm->{lines}, 875 $_8_BIT * $parm->{bytes_per_line} / $parm->{pixels_per_line} 876 ); 877 } 878 else { 879 $logger->info( 880 sprintf "$prog_name: scanning image %d pixels wide and " 881 . 'variable height at %d bits/pixel', 882 $parm->{pixels_per_line}, 883 $_8_BIT * $parm->{bytes_per_line} / $parm->{pixels_per_line} 884 ); 885 } 886 887 $logger->info( 888 sprintf "$prog_name: acquiring %s frame", 889 $parm->{format} <= SANE_FRAME_BLUE 890 ? $format_name->[ $parm->{format} ] 891 : 'Unknown' 892 ); 893 } 894 return; 895} 896 897sub _initialise_scan { 898 my ( $fh, $first_frame, $parm ) = @_; 899 my ( $must_buffer, $offset ); 900 if ($first_frame) { 901 if ( $parm->{format} == SANE_FRAME_RED 902 or $parm->{format} == SANE_FRAME_GREEN 903 or $parm->{format} == SANE_FRAME_BLUE ) 904 { 905 if ( $parm->{depth} != $_8_BIT ) { 906 die "Red/Green/Blue frames require depth=$_8_BIT\n"; 907 } 908 $must_buffer = 1; 909 $offset = $parm->{format} - SANE_FRAME_RED; 910 } 911 elsif ( $parm->{format} == SANE_FRAME_RGB ) { 912 if ( ( $parm->{depth} != $_8_BIT ) 913 and ( $parm->{depth} != $_16_BIT ) ) 914 { 915 die "RGB frames require depth=$_8_BIT or $_16_BIT\n"; 916 } 917 } 918 if ( $parm->{format} == SANE_FRAME_RGB 919 or $parm->{format} == SANE_FRAME_GRAY ) 920 { 921 if ( ( $parm->{depth} != 1 ) 922 and ( $parm->{depth} != $_8_BIT ) 923 and ( $parm->{depth} != $_16_BIT ) ) 924 { 925 die "Valid depths are 1, $_8_BIT or $_16_BIT\n"; 926 } 927 if ( $parm->{lines} < 0 ) { 928 $must_buffer = 1; 929 $offset = 0; 930 } 931 else { 932 _thread_write_pnm_header( $fh, $parm->{format}, 933 $parm->{pixels_per_line}, 934 $parm->{lines}, $parm->{depth} ); 935 } 936 } 937 } 938 else { 939 die "Encountered unknown format\n" 940 if ( $parm->{format} < SANE_FRAME_RED 941 or $parm->{format} > SANE_FRAME_BLUE ); 942 $offset = $parm->{format} - SANE_FRAME_RED; 943 } 944 return ( $must_buffer, $offset ); 945} 946 947# Return size of final scan (ignoring header) 948 949sub _scan_data_size { 950 my ($parm) = @_; 951 return $parm->{bytes_per_line} * $parm->{lines} * _number_frames($parm); 952} 953 954# Return number of frames 955 956sub _number_frames { 957 my ($parm) = @_; 958 return ( 959 $parm->{format} == SANE_FRAME_RGB 960 or $parm->{format} == SANE_FRAME_GRAY 961 ) 962 ? 1 963 : 3; ## no critic (ProhibitMagicNumbers) 964} 965 966# We're either scanning a multi-frame image or the 967# scanner doesn't know what the eventual image height 968# will be (common for hand-held scanners). In either 969# case, we need to buffer all data before we can write 970# the header 971 972sub _buffer_scan { 973 my ( $offset, $parm, $image, $len, $buffer ) = @_; 974 975 my $number_frames = _number_frames($parm); 976 for ( 0 .. $len - 1 ) { 977 $image->{data}[ $offset + $number_frames * $_ ] = substr $buffer, $_, 1; 978 } 979 $offset += $number_frames * $len; 980 return $offset; 981} 982 983sub _write_buffer_to_fh { 984 my ( $fh, $parm, $image ) = @_; 985 if ( $parm->{lines} > 0 ) { 986 $image->{height} = $parm->{lines}; 987 } 988 else { 989 $image->{height} = @{ $image->{data} } / $parm->{bytes_per_line}; 990 $image->{height} /= _number_frames($parm); 991 } 992 _thread_write_pnm_header( $fh, $parm->{format}, $parm->{pixels_per_line}, 993 $image->{height}, $parm->{depth} ); 994 for my $data ( @{ $image->{data} } ) { 995 goto CLEANUP if not print {$fh} $data; 996 } 997 return; 998} 999 10001; 1001 1002__END__ 1003