X-Git-Url: http://git.imager.perl.org/imager.git/blobdiff_plain/cf692b64919ff55e15f2428863192378ccc44fbd..80684605d2aae3a7955d45e8ca7b44460ee48d0f:/Imager.pm diff --git a/Imager.pm b/Imager.pm index a091901b..19dab111 100644 --- a/Imager.pm +++ b/Imager.pm @@ -168,6 +168,16 @@ BEGIN { $DEBUG=0; + # the members of the subhashes under %filters are: + # callseq - a list of the parameters to the underlying filter in the + # order they are passed + # callsub - a code ref that takes a named parameter list and calls the + # underlying filter + # defaults - a hash of default values + # names - defines names for value of given parameters so if the names + # field is foo=> { bar=>1 }, and the user supplies "bar" as the + # foo parameter, the filter will receive 1 for the foo + # parameter $filters{contrast}={ callseq => ['image','intensity'], callsub => sub { my %hsh=@_; i_contrast($hsh{image},$hsh{intensity}); } @@ -235,12 +245,37 @@ BEGIN { { callseq => [ qw(image bump elevation lightx lighty st) ], defaults => { elevation=>0, st=> 2 }, - callsub => sub { + callsub => sub { my %hsh = @_; i_bumpmap($hsh{image}, $hsh{bump}{IMG}, $hsh{elevation}, $hsh{lightx}, $hsh{lighty}, $hsh{st}); }, }; + $filters{bumpmap_complex} = + { + callseq => [ qw(image bump channel tx ty Lx Ly Lz cd cs n Ia Il Is) ], + defaults => { + channel => 0, + tx => 0, + ty => 0, + Lx => 0.2, + Ly => 0.4, + Lz => -1.0, + 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]), + }, + callsub => sub { + my %hsh = @_; + 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}, + $hsh{Is}); + }, + }; $filters{postlevels} = { callseq => [ qw(image levels) ], @@ -258,6 +293,72 @@ BEGIN { $hsh{pixdiff}); }, }; + $filters{fountain} = + { + callseq => [ qw(image xa ya xb yb ftype repeat combine super_sample ssample_param segments) ], + names => { + ftype => { linear => 0, + bilinear => 1, + radial => 2, + radial_square => 3, + revolution => 4, + conical => 5 }, + repeat => { none => 0, + sawtooth => 1, + triangle => 2, + saw_both => 3, + tri_both => 4, + }, + super_sample => { + none => 0, + grid => 1, + random => 2, + circle => 3, + }, + combine => { + none => 0, + normal => 1, + multiply => 2, mult => 2, + dissolve => 3, + add => 4, + subtract => 5, sub => 5, + diff => 6, + lighten => 7, + darken => 8, + hue => 9, + sat => 10, + value => 11, + color => 12, + }, + }, + defaults => { ftype => 0, repeat => 0, combine => 0, + 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, + ], + ], + }, + callsub => + sub { + my %hsh = @_; + i_fountain($hsh{image}, $hsh{xa}, $hsh{ya}, $hsh{xb}, $hsh{yb}, + $hsh{ftype}, $hsh{repeat}, $hsh{combine}, $hsh{super_sample}, + $hsh{ssample_param}, $hsh{segments}); + }, + }; + $filters{unsharpmask} = + { + callseq => [ qw(image stddev scale) ], + defaults => { stddev=>2.0, scale=>1.0 }, + callsub => + sub { + my %hsh = @_; + i_unsharp_mask($hsh{image}, $hsh{stddev}, $hsh{scale}); + }, + }; $FORMATGUESS=\&def_guess_type; } @@ -461,6 +562,9 @@ sub img_set { $self->{IMG} = i_img_pal_new($hsh{xsize}, $hsh{ysize}, $hsh{channels}, $hsh{maxcolors} || 256); } + elsif ($hsh{bits} eq 'double') { + $self->{IMG} = i_img_double_new($hsh{xsize}, $hsh{ysize}, $hsh{channels}); + } elsif ($hsh{bits} == 16) { $self->{IMG} = i_img_16_new($hsh{xsize}, $hsh{ysize}, $hsh{channels}); } @@ -582,7 +686,11 @@ sub findcolor { sub bits { my $self = shift; - $self->{IMG} and i_img_bits($self->{IMG}); + my $bits = $self->{IMG} && i_img_bits($self->{IMG}); + if ($bits && $bits == length(pack("d", 1)) * 8) { + $bits = 'double'; + } + $bits; } sub type { @@ -743,7 +851,7 @@ sub read { $self->{ERRSTR}='format not supported'; return undef; } - my %iolready=(jpeg=>1, png=>1, tiff=>1, pnm=>1, raw=>1, bmp=>1); + my %iolready=(jpeg=>1, png=>1, tiff=>1, pnm=>1, raw=>1, bmp=>1, tga=>1); if ($iolready{$input{type}}) { # Setup data source @@ -794,6 +902,16 @@ sub read { $self->{DEBUG} && print "loading a bmp file\n"; } + if ( $input{type} eq 'tga' ) { + $self->{IMG}=i_readtga_wiol( $IO, -1 ); # Fixme, check if that length parameter is ever needed + if ( !defined($self->{IMG}) ) { + $self->{ERRSTR}=$self->_error_as_msg(); +# $self->{ERRSTR}='unable to read tga image'; + return undef; + } + $self->{DEBUG} && print "loading a tga file\n"; + } + if ( $input{type} eq 'raw' ) { my %params=(datachannels=>3,storechannels=>3,interleave=>1,%input); @@ -887,7 +1005,7 @@ sub write { fax_fine=>1, @_); my ($fh, $rc, $fd, $IO); - my %iolready=( tiff=>1, raw=>1, png=>1, pnm=>1, bmp=>1, jpeg=>1 ); # this will be SO MUCH BETTER once they are all in there + my %iolready=( tiff=>1, raw=>1, png=>1, pnm=>1, bmp=>1, jpeg=>1, tga=>1 ); # this will be SO MUCH BETTER once they are all in there unless ($self->{IMG}) { $self->{ERRSTR}='empty input image'; return undef; } @@ -945,7 +1063,7 @@ sub write { $self->{DEBUG} && print "writing a png file\n"; } elsif ( $input{type} eq 'jpeg' ) { if ( !i_writejpeg_wiol($self->{IMG}, $IO, $input{jpegquality})) { - $self->{ERRSTR}='unable to write jpeg image'; + $self->{ERRSTR} = $self->_error_as_msg(); return undef; } $self->{DEBUG} && print "writing a jpeg file\n"; @@ -955,6 +1073,13 @@ sub write { return undef; } $self->{DEBUG} && print "writing a bmp file\n"; + } elsif ( $input{type} eq 'tga' ) { + if ( !i_writetga_wiol($self->{IMG}, $IO) ) { + $self->{ERRSTR}=$self->_error_as_msg(); +# $self->{ERRSTR}='unable to write tga image'; + return undef; + } + $self->{DEBUG} && print "writing a tga file\n"; } if (exists $input{'data'}) { @@ -1171,6 +1296,14 @@ sub filter { $self->{ERRSTR}='type parameter not matching any filter'; return undef; } + if ($filters{$input{type}}{names}) { + my $names = $filters{$input{type}}{names}; + for my $name (keys %$names) { + if (defined $input{$name} && exists $names->{$name}{$input{$name}}) { + $input{$name} = $names->{$name}{$input{$name}}; + } + } + } if (defined($filters{$input{type}}{defaults})) { %hsh=('image',$self->{IMG},%{$filters{$input{type}}{defaults}},%input); } else { @@ -1543,8 +1676,25 @@ sub box { $opts{'ymax'} = max($opts{'box'}->[1],$opts{'box'}->[3]); } - if ($opts{filled}) { i_box_filled($self->{IMG},$opts{xmin},$opts{ymin},$opts{xmax},$opts{ymax},$opts{color}); } - else { i_box($self->{IMG},$opts{xmin},$opts{ymin},$opts{xmax},$opts{ymax},$opts{color}); } + if ($opts{filled}) { + i_box_filled($self->{IMG},$opts{xmin},$opts{ymin},$opts{xmax}, + $opts{ymax},$opts{color}); + } + elsif ($opts{fill}) { + unless (UNIVERSAL::isa($opts{fill}, 'Imager::Fill')) { + # assume it's a hash ref + require 'Imager/Fill.pm'; + unless ($opts{fill} = Imager::Fill->new(%{$opts{fill}})) { + $self->{ERRSTR} = $Imager::ERRSTR; + return undef; + } + } + i_box_cfill($self->{IMG},$opts{xmin},$opts{ymin},$opts{xmax}, + $opts{ymax},$opts{fill}{fill}); + } + else { + i_box($self->{IMG},$opts{xmin},$opts{ymin},$opts{xmax},$opts{ymax},$opts{color}); + } return $self; } @@ -1559,7 +1709,26 @@ sub arc { 'x'=>$self->getwidth()/2, 'y'=>$self->getheight()/2, 'd1'=>0, 'd2'=>361, @_); - i_arc($self->{IMG},$opts{'x'},$opts{'y'},$opts{'r'},$opts{'d1'},$opts{'d2'},$opts{'color'}); + if ($opts{fill}) { + unless (UNIVERSAL::isa($opts{fill}, 'Imager::Fill')) { + # assume it's a hash ref + require 'Imager/Fill.pm'; + $opts{fill} = Imager::Fill->new(%{$opts{fill}}); + } + i_arc_cfill($self->{IMG},$opts{'x'},$opts{'y'},$opts{'r'},$opts{'d1'}, + $opts{'d2'}, $opts{fill}{fill}); + } + else { + if ($opts{d1} == 0 && $opts{d2} == 361 && $opts{aa}) { + i_circle_aa($self->{IMG}, $opts{'x'}, $opts{'y'}, $opts{'r'}, + $opts{'color'}); + } + else { + i_arc($self->{IMG},$opts{'x'},$opts{'y'},$opts{'r'},$opts{'d1'}, + $opts{'d2'},$opts{'color'}); + } + } + return $self; } @@ -1641,6 +1810,30 @@ sub polybezier { return $self; } +sub flood_fill { + my $self = shift; + my %opts = ( color=>Imager::Color->new(255, 255, 255), @_ ); + + unless (exists $opts{x} && exists $opts{'y'}) { + $self->{ERRSTR} = "missing seed x and y parameters"; + return undef; + } + + if ($opts{fill}) { + unless (UNIVERSAL::isa($opts{fill}, 'Imager::Fill')) { + # assume it's a hash ref + require 'Imager/Fill.pm'; + $opts{fill} = Imager::Fill->new(%{$opts{fill}}); + } + i_flood_cfill($self->{IMG}, $opts{x}, $opts{'y'}, $opts{fill}{fill}); + } + else { + i_flood_fill($self->{IMG}, $opts{x}, $opts{'y'}, $opts{color}); + } + + $self; +} + # make an identity matrix of the given size sub _identity { my ($size) = @_; @@ -1892,6 +2085,7 @@ sub def_guess_type { return 'pnm' if ($ext =~ m/^p[pgb]m$/); return 'png' if ($ext eq "png"); return 'bmp' if ($ext eq "bmp" || $ext eq "dib"); + return 'tga' if ($ext eq "tga"); return 'gif' if ($ext eq "gif"); return (); } @@ -2050,15 +2244,22 @@ Warning: if you draw on a paletted image with colors that aren't in the palette, the image will be internally converted to a normal image. For improved color precision you can use the bits parameter to specify -16 bites per channel: +16 bit per channel: $img = Imager->new(xsize=>200, ysize=>200, channels=>3, bits=>16); -Note that as of this writing all functions should work on 16-bit -images, but at only 8-bit/channel precision. +or for even more precision: + + $img = Imager->new(xsize=>200, ysize=>200, channels=>3, bits=>'double'); + +to get an image that uses a double for each channel. -Currently only 8 and 16/bit per channel image types are available, -this may change later. +Note that as of this writing all functions should work on images with +more than 8-bits/channel, but many will only work at only +8-bit/channel precision. + +Currently only 8-bit, 16-bit, and double per channel image types are +available, this may change later. Color objects are created by calling the Imager::Color->new() method: @@ -2123,7 +2324,12 @@ the file descriptor for the file: For writing using the 'fd' option you will probably want to set $| for that descriptor, since the writes to the file descriptor bypass Perl's (or the C libraries) buffering. Setting $| should avoid out of order -output. +output. For example a common idiom when writing a CGI script is: + + # the $| _must_ come before you send the content-type + $| = 1; + print "Content-Type: image/jpeg\n\n"; + $img->write(fd=>fileno(STDOUT), type=>'jpeg') or die $img->errstr; *Note that load() is now an alias for read but will be removed later* @@ -2340,6 +2546,11 @@ which makes the animation of the images repeat. This is currently unimplemented due to some limitations in giflib. +=item gif_eliminate_unused + +If this is true, when you write a paletted image any unused colors +will be eliminated from its palette. This is set by default. + =back =head2 Quantization options @@ -2600,7 +2811,8 @@ the function return undef. Examples: } The bits() method retrieves the number of bits used to represent each -channel in a pixel, typically 8. The type() method returns either +channel in a pixel, 8 for a normal image, 16 for 16-bit image and +'double' for a double/channel image. The type() method returns either 'direct' for truecolor images or 'paletted' for paletted images. The virtual() method returns non-zero if the image contains no actual pixels, for example masked images. @@ -2672,6 +2884,18 @@ Arc: This creates a filled red arc with a 'center' at (200, 100) and spans 10 degrees and the slice has a radius of 20. SEE section on BUGS. +Both the arc() and box() methods can take a C parameter which +can either be an Imager::Fill object, or a reference to a hash +containing the parameters used to create the fill: + + $img->box(xmin=>10, ymin=>30, xmax=>150, ymax=>60, + fill => { hatch=>'cross2' }); + use Imager::Fill; + my $fill = Imager::Fill->new(hatch=>'stipple'); + $img->box(fill=>$fill); + +See L for the type of fills you can use. + Circle: $img->circle(color=>$green, r=50, x=>200, y=>100); @@ -2693,6 +2917,20 @@ The point set can either be specified as an arrayref to an array of array references (where each such array represents a point). The other way is to specify two array references. +You can fill a region that all has the same color using the +flood_fill() method, for example: + + $img->flood_fill(x=>50, y=>50, color=>$color); + +will fill all regions the same color connected to the point (50, 50). + +You can also use a general fill, so you could fill the same region +with a check pattern using: + + $img->flood_fill(x=>50, y=>50, fill=>{ hatch=>'check2x2' }); + +See L for more information on general fills. + =head2 Text rendering Text rendering is described in the Imager::Font manpage. @@ -2770,7 +3008,8 @@ parameter which can take the values C, C, C and C. =head2 Rotating images -Use the rotate() method to rotate an image. +Use the rotate() method to rotate an image. This method will return a +new, rotated image. To rotate by an exact amount in degrees or radians, use the 'degrees' or 'radians' parameter: @@ -2778,6 +3017,9 @@ or 'radians' parameter: my $rot20 = $img->rotate(degrees=>20); my $rotpi4 = $img->rotate(radians=>3.14159265/4); +Exact image rotation uses the same underlying transformation engine as +the matrix_transform() method. + To rotate in steps of 90 degrees, use the 'right' parameter: my $rotated = $img->rotate(right=>270); @@ -2796,11 +3038,11 @@ That will take paste C<$srcimage> into C<$img> with the upper left corner at (30,50). If no values are given for C or C they will default to 0. -A more complicated way of blending images is where one image is +A more complicated way of blending images is where one image is put 'over' the other with a certain amount of opaqueness. The method that does this is rubthrough. - $img->rubthrough(src=>$srcimage,tx=>30,ty=>50); + $img->rubthrough(src=>$srcimage,tx=>30,ty=>50); That will take the image C<$srcimage> and overlay it with the upper left corner at (30,50). You can rub 2 or 4 channel images onto a 3 @@ -2822,15 +3064,22 @@ source. Filter Arguments autolevels lsat(0.1) usat(0.1) skew(0) bumpmap bump elevation(0) lightx lighty st(2) + bumpmap_complex bump channel(0) tx(0) ty(0) Lx(0.2) Ly(0.4) + Lz(-1) cd(1.0) cs(40.0) n(1.3) Ia(0 0 0) Il(255 255 255) + Is(255 255 255) contrast intensity conv coef + fountain xa ya xb yb ftype(linear) repeat(none) combine(none) + super_sample(none) ssample_param(4) segments(see below) gaussian stddev gradgen xo yo colors dist hardinvert + mosaic size(20) noise amount(3) subtype(0) postlevels levels(10) radnoise xo(100) yo(100) ascale(17.0) rscale(0.02) turbnoise xo(0.0) yo(0.0) scale(10.0) + unsharpmask stddev(2.0) scale(1.0) watermark wmark pixdiff(10) tx(0) ty(0) The default values are in parenthesis. All parameters must have some @@ -2854,6 +3103,16 @@ uses the channel I image I as a bumpmap on your image, with the light at (I, I), with a shadow length of I. +=item bumpmap_complex + +uses the channel I image I as a bumpmap on your image. +If Lz<0 the three L parameters are considered to be the direction of +the light. If Lz>0 the L parameters are considered to be the light +position. I is the ambient colour, I is the light colour, +I is the color of specular highlights. I is the diffuse +coefficient and I is the specular coefficient. I is the +shininess of the surface. + =item contrast scales each channel by I. Values of I < 1.0 @@ -2864,6 +3123,164 @@ will reduce the contrast. performs 2 1-dimensional convolutions on the image using the values from I. I should be have an odd length. +=item fountain + +renders a fountain fill, similar to the gradient tool in most paint +software. The default fill is a linear fill from opaque black to +opaque white. The points A(xa, ya) and B(xb, yb) control the way the +fill is performed, depending on the ftype parameter: + +=over + +=item linear + +the fill ramps from A through to B. + +=item bilinear + +the fill ramps in both directions from A, where AB defines the length +of the gradient. + +=item radial + +A is the center of a circle, and B is a point on it's circumference. +The fill ramps from the center out to the circumference. + +=item radial_square + +A is the center of a square and B is the center of one of it's sides. +This can be used to rotate the square. The fill ramps out to the +edges of the square. + +=item revolution + +A is the centre of a circle and B is a point on it's circumference. B +marks the 0 and 360 point on the circle, with the fill ramping +clockwise. + +=item conical + +A is the center of a circle and B is a point on it's circumference. B +marks the 0 and point on the circle, with the fill ramping in both +directions to meet opposite. + +=back + +The I option controls how the fill is repeated for some +Is after it leaves the AB range: + +=over + +=item none + +no repeats, points outside of each range are treated as if they were +on the extreme end of that range. + +=item sawtooth + +the fill simply repeats in the positive direction + +=item triangle + +the fill repeats in reverse and then forward and so on, in the +positive direction + +=item saw_both + +the fill repeats in both the positive and negative directions (only +meaningful for a linear fill). + +=item tri_both + +as for triangle, but in the negative direction too (only meaningful +for a linear fill). + +=back + +By default the fill simply overwrites the whole image (unless you have +parts of the range 0 through 1 that aren't covered by a segment), if +any segments of your fill have any transparency, you can set the +I option to 'normal' to have the fill combined with the +existing pixels. See the description of I in L. + +If your fill has sharp edges, for example between steps if you use +repeat set to 'triangle', you may see some aliased or ragged edges. +You can enable super-sampling which will take extra samples within the +pixel in an attempt anti-alias the fill. + +The possible values for the super_sample option are: + +=over + +=item none + +no super-sampling is done + +=item grid + +a square grid of points are sampled. The number of points sampled is +the square of ceil(0.5 + sqrt(ssample_param)). + +=item random + +a random set of points within the pixel are sampled. This looks +pretty bad for low ssample_param values. + +=item circle + +the points on the radius of a circle within the pixel are sampled. +This seems to produce the best results, but is fairly slow (for now). + +=back + +You can control the level of sampling by setting the ssample_param +option. This is roughly the number of points sampled, but depends on +the type of sampling. + +The segments option is an arrayref of segments. You really should use +the Imager::Fountain class to build your fountain fill. Each segment +is an array ref containing: + +=over + +=item start + +a floating point number between 0 and 1, the start of the range of fill parameters covered by this segment. + +=item middle + +a floating point number between start and end which can be used to +push the color range towards one end of the segment. + +=item end + +a floating point number between 0 and 1, the end of the range of fill +parameters covered by this segment. This should be greater than +start. + +=item c0 + +=item c1 + +The colors at each end of the segment. These can be either +Imager::Color or Imager::Color::Float objects. + +=item segment type + +The type of segment, this controls the way the fill parameter varies +over the segment. 0 for linear, 1 for curved (unimplemented), 2 for +sine, 3 for sphere increasing, 4 for sphere decreasing. + +=item color type + +The way the color varies within the segment, 0 for simple RGB, 1 for +hue increasing and 2 for hue decreasing. + +=back + +Don't forgot to use Imager::Fountain instead of building your own. +Really. It even loads GIMP gradient files. + =item gaussian performs a gaussian blur of the image, using I as the standard @@ -2884,6 +3301,10 @@ for Euclidean squared, and 2 for Manhattan distance. inverts the image, black to white, white to black. All channels are inverted, including the alpha channel if any. +=item mosaic + +produces averaged tiles of the given I. + =item noise adds noise of the given I to the image. If I is @@ -2907,6 +3328,13 @@ renders Perlin turbulent noise. (I, I) controls the origin of the noise, and I the scale of the noise, with lower numbers giving more detail. +=item unsharpmask + +performs an unsharp mask on the image. This is the result of +subtracting a gaussian blurred version of the image from the original. +I controls the stddev parameter of the gaussian blur. Each +output pixel is: in + I * (in - blurred). + =item watermark applies I as a watermark on the image with strength I, @@ -3490,7 +3918,7 @@ the i_aspect_only tag is non-zero. =back -The following tags are set when reading a Windows BMP file is read: +The following tags are set when a Windows BMP file is read: =over