X-Git-Url: http://git.imager.perl.org/imager.git/blobdiff_plain/d06f6707ebf1defe71d9f42754c0f27c2de4a4cc..d5fb1fdfe77529d2d55e62dc4024b51c3e44e60b:/Imager.pm diff --git a/Imager.pm b/Imager.pm index 1ac78637..869dabc4 100644 --- a/Imager.pm +++ b/Imager.pm @@ -1,7 +1,7 @@ package Imager; use strict; -use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS %formats $DEBUG %filters %DSOs $ERRSTR $fontstate %OPCODES $I2P $FORMATGUESS $warn_obsolete); +use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS %formats $DEBUG %filters %DSOs $ERRSTR %OPCODES $I2P $FORMATGUESS $warn_obsolete); use IO::File; use Imager::Color; @@ -74,24 +74,6 @@ use Imager::Font; i_tt_text i_tt_bbox - i_readjpeg_wiol - i_writejpeg_wiol - - i_readtiff_wiol - i_writetiff_wiol - i_writetiff_wiol_faxable - - i_readpng_wiol - i_writepng_wiol - - i_readgif - i_readgif_wiol - i_readgif_callback - i_writegif - i_writegifmc - i_writegif_gen - i_writegif_callback - i_readpnm_wiol i_writeppm_wiol @@ -117,6 +99,7 @@ use Imager::Font; newcolour NC NF + NCF ); @EXPORT=qw( @@ -136,6 +119,7 @@ use Imager::Font; newcolor NF NC + NCF )], all => [@EXPORT_OK], default => [qw( @@ -155,10 +139,23 @@ my %attempted_to_load; # library keys that are image file formats my %file_formats = map { $_ => 1 } qw/tiff pnm gif png jpeg raw bmp tga/; +# image pixel combine types +my @combine_types = + qw/none normal multiply dissolve add subtract diff lighten darken + hue saturation value color/; +my %combine_types; +@combine_types{@combine_types} = 0 .. $#combine_types; +$combine_types{mult} = $combine_types{multiply}; +$combine_types{'sub'} = $combine_types{subtract}; +$combine_types{sat} = $combine_types{saturation}; + +# this will be used to store global defaults at some point +my %defaults; + BEGIN { require Exporter; @ISA = qw(Exporter); - $VERSION = '0.61'; + $VERSION = '0.80'; eval { require XSLoader; XSLoader::load(Imager => $VERSION); @@ -170,19 +167,21 @@ BEGIN { } } -BEGIN { - i_init_fonts(); # Initialize font engines - Imager::Font::__init(); - for(i_list_formats()) { $formats{$_}++; } +my %formats_low; +my %format_classes = + ( + png => "Imager::File::PNG", + gif => "Imager::File::GIF", + tiff => "Imager::File::TIFF", + jpeg => "Imager::File::JPEG", + w32 => "Imager::Font::W32", + ft2 => "Imager::Font::FT2", + ); - if ($formats{'t1'}) { - i_t1_set_aa(1); - } +tie %formats, "Imager::FORMATS", \%formats_low, \%format_classes; - if (!$formats{'t1'} and !$formats{'tt'} - && !$formats{'ft2'} && !$formats{'w32'}) { - $fontstate='no font support'; - } +BEGIN { + for(i_list_formats()) { $formats_low{$_}++; } %OPCODES=(Add=>[0],Sub=>[1],Mult=>[2],Div=>[3],Parm=>[4],'sin'=>[5],'cos'=>[6],'x'=>[4,0],'y'=>[4,1]); @@ -215,6 +214,13 @@ BEGIN { callsub => sub { my %hsh=@_; i_hardinvert($hsh{image}); } }; + $filters{hardinvertall} = + { + callseq => ['image'], + defaults => { }, + callsub => sub { my %hsh=@_; i_hardinvertall($hsh{image}); } + }; + $filters{autolevels} ={ callseq => ['image','lsat','usat','skew'], defaults => { lsat=>0.1,usat=>0.1,skew=>0.0 }, @@ -233,11 +239,17 @@ BEGIN { callsub => sub { my %hsh=@_; i_radnoise($hsh{image},$hsh{xo},$hsh{yo},$hsh{rscale},$hsh{ascale}); } }; - $filters{conv} ={ - callseq => ['image', 'coef'], - defaults => { }, - callsub => sub { my %hsh=@_; i_conv($hsh{image},$hsh{coef}); } - }; + $filters{conv} = + { + callseq => ['image', 'coef'], + defaults => { }, + callsub => + sub { + my %hsh=@_; + i_conv($hsh{image},$hsh{coef}) + or die Imager->_error_as_msg() . "\n"; + } + }; $filters{gradgen} = { @@ -307,12 +319,18 @@ BEGIN { cd => 1.0, cs => 40, n => 1.3, - Ia => Imager::Color->new(rgb=>[0,0,0]), - Il => Imager::Color->new(rgb=>[255,255,255]), - Is => Imager::Color->new(rgb=>[255,255,255]), + Ia => [0,0,0], + Il => [255,255,255], + Is => [255,255,255], }, callsub => sub { my %hsh = @_; + for my $cname (qw/Ia Il Is/) { + my $old = $hsh{$cname}; + my $new_color = _color($old) + or die $Imager::ERRSTR, "\n"; + $hsh{$cname} = $new_color; + } i_bumpmap_complex($hsh{image}, $hsh{bump}{IMG}, $hsh{channel}, $hsh{tx}, $hsh{ty}, $hsh{Lx}, $hsh{Ly}, $hsh{Lz}, $hsh{cd}, $hsh{cs}, $hsh{n}, $hsh{Ia}, $hsh{Il}, @@ -378,8 +396,8 @@ BEGIN { super_sample => 0, ssample_param => 4, segments=>[ [ 0, 0.5, 1, - Imager::Color->new(0,0,0), - Imager::Color->new(255, 255, 255), + [0,0,0], + [255, 255, 255], 0, 0, ], ], @@ -426,13 +444,19 @@ BEGIN { # initlize Imager # NOTE: this might be moved to an import override later on -#sub import { -# my $pack = shift; -# (look through @_ for special tags, process, and remove them); -# use Data::Dumper; -# print Dumper($pack); -# print Dumper(@_); -#} +sub import { + my $i = 1; + while ($i < @_) { + if ($_[$i] eq '-log-stderr') { + init_log(undef, 4); + splice(@_, $i, 1); + } + else { + ++$i; + } + } + goto &Exporter::import; +} sub init_log { i_init_log($_[0],$_[1]); @@ -555,6 +579,22 @@ sub _color { return $result; } +sub _combine { + my ($self, $combine, $default) = @_; + + if (!defined $combine && ref $self) { + $combine = $self->{combine}; + } + defined $combine or $combine = $defaults{combine}; + defined $combine or $combine = $default; + + if (exists $combine_types{$combine}) { + $combine = $combine_types{$combine}; + } + + return $combine; +} + sub _valid_image { my ($self) = @_; @@ -565,6 +605,14 @@ sub _valid_image { return; } +# returns first defined parameter +sub _first { + for (@_) { + return $_ if defined $_; + } + return undef; +} + # # Methods to be called on objects. # @@ -581,13 +629,33 @@ sub new { $self->{IMG}=undef; # Just to indicate what exists $self->{ERRSTR}=undef; # $self->{DEBUG}=$DEBUG; - $self->{DEBUG} && print "Initialized Imager\n"; - if (defined $hsh{xsize} && defined $hsh{ysize}) { + $self->{DEBUG} and print "Initialized Imager\n"; + if (defined $hsh{xsize} || defined $hsh{ysize}) { unless ($self->img_set(%hsh)) { $Imager::ERRSTR = $self->{ERRSTR}; return; } } + elsif (defined $hsh{file} || + defined $hsh{fh} || + defined $hsh{fd} || + defined $hsh{callback} || + defined $hsh{readcb} || + defined $hsh{data}) { + # allow $img = Imager->new(file => $filename) + my %extras; + + # type is already used as a parameter to new(), rename it for the + # call to read() + if ($hsh{filetype}) { + $extras{type} = $hsh{filetype}; + } + unless ($self->read(%hsh, %extras)) { + $Imager::ERRSTR = $self->{ERRSTR}; + return; + } + } + return $self; } @@ -845,11 +913,16 @@ sub masked { $result->{IMG} = i_img_masked_new($self->{IMG}, $mask, $opts{left}, $opts{top}, $opts{right} - $opts{left}, $opts{bottom} - $opts{top}); + unless ($result->{IMG}) { + $self->_set_error(Imager->_error_as_msg); + return; + } + # keep references to the mask and base images so they don't # disappear on us $result->{DEPENDS} = [ $self->{IMG}, $mask ]; - $result; + return $result; } # convert an RGB image into a paletted image @@ -1025,6 +1098,14 @@ sub virtual { $self->{IMG} and i_img_virtual($self->{IMG}); } +sub is_bilevel { + my ($self) = @_; + + $self->{IMG} or return; + + return i_img_is_monochrome($self->{IMG}); +} + sub tags { my ($self, %opts) = @_; @@ -1157,7 +1238,7 @@ sub _get_reader_io { } elsif ($input->{fh}) { my $fd = fileno($input->{fh}); - unless ($fd) { + unless (defined $fd) { $self->_set_error("Handle in fh option not opened"); return; } @@ -1208,7 +1289,7 @@ sub _get_writer_io { } elsif ($input->{fh}) { my $fd = fileno($input->{fh}); - unless ($fd) { + unless (defined $fd) { $self->_set_error("Handle in fh option not opened"); return; } @@ -1280,36 +1361,15 @@ sub read { return $readers{$input{type}}{single}->($self, $IO, %input); } - unless ($formats{$input{'type'}}) { + unless ($formats_low{$input{'type'}}) { my $read_types = join ', ', sort Imager->read_types(); $self->_set_error("format '$input{'type'}' not supported - formats $read_types available for reading"); return; } - # Setup data source - if ( $input{'type'} eq 'jpeg' ) { - ($self->{IMG},$self->{IPTCRAW}) = i_readjpeg_wiol( $IO ); - if ( !defined($self->{IMG}) ) { - $self->{ERRSTR}=$self->_error_as_msg(); return undef; - } - $self->{DEBUG} && print "loading a jpeg file\n"; - return $self; - } - my $allow_incomplete = $input{allow_incomplete}; defined $allow_incomplete or $allow_incomplete = 0; - if ( $input{'type'} eq 'tiff' ) { - my $page = $input{'page'}; - defined $page or $page = 0; - $self->{IMG}=i_readtiff_wiol( $IO, $allow_incomplete, $page ); - if ( !defined($self->{IMG}) ) { - $self->{ERRSTR}=$self->_error_as_msg(); return undef; - } - $self->{DEBUG} && print "loading a tiff file\n"; - return $self; - } - if ( $input{'type'} eq 'pnm' ) { $self->{IMG}=i_readpnm_wiol( $IO, $allow_incomplete ); if ( !defined($self->{IMG}) ) { @@ -1320,15 +1380,6 @@ sub read { return $self; } - if ( $input{'type'} eq 'png' ) { - $self->{IMG}=i_readpng_wiol( $IO, -1 ); # Fixme, check if that length parameter is ever needed - if ( !defined($self->{IMG}) ) { - $self->{ERRSTR} = $self->_error_as_msg(); - return undef; - } - $self->{DEBUG} && print "loading a png file\n"; - } - if ( $input{'type'} eq 'bmp' ) { $self->{IMG}=i_readbmp_wiol( $IO, $allow_incomplete ); if ( !defined($self->{IMG}) ) { @@ -1383,19 +1434,26 @@ sub read { } if ( $input{'type'} eq 'raw' ) { - my %params=(datachannels=>3,storechannels=>3,interleave=>1,%input); - - if ( !($params{xsize} && $params{ysize}) ) { - $self->{ERRSTR}='missing xsize or ysize parameter for raw'; + unless ( $input{xsize} && $input{ysize} ) { + $self->_set_error('missing xsize or ysize parameter for raw'); return undef; } + my $interleave = _first($input{raw_interleave}, $input{interleave}); + unless (defined $interleave) { + my @caller = caller; + warn "read(type => 'raw') $caller[2] line $caller[1]: supply interleave or raw_interleave for future compatibility\n"; + $interleave = 1; + } + my $data_ch = _first($input{raw_datachannels}, $input{datachannels}, 3); + my $store_ch = _first($input{raw_storechannels}, $input{storechannels}, 3); + $self->{IMG} = i_readraw_wiol( $IO, - $params{xsize}, - $params{ysize}, - $params{datachannels}, - $params{storechannels}, - $params{interleave}); + $input{xsize}, + $input{ysize}, + $data_ch, + $store_ch, + $interleave); if ( !defined($self->{IMG}) ) { $self->{ERRSTR}=$self->_error_as_msg(); return undef; @@ -1478,7 +1536,7 @@ sub write_types { sub _reader_autoload { my $type = shift; - return if $formats{$type} || $readers{$type}; + return if $formats_low{$type} || $readers{$type}; return unless $type =~ /^\w+$/; @@ -1506,7 +1564,7 @@ sub _reader_autoload { sub _writer_autoload { my $type = shift; - return if $formats{$type} || $readers{$type}; + return if $formats_low{$type} || $readers{$type}; return unless $type =~ /^\w+$/; @@ -1553,6 +1611,9 @@ my %obsolete_opts = gif_loop_count => 'gif_loop', ); +# options that should be converted to colors +my %color_opts = map { $_ => 1 } qw/i_background/; + sub _set_opts { my ($self, $opts, $prefix, @imgs) = @_; @@ -1573,6 +1634,13 @@ sub _set_opts { } next unless $tagname =~ /^\Q$prefix/; my $value = $opts->{$opt}; + if ($color_opts{$opt}) { + $value = _color($value); + unless ($value) { + $self->_set_error($Imager::ERRSTR); + return; + } + } if (ref $value) { if (UNIVERSAL::isa($value, "Imager::Color")) { my $tag = sprintf("color(%d,%d,%d,%d)", $value->rgba); @@ -1655,7 +1723,7 @@ sub write { or return undef; } else { - if (!$formats{$input{'type'}}) { + if (!$formats_low{$input{'type'}}) { my $write_types = join ', ', sort Imager->write_types(); $self->_set_error("format '$input{'type'}' not supported - formats $write_types available for writing"); return undef; @@ -1664,24 +1732,7 @@ sub write { ($IO, $fh) = $self->_get_writer_io(\%input, $input{'type'}) or return undef; - if ($input{'type'} eq 'tiff') { - $self->_set_opts(\%input, "tiff_", $self) - or return undef; - $self->_set_opts(\%input, "exif_", $self) - or return undef; - - if (defined $input{class} && $input{class} eq 'fax') { - if (!i_writetiff_wiol_faxable($self->{IMG}, $IO, $input{fax_fine})) { - $self->{ERRSTR} = $self->_error_as_msg(); - return undef; - } - } else { - if (!i_writetiff_wiol($self->{IMG}, $IO)) { - $self->{ERRSTR} = $self->_error_as_msg(); - return undef; - } - } - } elsif ( $input{'type'} eq 'pnm' ) { + if ( $input{'type'} eq 'pnm' ) { $self->_set_opts(\%input, "pnm_", $self) or return undef; if ( ! i_writeppm_wiol($self->{IMG},$IO) ) { @@ -1697,14 +1748,6 @@ sub write { return undef; } $self->{DEBUG} && print "writing a raw file\n"; - } elsif ( $input{'type'} eq 'png' ) { - $self->_set_opts(\%input, "png_", $self) - or return undef; - if ( !i_writepng_wiol($self->{IMG}, $IO) ) { - $self->{ERRSTR}='unable to write png image'; - return undef; - } - $self->{DEBUG} && print "writing a png file\n"; } elsif ( $input{'type'} eq 'jpeg' ) { $self->_set_opts(\%input, "jpeg_", $self) or return undef; @@ -1805,37 +1848,7 @@ sub write_multi { ($IO, $file) = $class->_get_writer_io($opts, $type) or return undef; - if ($type eq 'gif') { - $class->_set_opts($opts, "gif_", @images) - or return; - my $gif_delays = $opts->{gif_delays}; - local $opts->{gif_delays} = $gif_delays; - if ($opts->{gif_delays} && !ref $opts->{gif_delays}) { - # assume the caller wants the same delay for each frame - $opts->{gif_delays} = [ ($gif_delays) x @images ]; - } - unless (i_writegif_wiol($IO, $opts, @work)) { - $class->_set_error($class->_error_as_msg()); - return undef; - } - } - elsif ($type eq 'tiff') { - $class->_set_opts($opts, "tiff_", @images) - or return; - $class->_set_opts($opts, "exif_", @images) - or return; - my $res; - $opts->{fax_fine} = 1 unless exists $opts->{fax_fine}; - if ($opts->{'class'} && $opts->{'class'} eq 'fax') { - $res = i_writetiff_multi_wiol_faxable($IO, $opts->{fax_fine}, @work); - } - else { - $res = i_writetiff_multi_wiol($IO, @work); - } - unless ($res) { - $class->_set_error($class->_error_as_msg()); - return undef; - } + if (0) { # eventually PNM in here, now that TIFF/GIF are elsewhere } else { if (@images == 1) { @@ -1889,30 +1902,15 @@ sub read_multi { return $readers{$type}{multiple}->($IO, %opts); } - if ($type eq 'gif') { - my @imgs; - @imgs = i_readgif_multi_wiol($IO); - if (@imgs) { - return map { - bless { IMG=>$_, DEBUG=>$DEBUG, ERRSTR=>undef }, 'Imager' - } @imgs; - } - else { - $ERRSTR = _error_as_msg(); - return; - } + unless ($formats{$type}) { + my $read_types = join ', ', sort Imager->read_types(); + Imager->_set_error("format '$type' not supported - formats $read_types available for reading"); + return; } - elsif ($type eq 'tiff') { - my @imgs = i_readtiff_multi_wiol($IO, -1); - if (@imgs) { - return map { - bless { IMG=>$_, DEBUG=>$DEBUG, ERRSTR=>undef }, 'Imager' - } @imgs; - } - else { - $ERRSTR = _error_as_msg(); - return; - } + + my @imgs; + if ($type eq 'pnm') { + @imgs = i_readpnm_multi_wiol($IO, $opts{allow_incomplete}||0); } else { my $img = Imager->new; @@ -1920,9 +1918,16 @@ sub read_multi { return ( $img ); } Imager->_set_error($img->errstr); + return; } + if (!@imgs) { + $ERRSTR = _error_as_msg(); return; + } + return map { + bless { IMG=>$_, DEBUG=>$DEBUG, ERRSTR=>undef }, 'Imager' + } @imgs; } # Destroy an Imager object @@ -2020,24 +2025,31 @@ sub register_filter { return 1; } -# Scale an image to requested size and return the scaled version +sub scale_calculate { + my $self = shift; -sub scale { - my $self=shift; - my %opts=('type'=>'max',qtype=>'normal',@_); - my $img = Imager->new(); - my $tmp = Imager->new(); - my ($x_scale, $y_scale); + my %opts = ('type'=>'max', @_); - unless (defined wantarray) { - my @caller = caller; - warn "scale() called in void context - scale() returns the scaled image at $caller[1] line $caller[2]\n"; - return; + # none of these should be references + for my $name (qw/xpixels ypixels xscalefactor yscalefactor width height/) { + if (defined $opts{$name} && ref $opts{$name}) { + $self->_set_error("scale_calculate: $name parameter cannot be a reference"); + return; + } } - unless ($self->{IMG}) { - $self->_set_error('empty input image'); - return undef; + my ($x_scale, $y_scale); + my $width = $opts{width}; + my $height = $opts{height}; + if (ref $self) { + defined $width or $width = $self->getwidth; + defined $height or $height = $self->getheight; + } + else { + unless (defined $width && defined $height) { + $self->_set_error("scale_calculate: width and height parameters must be supplied when called as a class method"); + return; + } } if ($opts{'xscalefactor'} && $opts{'yscalefactor'}) { @@ -2058,8 +2070,8 @@ sub scale { # work out the scaling if ($opts{xpixels} and $opts{ypixels} and $opts{'type'}) { - my ($xpix, $ypix)=( $opts{xpixels} / $self->getwidth() , - $opts{ypixels} / $self->getheight() ); + my ($xpix, $ypix)=( $opts{xpixels} / $width , + $opts{ypixels} / $height ); if ($opts{'type'} eq 'min') { $x_scale = $y_scale = _min($xpix,$ypix); } @@ -2072,13 +2084,13 @@ sub scale { } else { $self->_set_error('invalid value for type parameter'); - return undef; + return; } } elsif ($opts{xpixels}) { - $x_scale = $y_scale = $opts{xpixels} / $self->getwidth(); + $x_scale = $y_scale = $opts{xpixels} / $width; } elsif ($opts{ypixels}) { - $x_scale = $y_scale = $opts{ypixels}/$self->getheight(); + $x_scale = $y_scale = $opts{ypixels}/$height; } elsif ($opts{constrain} && ref $opts{constrain} && $opts{constrain}->can('constrain')) { @@ -2089,20 +2101,52 @@ sub scale { = $opts{constrain}->constrain($self->getwidth, $self->getheight); unless ($scalefactor) { $self->_set_error('constrain method failed on constrain parameter'); - return undef; + return; } $x_scale = $y_scale = $scalefactor; } + my $new_width = int($x_scale * $width + 0.5); + $new_width > 0 or $new_width = 1; + my $new_height = int($y_scale * $height + 0.5); + $new_height > 0 or $new_height = 1; + + return ($x_scale, $y_scale, $new_width, $new_height); + +} + +# Scale an image to requested size and return the scaled version + +sub scale { + my $self=shift; + my %opts = (qtype=>'normal' ,@_); + my $img = Imager->new(); + my $tmp = Imager->new(); + + unless (defined wantarray) { + my @caller = caller; + warn "scale() called in void context - scale() returns the scaled image at $caller[1] line $caller[2]\n"; + return; + } + + unless ($self->{IMG}) { + $self->_set_error('empty input image'); + return undef; + } + + my ($x_scale, $y_scale, $new_width, $new_height) = + $self->scale_calculate(%opts) + or return; + if ($opts{qtype} eq 'normal') { $tmp->{IMG} = i_scaleaxis($self->{IMG}, $x_scale, 0); if ( !defined($tmp->{IMG}) ) { - $self->{ERRSTR} = 'unable to scale image'; + $self->{ERRSTR} = 'unable to scale image: ' . $self->_error_as_msg; return undef; } $img->{IMG}=i_scaleaxis($tmp->{IMG}, $y_scale, 1); if ( !defined($img->{IMG}) ) { - $self->{ERRSTR}='unable to scale image'; + $self->{ERRSTR}='unable to scale image: ' . $self->_error_as_msg; return undef; } @@ -2117,13 +2161,9 @@ sub scale { return $img; } elsif ($opts{'qtype'} eq 'mixing') { - my $new_width = int(0.5 + $self->getwidth * $x_scale); - my $new_height = int(0.5 + $self->getheight * $y_scale); - $new_width >= 1 or $new_width = 1; - $new_height >= 1 or $new_height = 1; $img->{IMG} = i_scale_mixing($self->{IMG}, $new_width, $new_height); unless ($img->{IMG}) { - $self->_set_error(Imager->_error_as_meg); + $self->_set_error(Imager->_error_as_msg); return; } return $img; @@ -2367,7 +2407,7 @@ sub transform2 { sub rubthrough { my $self=shift; - my %opts=(tx => 0,ty => 0, @_); + my %opts= @_; unless ($self->{IMG}) { $self->{ERRSTR}='empty input image'; @@ -2384,15 +2424,108 @@ sub rubthrough { src_maxy => $opts{src}->getheight(), %opts); - unless (i_rubthru($self->{IMG}, $opts{src}->{IMG}, $opts{tx}, $opts{ty}, + my $tx = $opts{tx}; + defined $tx or $tx = $opts{left}; + defined $tx or $tx = 0; + + my $ty = $opts{ty}; + defined $ty or $ty = $opts{top}; + defined $ty or $ty = 0; + + unless (i_rubthru($self->{IMG}, $opts{src}->{IMG}, $tx, $ty, $opts{src_minx}, $opts{src_miny}, $opts{src_maxx}, $opts{src_maxy})) { $self->_set_error($self->_error_as_msg()); return undef; } + return $self; } +sub compose { + my $self = shift; + my %opts = + ( + opacity => 1.0, + mask_left => 0, + mask_top => 0, + @_ + ); + + unless ($self->{IMG}) { + $self->_set_error("compose: empty input image"); + return; + } + + unless ($opts{src}) { + $self->_set_error("compose: src parameter missing"); + return; + } + + unless ($opts{src}{IMG}) { + $self->_set_error("compose: src parameter empty image"); + return; + } + my $src = $opts{src}; + + my $left = $opts{left}; + defined $left or $left = $opts{tx}; + defined $left or $left = 0; + + my $top = $opts{top}; + defined $top or $top = $opts{ty}; + defined $top or $top = 0; + + my $src_left = $opts{src_left}; + defined $src_left or $src_left = $opts{src_minx}; + defined $src_left or $src_left = 0; + + my $src_top = $opts{src_top}; + defined $src_top or $src_top = $opts{src_miny}; + defined $src_top or $src_top = 0; + + my $width = $opts{width}; + if (!defined $width && defined $opts{src_maxx}) { + $width = $opts{src_maxx} - $src_left; + } + defined $width or $width = $src->getwidth() - $src_left; + + my $height = $opts{height}; + if (!defined $height && defined $opts{src_maxy}) { + $height = $opts{src_maxy} - $src_top; + } + defined $height or $height = $src->getheight() - $src_top; + + my $combine = $self->_combine($opts{combine}, 'normal'); + + if ($opts{mask}) { + unless ($opts{mask}{IMG}) { + $self->_set_error("compose: mask parameter empty image"); + return; + } + + my $mask_left = $opts{mask_left}; + defined $mask_left or $mask_left = $opts{mask_minx}; + defined $mask_left or $mask_left = 0; + + my $mask_top = $opts{mask_top}; + defined $mask_top or $mask_top = $opts{mask_miny}; + defined $mask_top or $mask_top = 0; + + i_compose_mask($self->{IMG}, $src->{IMG}, $opts{mask}{IMG}, + $left, $top, $src_left, $src_top, + $mask_left, $mask_top, $width, $height, + $combine, $opts{opacity}) + or return; + } + else { + i_compose($self->{IMG}, $src->{IMG}, $left, $top, $src_left, $src_top, + $width, $height, $combine, $opts{opacity}) + or return; + } + + return $self; +} sub flip { my $self = shift; @@ -2520,25 +2653,46 @@ sub i_color_set { # Draws a box between the specified corner points. sub box { my $self=shift; - unless ($self->{IMG}) { $self->{ERRSTR}='empty input image'; return undef; } - my $dflcl=i_color_new(255,255,255,255); - my %opts=(color=>$dflcl,xmin=>0,ymin=>0,xmax=>$self->getwidth()-1,ymax=>$self->getheight()-1,@_); + my $raw = $self->{IMG}; + + unless ($raw) { + $self->{ERRSTR}='empty input image'; + return undef; + } + + my %opts = @_; + my ($xmin, $ymin, $xmax, $ymax); if (exists $opts{'box'}) { - $opts{'xmin'} = _min($opts{'box'}->[0],$opts{'box'}->[2]); - $opts{'xmax'} = _max($opts{'box'}->[0],$opts{'box'}->[2]); - $opts{'ymin'} = _min($opts{'box'}->[1],$opts{'box'}->[3]); - $opts{'ymax'} = _max($opts{'box'}->[1],$opts{'box'}->[3]); + $xmin = _min($opts{'box'}->[0],$opts{'box'}->[2]); + $xmax = _max($opts{'box'}->[0],$opts{'box'}->[2]); + $ymin = _min($opts{'box'}->[1],$opts{'box'}->[3]); + $ymax = _max($opts{'box'}->[1],$opts{'box'}->[3]); + } + else { + defined($xmin = $opts{xmin}) or $xmin = 0; + defined($xmax = $opts{xmax}) or $xmax = $self->getwidth()-1; + defined($ymin = $opts{ymin}) or $ymin = 0; + defined($ymax = $opts{ymax}) or $ymax = $self->getheight()-1; } if ($opts{filled}) { - my $color = _color($opts{'color'}); - unless ($color) { - $self->{ERRSTR} = $Imager::ERRSTR; - return; + my $color = $opts{'color'}; + + if (defined $color) { + unless (_is_color_object($color)) { + $color = _color($color); + unless ($color) { + $self->{ERRSTR} = $Imager::ERRSTR; + return; + } + } } - i_box_filled($self->{IMG},$opts{xmin},$opts{ymin},$opts{xmax}, - $opts{ymax}, $color); + else { + $color = i_color_new(255,255,255,255); + } + + i_box_filled($raw, $xmin, $ymin,$xmax, $ymax, $color); } elsif ($opts{fill}) { unless (UNIVERSAL::isa($opts{fill}, 'Imager::Fill')) { @@ -2549,30 +2703,47 @@ sub box { return undef; } } - i_box_cfill($self->{IMG},$opts{xmin},$opts{ymin},$opts{xmax}, - $opts{ymax},$opts{fill}{fill}); + i_box_cfill($raw, $xmin, $ymin, $xmax, $ymax, $opts{fill}{fill}); } else { - my $color = _color($opts{'color'}); + my $color = $opts{'color'}; + if (defined $color) { + unless (_is_color_object($color)) { + $color = _color($color); + unless ($color) { + $self->{ERRSTR} = $Imager::ERRSTR; + return; + } + } + } + else { + $color = i_color_new(255, 255, 255, 255); + } unless ($color) { $self->{ERRSTR} = $Imager::ERRSTR; return; } - i_box($self->{IMG},$opts{xmin},$opts{ymin},$opts{xmax},$opts{ymax}, - $color); + i_box($raw, $xmin, $ymin, $xmax, $ymax, $color); } + return $self; } sub arc { my $self=shift; unless ($self->{IMG}) { $self->{ERRSTR}='empty input image'; return undef; } - my $dflcl=i_color_new(255,255,255,255); - my %opts=(color=>$dflcl, - 'r'=>_min($self->getwidth(),$self->getheight())/3, - 'x'=>$self->getwidth()/2, - 'y'=>$self->getheight()/2, - 'd1'=>0, 'd2'=>361, @_); + my $dflcl= [ 255, 255, 255, 255]; + my $good = 1; + my %opts= + ( + color=>$dflcl, + 'r'=>_min($self->getwidth(),$self->getheight())/3, + 'x'=>$self->getwidth()/2, + 'y'=>$self->getheight()/2, + 'd1'=>0, 'd2'=>361, + filled => 1, + @_, + ); if ($opts{aa}) { if ($opts{fill}) { unless (UNIVERSAL::isa($opts{fill}, 'Imager::Fill')) { @@ -2586,7 +2757,7 @@ sub arc { i_arc_aa_cfill($self->{IMG},$opts{'x'},$opts{'y'},$opts{'r'},$opts{'d1'}, $opts{'d2'}, $opts{fill}{fill}); } - else { + elsif ($opts{filled}) { my $color = _color($opts{'color'}); unless ($color) { $self->{ERRSTR} = $Imager::ERRSTR; @@ -2601,6 +2772,15 @@ sub arc { $opts{'d1'}, $opts{'d2'}, $color); } } + else { + my $color = _color($opts{'color'}); + if ($opts{d2} - $opts{d1} >= 360) { + $good = i_circle_out_aa($self->{IMG}, $opts{'x'}, $opts{'y'}, $opts{'r'}, $color); + } + else { + $good = i_arc_out_aa($self->{IMG}, $opts{'x'}, $opts{'y'}, $opts{'r'}, $opts{'d1'}, $opts{'d2'}, $color); + } + } } else { if ($opts{fill}) { @@ -2619,12 +2799,26 @@ sub arc { my $color = _color($opts{'color'}); unless ($color) { $self->{ERRSTR} = $Imager::ERRSTR; - return; + return; + } + if ($opts{filled}) { + i_arc($self->{IMG},$opts{'x'},$opts{'y'},$opts{'r'}, + $opts{'d1'}, $opts{'d2'}, $color); + } + else { + if ($opts{d1} == 0 && $opts{d2} == 361) { + $good = i_circle_out($self->{IMG}, $opts{x}, $opts{y}, $opts{r}, $color); + } + else { + $good = i_arc_out($self->{IMG}, $opts{x}, $opts{y}, $opts{r}, $opts{d1}, $opts{d2}, $color); + } } - i_arc($self->{IMG},$opts{'x'},$opts{'y'},$opts{'r'}, - $opts{'d1'}, $opts{'d2'}, $color); } } + unless ($good) { + $self->_set_error($self->_error_as_msg); + return; + } return $self; } @@ -2853,9 +3047,18 @@ sub flood_fill { } sub setpixel { - my $self = shift; + my ($self, %opts) = @_; - my %opts = ( color=>$self->{fg} || NC(255, 255, 255), @_); + my $color = $opts{color}; + unless (defined $color) { + $color = $self->{fg}; + defined $color or $color = NC(255, 255, 255); + } + + unless (ref $color && UNIVERSAL::isa($color, "Imager::Color")) { + $color = _color($color) + or return undef; + } unless (exists $opts{'x'} && exists $opts{'y'}) { $self->{ERRSTR} = 'missing x and y parameters'; @@ -2864,8 +3067,6 @@ sub setpixel { my $x = $opts{'x'}; my $y = $opts{'y'}; - my $color = _color($opts{color}) - or return undef; if (ref $x && ref $y) { unless (@$x == @$y) { $self->{ERRSTR} = 'length of x and y mismatch'; @@ -3041,7 +3242,7 @@ sub setscanline { sub getsamples { my $self = shift; - my %opts = ( type => '8bit', x=>0, @_); + my %opts = ( type => '8bit', x=>0, offset => 0, @_); defined $opts{width} or $opts{width} = $self->getwidth - $opts{x}; @@ -3054,18 +3255,103 @@ sub getsamples { $opts{channels} = [ 0 .. $self->getchannels()-1 ]; } - if ($opts{type} eq '8bit') { - return i_gsamp($self->{IMG}, $opts{x}, $opts{x}+$opts{width}, - $opts{y}, @{$opts{channels}}); - } - elsif ($opts{type} eq 'float') { - return i_gsampf($self->{IMG}, $opts{x}, $opts{x}+$opts{width}, - $opts{y}, @{$opts{channels}}); + if ($opts{target}) { + my $target = $opts{target}; + my $offset = $opts{offset}; + if ($opts{type} eq '8bit') { + my @samples = i_gsamp($self->{IMG}, $opts{x}, $opts{x}+$opts{width}, + $opts{y}, @{$opts{channels}}) + or return; + @{$target}{$offset .. $offset + @samples - 1} = @samples; + return scalar(@samples); + } + elsif ($opts{type} eq 'float') { + my @samples = i_gsampf($self->{IMG}, $opts{x}, $opts{x}+$opts{width}, + $opts{y}, @{$opts{channels}}); + @{$target}{$offset .. $offset + @samples - 1} = @samples; + return scalar(@samples); + } + elsif ($opts{type} =~ /^(\d+)bit$/) { + my $bits = $1; + + my @data; + my $count = i_gsamp_bits($self->{IMG}, $opts{x}, $opts{x}+$opts{width}, + $opts{y}, $bits, $target, + $offset, @{$opts{channels}}); + unless (defined $count) { + $self->_set_error(Imager->_error_as_msg); + return; + } + + return $count; + } + else { + $self->_set_error("invalid type parameter - must be '8bit' or 'float'"); + return; + } } else { - $self->_set_error("invalid type parameter - must be '8bit' or 'float'"); + if ($opts{type} eq '8bit') { + return i_gsamp($self->{IMG}, $opts{x}, $opts{x}+$opts{width}, + $opts{y}, @{$opts{channels}}); + } + elsif ($opts{type} eq 'float') { + return i_gsampf($self->{IMG}, $opts{x}, $opts{x}+$opts{width}, + $opts{y}, @{$opts{channels}}); + } + elsif ($opts{type} =~ /^(\d+)bit$/) { + my $bits = $1; + + my @data; + i_gsamp_bits($self->{IMG}, $opts{x}, $opts{x}+$opts{width}, + $opts{y}, $bits, \@data, 0, @{$opts{channels}}) + or return; + return @data; + } + else { + $self->_set_error("invalid type parameter - must be '8bit' or 'float'"); + return; + } + } +} + +sub setsamples { + my $self = shift; + my %opts = ( x => 0, offset => 0, @_ ); + + unless ($self->{IMG}) { + $self->_set_error('setsamples: empty input image'); + return; + } + + unless(defined $opts{data} && ref $opts{data}) { + $self->_set_error('setsamples: data parameter missing or invalid'); return; } + + unless ($opts{channels}) { + $opts{channels} = [ 0 .. $self->getchannels()-1 ]; + } + + unless ($opts{type} && $opts{type} =~ /^(\d+)bit$/) { + $self->_set_error('setsamples: type parameter missing or invalid'); + return; + } + my $bits = $1; + + unless (defined $opts{width}) { + $opts{width} = $self->getwidth() - $opts{x}; + } + + my $count = i_psamp_bits($self->{IMG}, $opts{x}, $opts{y}, $bits, + $opts{channels}, $opts{data}, $opts{offset}, + $opts{width}); + unless (defined $count) { + $self->_set_error(Imager->_error_as_msg); + return; + } + + return $count; } # make an identity matrix of the given size @@ -3185,6 +3471,46 @@ sub convert { return $new; } +# combine channels from multiple input images, a class method +sub combine { + my ($class, %opts) = @_; + + my $src = delete $opts{src}; + unless ($src) { + $class->_set_error("src parameter missing"); + return; + } + my @imgs; + my $index = 0; + for my $img (@$src) { + unless (eval { $img->isa("Imager") }) { + $class->_set_error("src must contain image objects"); + return; + } + unless ($img->{IMG}) { + $class->_set_error("empty input image"); + return; + } + push @imgs, $img->{IMG}; + } + my $result; + if (my $channels = delete $opts{channels}) { + $result = i_combine(\@imgs, $channels); + } + else { + $result = i_combine(\@imgs); + } + unless ($result) { + $class->_set_error($class->_error_as_msg); + return; + } + + my $img = $class->new; + $img->{IMG} = $result; + + return $img; +} + # general function to map an image through lookup tables @@ -3243,16 +3569,26 @@ sub border { sub getwidth { my $self = shift; - if (!defined($self->{IMG})) { $self->{ERRSTR} = 'image is empty'; return undef; } - return (i_img_info($self->{IMG}))[0]; + + if (my $raw = $self->{IMG}) { + return i_img_get_width($raw); + } + else { + $self->{ERRSTR} = 'image is empty'; return undef; + } } # Get the height of an image sub getheight { my $self = shift; - if (!defined($self->{IMG})) { $self->{ERRSTR} = 'image is empty'; return undef; } - return (i_img_info($self->{IMG}))[1]; + + if (my $raw = $self->{IMG}) { + return i_img_get_height($raw); + } + else { + $self->{ERRSTR} = 'image is empty'; return undef; + } } # Get number of channels in an image @@ -3396,7 +3732,8 @@ sub align_string { } my %input=('x'=>0, 'y'=>0, @_); - $input{string}||=$input{text}; + defined $input{string} + or $input{string} = $input{text}; unless(exists $input{string}) { $self->_set_error("missing required parameter 'string'"); @@ -3446,6 +3783,10 @@ sub get_file_limits { sub newcolor { Imager::Color->new(@_); } sub newfont { Imager::Font->new(@_); } +sub NCF { + require Imager::Color::Float; + return Imager::Color::Float->new(@_); +} *NC=*newcolour=*newcolor; *NF=*newfont; @@ -3491,6 +3832,10 @@ sub def_guess_type { return (); } +sub combines { + return @combine_types; +} + # get the minimum of a list sub _min { @@ -3570,6 +3915,110 @@ sub Inline { return Imager::ExtUtils->inline_config; } +# threads shouldn't try to close raw Imager objects +sub Imager::ImgRaw::CLONE_SKIP { 1 } + +# backward compatibility for %formats +package Imager::FORMATS; +use strict; +use constant IX_FORMATS => 0; +use constant IX_LIST => 1; +use constant IX_INDEX => 2; +use constant IX_CLASSES => 3; + +sub TIEHASH { + my ($class, $formats, $classes) = @_; + + return bless [ $formats, [ ], 0, $classes ], $class; +} + +sub _check { + my ($self, $key) = @_; + + (my $file = $self->[IX_CLASSES]{$key} . ".pm") =~ s(::)(/)g; + my $value; + if (eval { require $file; 1 }) { + $value = 1; + } + else { + $value = undef; + } + $self->[IX_FORMATS]{$key} = $value; + + return $value; +} + +sub FETCH { + my ($self, $key) = @_; + + exists $self->[IX_FORMATS]{$key} and return $self->[IX_FORMATS]{$key}; + + $self->[IX_CLASSES]{$key} or return undef; + + return $self->_check($key); +} + +sub STORE { + die "%Imager::formats is not user monifiable"; +} + +sub DELETE { + die "%Imager::formats is not user monifiable"; +} + +sub CLEAR { + die "%Imager::formats is not user monifiable"; +} + +sub EXISTS { + my ($self, $key) = @_; + + if (exists $self->[IX_FORMATS]{$key}) { + my $value = $self->[IX_FORMATS]{$key} + or return; + return 1; + } + + $self->_check($key) or return 1==0; + + return 1==1; +} + +sub FIRSTKEY { + my ($self) = @_; + + unless (@{$self->[IX_LIST]}) { + # full populate it + @{$self->[IX_LIST]} = grep $self->[IX_FORMATS]{$_}, + keys %{$self->[IX_FORMATS]}; + + for my $key (keys %{$self->[IX_CLASSES]}) { + $self->[IX_FORMATS]{$key} and next; + $self->_check($key) + and push @{$self->[IX_LIST]}, $key; + } + } + + @{$self->[IX_LIST]} or return; + $self->[IX_INDEX] = 1; + return $self->[IX_LIST][0]; +} + +sub NEXTKEY { + my ($self) = @_; + + $self->[IX_INDEX] < @{$self->[IX_LIST]} + or return; + + return $self->[IX_LIST][$self->[IX_INDEX]++]; +} + +sub SCALAR { + my ($self) = @_; + + return scalar @{$self->[IX_LIST]}; +} + 1; __END__ # Below is the stub of documentation for your module. You better edit it! @@ -3591,9 +4040,9 @@ Imager - Perl extension for Generating 24 bit Images my $format; - my $img = Imager->new(); # see Imager::Files for information on the read() method - $img->read(file=>$file) or die $img->errstr(); + my $img = Imager->new(file=>$file) + or die Imager->errstr(); $file =~ s/\.[^.]*$//; @@ -3607,7 +4056,7 @@ Imager - Perl extension for Generating 24 bit Images # try to save in one of these formats SAVE: - for $format ( qw( png gif jpg tiff ppm ) ) { + for $format ( qw( png gif jpeg tiff ppm ) ) { # Check if given format is supported if ($Imager::formats{$format}) { $file.="_low.$format"; @@ -3686,7 +4135,7 @@ C, C and C. =item * L - Filters, sharpen, blur, noise, convolve etc. and -filter plugins. +filter plug-ins. =item * @@ -3738,11 +4187,12 @@ This example creates a completely black image of width 400 and height =head1 ERROR HANDLING -In general a method will return false when it fails, if it does use the errstr() method to find out why: +In general a method will return false when it fails, if it does use +the C method to find out why: =over -=item errstr +=item C Returns the last error message in that context. @@ -3770,50 +4220,70 @@ L. Where to find information on methods for Imager class objects. -addcolors() - L +addcolors() - L - add colors to a +paletted image addtag() - L - add image tags -align_string() - L +align_string() - L - draw text aligned on a +point -arc() - L +arc() - L - draw a filled arc bits() - L - number of bits per sample for the image -box() - L +box() - L - draw a filled or outline box. + +circle() - L - draw a filled circle -circle() - L +colorcount() - L - the number of colors in an +image's palette (paletted images only) -colorcount() - L +combine() - L - combine channels from one or +more images. + +combines() - L - return a list of the different +combine type keywords + +compose() - L - compose one image +over another. convert() - L - transform the color space -copy() - L +copy() - L - make a duplicate of an +image crop() - L - extract part of an image -def_guess_type() - L +def_guess_type() - L - default function +used to guess the output file format based on the output file name deltag() - L - delete image tags -difference() - L +difference() - L - produce a +difference images from two input images. -errstr() - L<"Basic Overview"> +errstr() - L<"Basic Overview"> - the error from the last failed +operation. -filter() - L +filter() - L - image filtering -findcolor() - L - search the image palette, if it -has one +findcolor() - L - search the image +palette, if it has one -flip() - L +flip() - L - flip an image, vertically, +horizontally -flood_fill() - L +flood_fill() - L - fill an enclosed or same +color area -getchannels() - L +getchannels() - L - the number of +samples per pixel for an image -getcolorcount() - L +getcolorcount() - L - the number of +different colors used by an image (works for direct color images) getcolors() - L - get colors from the image palette, if it has one @@ -3824,23 +4294,33 @@ getcolorusagehash() - L get_file_limits() - L -getheight() - L +getheight() - L - height of the image in +pixels -getmask() - L +getmask() - L - write mask for the image -getpixel() - L +getpixel() - L - retrieve one or more pixel +colors -getsamples() - L +getsamples() - L - retrieve samples from a +row or partial row of pixels. -getscanline() - L +getscanline() - L - retrieve colors for a +row or partial row of pixels. -getwidth() - L +getwidth() - L - width of the image in +pixels. -img_set() - L +img_set() - L - re-use an Imager object +for a new image. init() - L -line() - L +is_bilevel() - L - returns whether +image write functions should write the image in their bilevel (blank +and white, no gray levels) format + +line() - L - draw an interval load_plugin() - L @@ -3855,6 +4335,8 @@ maxcolors() - L NC() - L +NCF() - L + new() - L newcolor() - L @@ -3867,6 +4349,8 @@ NF() - L open() - L - an alias for read() +=for stopwords IPTC + parseiptc() - L - parse IPTC data from a JPEG image @@ -3886,9 +4370,9 @@ can read. register_filter() - L -register_reader() - L +register_reader() - L -register_writer() - L +register_writer() - L rotate() - L @@ -3897,6 +4381,8 @@ image and use the alpha channel scale() - L +scale_calculate() - L + scaleX() - L scaleY() - L @@ -3910,6 +4396,8 @@ setmask() - L setpixel() - L +setsamples() - L + setscanline() - L settag() - L @@ -3945,7 +4433,7 @@ can write. =head1 CONCEPT INDEX -animated GIF - L +animated GIF - L aspect ratio - L, L, L @@ -3957,25 +4445,27 @@ blur - L, L boxes, drawing - L -changes between image - L +changes between image - L + +channels, combine into one image - L color - L color names - L, L -combine modes - L +combine modes - L -compare images - L +compare images - L -contrast - L, L +contrast - L, L -convolution - L +convolution - L cropping - L CUR files - L -C images - L +C images - L dpi - L, L @@ -3986,7 +4476,7 @@ drawing lines - L drawing text - L, L -error message - L<"Basic Overview"> +error message - L<"ERROR HANDLING"> files, font - L @@ -4021,15 +4511,16 @@ gradient fill - L, L, L, L -grayscale, convert image to - L +gray scale, convert image to - L -guassian blur - L +guassian blur - L hatch fills - L ICO files - L -invert image - L +invert image - L, +L JPEG - L @@ -4043,12 +4534,12 @@ L metadata, image - L -mosaic - L +mosaic - L -noise, filter - L +noise, filter - L -noise, rendered - L, -L +noise, rendered - L, +L paste - L, L @@ -4056,11 +4547,13 @@ L pseudo-color image - L, L -posterize - L +=for stopwords posterize + +posterize - L -png files - L, L +PNG files - L, L -pnm - L +PNM - L rectangles, drawing - L @@ -4091,14 +4584,28 @@ text, wrapping text in an area - L text, measuring - L, L -tiles, color - L +tiles, color - L + +transparent images - L, +L -unsharp mask - L +=for stopwords unsharp -watermark - L +unsharp mask - L + +watermark - L writing an image to a file - L +=head1 THREADS + +Imager doesn't support perl threads. + +Imager has limited code to prevent double frees if you create images, +colors etc, and then create a thread, but has no code to prevent two +threads entering Imager's error handling code, and none is likely to +be added. + =head1 SUPPORT The best place to get help with Imager is the mailing list. @@ -4155,11 +4662,14 @@ at CPAN Ratings: http://cpanratings.perl.org/dist/Imager -This requires a Bitcard Account (http://www.bitcard.org). +=for stopwords Bitcard + +This requires a Bitcard account (http://www.bitcard.org). You can also send email to the maintainer below. -If you send me a bug report via email, it will be copied to RT. +If you send me a bug report via email, it will be copied to Request +Tracker. =head2 Patches @@ -4176,9 +4686,20 @@ Tony Cook is the current maintainer for Imager. Arnar M. Hrafnkelsson is the original author of Imager. -Many others have contributed to Imager, please see the README for a +Many others have contributed to Imager, please see the C for a complete list. +=head1 LICENSE + +Imager is licensed under the same terms as perl itself. + +=for stopwords +makeblendedfont Fontforge + +A test font, FT2/fontfiles/MMOne.pfb, contains a Postscript operator +definition copyrighted by Adobe. See F in the source for +license information. + =head1 SEE ALSO L(1), L(3), L(3),