From 2c97da6db00007430d4199363f999b0449f91641 Mon Sep 17 00:00:00 2001 From: Dmitry Karasik Date: Fri, 3 Nov 2023 13:02:55 +0100 Subject: [PATCH] align the API, document it too --- Prima.pm | 2 + Prima/Image/Animate.pm | 4 +- Prima/Image/Loader.pm | 150 ++++++++++++++++++++++++++++++++++----- class/Image/io.c | 6 +- img/load.c | 17 ++++- pod/Prima/Image.pod | 4 +- pod/Prima/image-load.pod | 51 +++++++++++++ 7 files changed, 205 insertions(+), 29 deletions(-) diff --git a/Prima.pm b/Prima.pm index 16e3b7685..e50a24c2c 100644 --- a/Prima.pm +++ b/Prima.pm @@ -522,6 +522,8 @@ L - animate gif and webp files L - hardcoded image files +L - per-frame image loading and saving + L - support of Windows-like initialization files L - shared access to the standard bitmaps diff --git a/Prima/Image/Animate.pm b/Prima/Image/Animate.pm index f4cfb4452..adbb5f2a1 100644 --- a/Prima/Image/Animate.pm +++ b/Prima/Image/Animate.pm @@ -160,7 +160,7 @@ sub reset return unless @$i; $ix = $i-> [0]; } else { - $self->{loader}->rewind; + $self->{loader}->current(0); $ix = $self->load_next_image; } return unless $ix; @@ -200,7 +200,7 @@ sub advance_frame } elsif ( $curr >= 0 ) { $self->{image} = $self-> load_next_image; } elsif ( $curr == -2 ) { - $self-> {loader}->rewind; + $self-> {loader}->current(0); $self->{image} = $self-> load_next_image; } else { $self->{image} = $oimg; diff --git a/Prima/Image/Loader.pm b/Prima/Image/Loader.pm index 8d7dc1b9c..a95173b0f 100644 --- a/Prima/Image/Loader.pm +++ b/Prima/Image/Loader.pm @@ -10,7 +10,12 @@ sub new $opt{className} = 'Prima::Icon' if delete $opt{icons}; - my $img = Prima::Image->new; + my %events; + while ( my ($k, $v) = each %opt) { + $events{$k} = $v if $k =~ /^on/; + } + + my $img = Prima::Image->new( %events ); my $ok = $img->load( $source, loadExtras => 1, wantFrames => 1, @@ -32,24 +37,10 @@ sub new extras => $img->{extras}, frames => $img->{extras}->{frames}, current => 0, + events => \%events, }, $class; } -sub rewind -{ - my ( $self, $index ) = @_; - - if (defined $self->{error} && $self->{error} ne "End of image list") { - Carp::carp("cannot rewind after an error is encountered"); - return; - } - - $index //= 0; - $self->{rewind_request} = $index; - $self->{current} = $index; - delete $self->{error}; -} - sub next { my ( $self, %opt ) = @_; @@ -57,9 +48,9 @@ sub next return (undef, $self->{error}) if defined $self->{error}; my $new_frame; - $new_frame = $opt{rewind} = delete $self->{rewind_request} + $new_frame = $opt{index} = delete $self->{rewind_request} if defined $self->{rewind_request}; - my @img = $self->{image}->load( undef, %opt, session => 1 ); + my @img = $self->{image}->load( undef, %{ $self->{events} } , %opt, session => 1 ); if ( $img[0] ) { my $e = $img[0]->{extras}; %{ $self->{extras} } = ( %{ $self->{extras} }, %$e ) if $e; @@ -82,9 +73,23 @@ sub eof return $current >= $self->{frames}; } +sub current +{ + return $_[0]->{current} unless $#_; + + my ( $self, $index ) = @_; + + if (defined $self->{error} && $self->{error} ne "End of image list") { + Carp::carp("cannot rewind after an error is encountered"); + return; + } + + $self->{current} = $self->{rewind_request} = $index; + delete $self->{error}; +} + sub extras { shift->{extras} } sub frames { shift->{frames} } -sub current { shift->{current} } sub source { shift->{source} } sub DESTROY { $_[0]->{image}-> destroy if $_[0]->{image} } @@ -127,3 +132,110 @@ sub DESTROY { $_[0]->{image}-> destroy if $_[0]->{image} } 1; +=pod + +=head1 NAME + +Prima::Image::Loader - per-frame image loading and saving + +=head1 DESCRIPTION + +The toolkit provides functionality for session-based loadign and saving of +multiframe images to that is is not needed to store all images in memory at +once. Instead, the C and C classes +provide the API for operating on a single frame at a time. + +=head1 Prima::Image::Loader + + use Prima::Image::Loader; + my $l = Prima::Image::Loader->new($ARGV[0]); + printf "$ARGV[0]: %d frames\n", $l->frames; + while ( !$l->eof ) { + my ($i,$err) = $l->next; + die $err unless $i; + printf "$n: %d x %d\n", $i->size; + } + +=over + +=item new FILENAME|FILEHANDLE, %OPTIONS + +Opens a filename or a file handle, tries to deduce if the toolkit can recognize +the image, and creates an image loading handler. The C<%OPTIONS> are same as +recognized by L except C, C, and C. +The other options apply to each frame that will be coonsequently loaded, but these +options could be overridden by supplying parameters to the C call. + +Returns either a new loader object, or C and the error string. + +Note that it is possible to supply the C and C +callbacks in the options, however, note that the first arguments in these +callbacks will the newly created image. + +=item current INDEX + +Manages the index of the frame that will be loaded next. When set, requests +repositioning of the frame pointer so that the next call to C would load +the INDEXth image. + +=item eof + +Return the boolean flag is the end of the file is reached. + +=item extras + +Returns the hash of extra file data filled by the codec + +=item frames + +Returns number of frames in the file + +=item next %OPTIONS + +Loads the next image frame. + +Returns either a newly loaded image, or C and the error string. + +=item source + +Return the filename or the file handle passed to the C call. + +=back + +=head1 Prima::Image::Saver + + my $fn = '1.webp'; + open F, ">", $fn or die $!; + my ($s,$err) = Prima::Image::Saver->new(\*F, frames => scalar(@images)); + die $err unless $s; + for my $image (@images) { + my ($ok,$err) = $s->save($image); + next if $ok; + unlink $fn; + die $err; + } + +=item new FILENAME|FILEHANDLE, %OPTIONS + +Opens a filename or a file handle and an image saving handler. The C<%OPTIONS> +are same as recognized by L except C. The other +options apply to each frame that will be coonsequently saved, but these options +could be overridden by supplying parameters to the C call. + +Returns either a new saver object, or C and the error string. + +=item save %OPTIONS + +Saves the next image frame. + +Returns a success boolean flag and an eventual error string + +=head1 AUTHOR + +Dmitry Karasik, Edmitry@karasik.eu.orgE. + +=head1 SEE ALSO + +L. + +=cut diff --git a/class/Image/io.c b/class/Image/io.c index 3945e6294..bdf9f4f54 100644 --- a/class/Image/io.c +++ b/class/Image/io.c @@ -139,9 +139,9 @@ XS( Image_load_FROMPERL) croak("Another loading session is in progress"); load_next_frame = true; - if ( pexist(rewind)) { - (( PImgLoadFileInstance) var-> loading_session)->frame = pget_i(rewind); - pdelete(rewind); + if ( pexist(index)) { + (( PImgLoadFileInstance) var-> loading_session)->frame = pget_i(index); + pdelete(index); } } else open_load = true; diff --git a/img/load.c b/img/load.c index badba7118..9343e38c5 100644 --- a/img/load.c +++ b/img/load.c @@ -483,9 +483,20 @@ apc_img_load_next_frame( Handle target, PImgLoadFileInstance fi, HV * profile, c /* create storage */ if (target == NULL_HANDLE) { - HV * profile = newHV(); - fi->object = Object_create( className, profile); - sv_free(( SV *) profile); + HE * he; + HV * hv = newHV(); + hv_iterinit( profile); + while (( he = hv_iternext( profile)) != NULL) { + char *key = HeKEY(he); + if ( key && key[0] == 'o' && key[1] == 'n') { + SV ** holder; + holder = hv_fetch( profile, key, HeKLEN(he), 0); + if ( holder == NULL || !SvOK( *holder)) continue; + (void) hv_store( hv, key, HeKLEN(he), newSVsv(*holder), 0); + } + } + fi->object = Object_create( className, hv); + sv_free(( SV *) hv); if ( !fi->object) outd("Failed to create object '%s'", className); } else diff --git a/pod/Prima/Image.pod b/pod/Prima/Image.pod index 45a1cd162..4dabe1f52 100644 --- a/pod/Prima/Image.pod +++ b/pod/Prima/Image.pod @@ -822,7 +822,7 @@ and my $x = Prima::Image-> load( ... ); die "$@" unless $x; -See L for details. +See L for details and L for more functionality. Note: when loading from streams on win32, mind the C. @@ -905,7 +905,7 @@ string. Note that when saving to a stream, C must be explicitly given in C<%PARAMETERS>. -See L for details. +See L for details and L for more functionality. Note: when saving to streams on win32, mind the C. diff --git a/pod/Prima/image-load.pod b/pod/Prima/image-load.pod index d99dd616d..3ea4c66ef 100644 --- a/pod/Prima/image-load.pod +++ b/pod/Prima/image-load.pod @@ -285,6 +285,33 @@ appropriate codec was compiled in, the following would work: ICON print $icon->save_stream; +=head2 Reading one frame at a time + +When one needs to load all frames from an image that contains too many frames, +or there is a constraint on memory, Prima provides a way to load images one by +one, without needing to allocate space for all frames in the file. + +This section describes the lower-level API that allows for that functionality, +however, an easier-to-use higher level API is documented in L . + +In order to read one frame at a time, the programmer needs to open a loading +session, by adding the C<< session => 1 >> option to the C call; that +call can only be made on an existing object, not on the package. The call would +return the success flag and an eventual error in C<$@>, as usual. No frames are +loaded yet, though the C hash on the caller image object may be filled, +depending on the C option. The options supplied to the session +opening call would apply to all subsequent frames, but these settings may be +overridden later. + + +Having the session successfully opened, the subsequent calls to C with +the C<< session => 1 >> option but with the first parameter set to undef will +load the next frame. Each of those C call will recognize the options +supplied and will apply them to indifidual frames. The session-based loading will +recognize all of the function options, except the C, C and +C options. The loading session is closed automatically after either a +first loading failure or after the end of file is reached. + =head1 Saving =head2 Simple saving @@ -337,6 +364,30 @@ values raise an error. $x-> {extras}-> {comments} = 'Created by Prima'; $x-> save( $f); +=head2 Saving one frame at a time + +Similar to the session-based loading, Prima provides the functionality to +save a multi-frame image with one frame at a time, using the similar API calls. + +This section describes the lower-level API that allows for that functionality, +however, an easier-to-use higher level API is documented in +L . + +In order to save one frame at a time, the programmer needs to open a saving +session, by adding the C<< session => 1 >> option to the C call, and the +C options that signals how many frames are to be saved in total; that +call can only be made on an existing object, not on the package. The call would +return the success flag and an eventual error in C<$@>, as usual. The options +supplied to the session opening call would apply to all subsequent frames, but +these settings may be overridden later. + +Having the session successfully opened, the subsequent calls to C with +the C<< session => 1 >> and the image as the first option option would save the +next frame. Each of those C call will recognize the options supplied and +will apply them to indifidual frames. The session-based saving will recognize +all of the function options, except the C option. The saving session is +closed automatically after a first failure. + =head2 Selecting a codec The integer field C, the same field that is defined after successful