Skip to content

Commit

Permalink
align the API, document it too
Browse files Browse the repository at this point in the history
  • Loading branch information
dk committed Nov 3, 2023
1 parent 202d869 commit 2c97da6
Show file tree
Hide file tree
Showing 7 changed files with 205 additions and 29 deletions.
2 changes: 2 additions & 0 deletions Prima.pm
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,8 @@ L<Prima::Image::Animate> - animate gif and webp files
L<Prima::Image::base64> - hardcoded image files
L<Prima::Image::Loader> - per-frame image loading and saving
L<Prima::IniFile> - support of Windows-like initialization files
L<Prima::StdBitmap> - shared access to the standard bitmaps
Expand Down
4 changes: 2 additions & 2 deletions Prima/Image/Animate.pm
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
150 changes: 131 additions & 19 deletions Prima/Image/Loader.pm
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -32,34 +37,20 @@ 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 ) = @_;

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;
Expand All @@ -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} }

Expand Down Expand Up @@ -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<Prima::Image::Loader> and C<Prima::Image::Saver> 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<Prima::Image/load> except C<map>, C<loadAll>, and C<profiles>.
The other options apply to each frame that will be coonsequently loaded, but these
options could be overridden by supplying parameters to the C<next> call.
Returns either a new loader object, or C<undef> and the error string.
Note that it is possible to supply the C<onHeaderReady> and C<onDataReady>
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<next> 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<undef> and the error string.
=item source
Return the filename or the file handle passed to the C<new> 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<Prima::Image/save> except C<images>. The other
options apply to each frame that will be coonsequently saved, but these options
could be overridden by supplying parameters to the C<save> call.
Returns either a new saver object, or C<undef> 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, E<lt>[email protected]E<gt>.
=head1 SEE ALSO
L<Prima::Dialog::ImageDialog>.
=cut
6 changes: 3 additions & 3 deletions class/Image/io.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
17 changes: 14 additions & 3 deletions img/load.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions pod/Prima/Image.pod
Original file line number Diff line number Diff line change
Expand Up @@ -822,7 +822,7 @@ and
my $x = Prima::Image-> load( ... );
die "$@" unless $x;

See L<Prima::image-load> for details.
See L<Prima::image-load> for details and L<Prima::Image::Loader> for more functionality.

Note: when loading from streams on win32, mind the C<binmode>.

Expand Down Expand Up @@ -905,7 +905,7 @@ string.
Note that when saving to a stream, C<codecID> must be explicitly given in
C<%PARAMETERS>.

See L<Prima::image-load> for details.
See L<Prima::image-load> for details and L<Prima::Image::Loader/Prima::Image::Saver> for more functionality.

Note: when saving to streams on win32, mind the C<binmode>.

Expand Down
51 changes: 51 additions & 0 deletions pod/Prima/image-load.pod
Original file line number Diff line number Diff line change
Expand Up @@ -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<Prima::Image::Loader> .

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<load> 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<extras> hash on the caller image object may be filled,
depending on the C<loadExtras> 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<load> with
the C<< session => 1 >> option but with the first parameter set to undef will
load the next frame. Each of those C<load> 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<map>, C<profiles> and
C<loadAll> 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
Expand Down Expand Up @@ -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<Prima::Image::Loader/Prima::Image::Saver> .

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<save> call, and the
C<frames> 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<save> with
the C<< session => 1 >> and the image as the first option option would save the
next frame. Each of those C<save> 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<images> option. The saving session is
closed automatically after a first failure.

=head2 Selecting a codec

The integer field C<codecID>, the same field that is defined after successful
Expand Down

0 comments on commit 2c97da6

Please sign in to comment.