Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

implement the convert() method for converting between numbers of chan…

…nels
  • Loading branch information...
commit f5991c03ada75ae1f42b680fd5e7b7af3b6141bf 1 parent b9029e2
Tony Cook authored
View
229 Imager.pm
@@ -57,6 +57,8 @@ use Imager::Font;
i_gaussian
i_conv
+ i_convert
+
i_img_diff
i_init_fonts
@@ -1174,6 +1176,117 @@ sub polybezier {
return $self;
}
+# make an identity matrix of the given size
+sub _identity {
+ my ($size) = @_;
+
+ my $matrix = [ map { [ (0) x $size ] } 1..$size ];
+ for my $c (0 .. ($size-1)) {
+ $matrix->[$c][$c] = 1;
+ }
+ return $matrix;
+}
+
+# general function to convert an image
+sub convert {
+ my ($self, %opts) = @_;
+ my $matrix;
+
+ # the user can either specify a matrix or preset
+ # the matrix overrides the preset
+ if (!exists($opts{matrix})) {
+ unless (exists($opts{preset})) {
+ $self->{ERRSTR} = "convert() needs a matrix or preset";
+ return;
+ }
+ else {
+ if ($opts{preset} eq 'gray' || $opts{preset} eq 'grey') {
+ # convert to greyscale, keeping the alpha channel if any
+ if ($self->getchannels == 3) {
+ $matrix = [ [ 0.222, 0.707, 0.071 ] ];
+ }
+ elsif ($self->getchannels == 4) {
+ # preserve the alpha channel
+ $matrix = [ [ 0.222, 0.707, 0.071, 0 ],
+ [ 0, 0, 0, 1 ] ];
+ }
+ else {
+ # an identity
+ $matrix = _identity($self->getchannels);
+ }
+ }
+ elsif ($opts{preset} eq 'noalpha') {
+ # strip the alpha channel
+ if ($self->getchannels == 2 or $self->getchannels == 4) {
+ $matrix = _identity($self->getchannels);
+ pop(@$matrix); # lose the alpha entry
+ }
+ else {
+ $matrix = _identity($self->getchannels);
+ }
+ }
+ elsif ($opts{preset} eq 'red' || $opts{preset} eq 'channel0') {
+ # extract channel 0
+ $matrix = [ [ 1 ] ];
+ }
+ elsif ($opts{preset} eq 'green' || $opts{preset} eq 'channel1') {
+ $matrix = [ [ 0, 1 ] ];
+ }
+ elsif ($opts{preset} eq 'blue' || $opts{preset} eq 'channel2') {
+ $matrix = [ [ 0, 0, 1 ] ];
+ }
+ elsif ($opts{preset} eq 'alpha') {
+ if ($self->getchannels == 2 or $self->getchannels == 4) {
+ $matrix = [ [ (0) x ($self->getchannels-1), 1 ] ];
+ }
+ else {
+ # the alpha is just 1 <shrug>
+ $matrix = [ [ (0) x $self->getchannels, 1 ] ];
+ }
+ }
+ elsif ($opts{preset} eq 'rgb') {
+ if ($self->getchannels == 1) {
+ $matrix = [ [ 1 ], [ 1 ], [ 1 ] ];
+ }
+ elsif ($self->getchannels == 2) {
+ # preserve the alpha channel
+ $matrix = [ [ 1, 0 ], [ 1, 0 ], [ 1, 0 ], [ 0, 1 ] ];
+ }
+ else {
+ $matrix = _identity($self->getchannels);
+ }
+ }
+ elsif ($opts{preset} eq 'addalpha') {
+ if ($self->getchannels == 1) {
+ $matrix = _identity(2);
+ }
+ elsif ($self->getchannels == 3) {
+ $matrix = _identity(4);
+ }
+ else {
+ $matrix = _identity($self->getchannels);
+ }
+ }
+ else {
+ $self->{ERRSTR} = "Unknown convert preset $opts{preset}";
+ return undef;
+ }
+ }
+ }
+ else {
+ $matrix = $opts{matrix};
+ }
+
+ my $new = Imager->new();
+ $new->{IMG} = i_img_new();
+ unless (i_convert($new->{IMG}, $self->{IMG}, $matrix)) {
+ # most likely a bad matrix
+ $self->{ERRSTR} = _error_as_msg();
+ return undef;
+ }
+ return $new;
+}
+
# destructive border - image is shrunk by one pixel all around
@@ -1633,6 +1746,8 @@ options>.
=back
+You must also specify the file format using the 'type' option.
+
The current aim is to support other multiple image formats in the
future, such as TIFF, and to support reading multiple images from a
single file.
@@ -1643,7 +1758,7 @@ A simple example:
# ... code to put images in @images
Imager->write_multi({type=>'gif',
file=>'anim.gif',
- gif_delays=>[ 10 x @images ] },
+ gif_delays=>[ (10) x @images ] },
@images)
or die "Oh dear!";
@@ -2135,6 +2250,118 @@ calling the filter function.
FIXME: make a seperate pod for filters?
+=head2 Color transformations
+
+You can use the convert method to transform the color space of an
+image using a matrix. For ease of use some presets are provided.
+
+The convert method can be used to:
+
+=over 4
+
+=item *
+
+convert an RGB or RGBA image to grayscale.
+
+=item *
+
+convert a grayscale image to RGB.
+
+=item *
+
+extract a single channel from an image.
+
+=item *
+
+set a given channel to a particular value (or from another channel)
+
+=back
+
+The currently defined presets are:
+
+=over
+
+=item gray
+
+=item grey
+
+converts an RGBA image into a grayscale image with alpha channel, or
+an RGB image into a grayscale image without an alpha channel.
+
+This weights the RGB channels at 22.2%, 70.7% and 7.1% respectively.
+
+=item noalpha
+
+removes the alpha channel from a 2 or 4 channel image. An identity
+for other images.
+
+=item red
+
+=item channel0
+
+extracts the first channel of the image into a single channel image
+
+=item green
+
+=item channel1
+
+extracts the second channel of the image into a single channel image
+
+=item blue
+
+=item channel2
+
+extracts the third channel of the image into a single channel image
+
+=item alpha
+
+extracts the alpha channel of the image into a single channel image.
+
+If the image has 1 or 3 channels (assumed to be grayscale of RGB) then
+the resulting image will be all white.
+
+=item rgb
+
+converts a grayscale image to RGB, preserving the alpha channel if any
+
+=item addalpha
+
+adds an alpha channel to a grayscale or RGB image. Preserves an
+existing alpha channel for a 2 or 4 channel image.
+
+=back
+
+For example, to convert an RGB image into a greyscale image:
+
+ $new = $img->convert(preset=>'grey'); # or gray
+
+or to convert a grayscale image to an RGB image:
+
+ $new = $img->convert(preset=>'rgb');
+
+The presets aren't necessary simple constants in the code, some are
+generated based on the number of channels in the input image.
+
+If you want to perform some other colour transformation, you can use
+the 'matrix' parameter.
+
+For each output pixel the following matrix multiplication is done:
+
+ channel[0] [ [ $c00, $c01, ... ] inchannel[0]
+ [ ... ] = ... x [ ... ]
+ channel[n-1] [ $cn0, ..., $cnn ] ] inchannel[max]
+ 1
+
+So if you want to swap the red and green channels on a 3 channel image:
+
+ $new = $img->convert(matrix=>[ [ 0, 1, 0 ],
+ [ 1, 0, 0 ],
+ [ 0, 0, 1 ] ]);
+
+or to convert a 3 channel image to greyscale using equal weightings:
+
+ $new = $img->convert(matrix=>[ [ 0.333, 0.333, 0.334 ] ])
+
=head2 Transformations
Another special image method is transform. It can be used to generate
View
61 Imager.xs
@@ -778,6 +778,56 @@ i_conv(im,pcoef)
i_conv(im,coeff,len);
myfree(coeff);
+undef_int
+i_convert(im, src, coeff)
+ Imager::ImgRaw im
+ Imager::ImgRaw src
+ PREINIT:
+ float *coeff;
+ int outchan;
+ int inchan;
+ AV *avmain;
+ SV **temp;
+ SV *svsub;
+ AV *avsub;
+ int len;
+ int i, j;
+ CODE:
+ printf("i_convert\n");
+ if (!SvROK(ST(2)) || SvTYPE(SvRV(ST(2))) != SVt_PVAV)
+ croak("i_convert: parameter 3 must be an arrayref\n");
+ avmain = (AV*)SvRV(ST(2));
+ outchan = av_len(avmain)+1;
+ /* find the biggest */
+ inchan = 0;
+ for (j=0; j < outchan; ++j) {
+ temp = av_fetch(avmain, j, 0);
+ if (temp && SvROK(*temp) && SvTYPE(SvRV(*temp)) == SVt_PVAV) {
+ avsub = (AV*)SvRV(*temp);
+ len = av_len(avsub)+1;
+ if (len > inchan)
+ inchan = len;
+ }
+ }
+ coeff = mymalloc(sizeof(float) * outchan * inchan);
+ for (j = 0; j < outchan; ++j) {
+ avsub = (AV*)SvRV(*av_fetch(avmain, j, 0));
+ len = av_len(avsub)+1;
+ for (i = 0; i < len; ++i) {
+ temp = av_fetch(avsub, i, 0);
+ if (temp)
+ coeff[i+j*inchan] = SvNV(*temp);
+ else
+ coeff[i+j*inchan] = 0;
+ }
+ while (i < inchan)
+ coeff[i++ + j*inchan] = 0;
+ }
+ RETVAL = i_convert(im, src, coeff, outchan, inchan);
+ myfree(coeff);
+ printf("i_convert returns %d\n", RETVAL);
+ OUTPUT:
+ RETVAL
float
i_img_diff(im1,im2)
@@ -1853,4 +1903,15 @@ DSO_call(handle,func_index,hv)
+# this is mostly for testing...
+Imager::Color
+i_get_pixel(im, x, y)
+ Imager::ImgRaw im
+ int x
+ int y;
+ CODE:
+ RETVAL = (i_color *)mymalloc(sizeof(i_color));
+ i_gpix(im, x, y, RETVAL);
+ OUTPUT:
+ RETVAL
View
1  MANIFEST
@@ -7,6 +7,7 @@ Makefile.PL
draw.c
draw.h
conv.c
+convert.c
error.c
gaussian.c
ppport.h
View
2  Makefile.PL
@@ -61,7 +61,7 @@ if (defined $Config{'d_dlsymun'}) { $OSDEF .= ' -DDLSYMUN'; }
@objs = qw(Imager.o draw.o image.o io.o iolayer.o log.o
gaussian.o conv.o pnm.o raw.o feat.o font.o
filters.o dynaload.o stackmach.o datatypes.o
- regmach.o trans2.o quant.o error.o);
+ regmach.o trans2.o quant.o error.o convert.o);
%opts=(
'NAME' => 'Imager',
View
107 convert.c
@@ -0,0 +1,107 @@
+/*
+=head1 NAME
+
+ convert.c - image conversions
+
+=head1 SYNOPSIS
+
+ i_convert(outimage, srcimage, coeff, outchans, inchans)
+
+=head1 DESCRIPTION
+
+Converts images from one format to another, typically in this case for
+converting from RGBA to greyscale and back.
+
+=over
+
+=cut
+*/
+
+#include "image.h"
+
+
+/*
+=item i_convert(im, src, coeff, outchan, inchan)
+
+Converts the image src into another image.
+
+coeff contains the co-efficients of an outchan x inchan matrix, for
+each output pixel:
+
+ coeff[0], coeff[1] ...
+ im[x,y] = [ coeff[inchan], coeff[inchan+1]... ] * [ src[x,y], 1]
+ ... coeff[inchan*outchan-1]
+
+If im has the wrong number of channels or is the wrong size then
+i_convert() will re-create it.
+
+=cut
+*/
+
+int
+i_convert(i_img *im, i_img *src, float *coeff, int outchan, int inchan)
+{
+ i_color *vals;
+ int x, y;
+ int i, j;
+ int ilimit, jlimit;
+ double work[MAXCHANNELS];
+
+ mm_log((1,"i_convert(im* 0x%x, src, 0x%x, coeff 0x%x,outchan %d, inchan %d)\n",im,src, coeff,outchan, inchan));
+
+ i_clear_error();
+
+ ilimit = inchan;
+ if (ilimit > src->channels)
+ ilimit = src->channels;
+ if (outchan > MAXCHANNELS) {
+ i_push_error(0, "cannot have outchan > MAXCHANNELS");
+ return 0;
+ }
+
+ /* first check the output image */
+ if (im->channels != outchan || im->xsize != src->xsize
+ || im->ysize != src->ysize) {
+ i_img_empty_ch(im, src->xsize, src->ysize, outchan);
+ }
+ vals = mymalloc(sizeof(i_color) * src->xsize);
+ for (y = 0; y < src->ysize; ++y) {
+ i_glin(src, 0, src->xsize, y, vals);
+ for (x = 0; x < src->xsize; ++x) {
+ for (j = 0; j < outchan; ++j) {
+ work[j] = 0;
+ for (i = 0; i < ilimit; ++i) {
+ work[j] += coeff[i+inchan*j] * vals[x].channel[i];
+ }
+ if (i < inchan) {
+ work[j] += coeff[i+inchan*j] * 255.9;
+ }
+ }
+ for (j = 0; j < outchan; ++j) {
+ if (work[j] < 0)
+ vals[x].channel[j] = 0;
+ else if (work[j] >= 256)
+ vals[x].channel[j] = 255;
+ else
+ vals[x].channel[j] = work[j];
+ }
+ }
+ i_plin(im, 0, src->xsize, y, vals);
+ }
+ myfree(vals);
+ return 1;
+}
+
+/*
+=back
+
+=head1 SEE ALSO
+
+Imager(3)
+
+=head1 AUTHOR
+
+Tony Cook <tony@develop-help.com>
+
+=cut
+*/
View
3  image.h
@@ -96,6 +96,9 @@ void i_flood_fill (i_img *im,int seedx,int seedy,i_color *dcol);
void i_gaussian (i_img *im,float stdev);
void i_conv (i_img *im,float *coeff,int len);
+/* colour manipulation */
+extern int i_convert(i_img *im, i_img *src, float *coeff, int outchan, int inchan);
+
float i_img_diff (i_img *im1,i_img *im2);
/* font routines */
View
66 t/t67convert.t
@@ -0,0 +1,66 @@
+Imager::init(log=>'testout/t67convert.log');
+
+use Imager qw(:all :handy);
+
+print "1..4\n";
+
+my $imbase = Imager::ImgRaw::new(200,300,3);
+
+# first a basic test, make sure the basic things happen ok
+# make a 1 channel image from the above (black) image
+# but with 1 as the 'extra' value
+my $imnew = Imager::i_img_new();
+unless (i_convert($imnew, $imbase, [ [ 0, 0, 0, 1 ] ])) {
+ print "not ok 1 # call failed\n";
+ print "ok 2 # skipped\n";
+ print "ok 3 # skipped\n";
+}
+else {
+ print "ok 1\n";
+ my ($w, $h, $ch) = i_img_info($imnew);
+
+ # the output image should now have one channel
+ if ($ch == 1) {
+ print "ok 2\n";
+ }
+ else {
+ print "not ok 2 # $ch channels in output\n";
+ }
+ # should have the same width and height
+ if ($w == 200 && $h == 300) {
+ print "ok 3\n";
+ }
+ else {
+ print "not ok 3 # output image is the wrong size!\n";
+ }
+ # should be a white image now, let's check
+ my $c = Imager::i_get_pixel($imnew, 20, 20);
+ my @c = $c->rgba;
+ print "# @c\n";
+ if (($c->rgba())[0] == 255) {
+ print "ok 4\n";
+ }
+ else {
+ print "not ok 4 # wrong colour in output image",($c->rgba())[0],"\n";
+ }
+}
+
+# test the highlevel interface
+# currently this requires visual inspection of the output files
+my $im = Imager->new;
+if ($im->read(file=>'testimg/scale.ppm')) {
+ my $out;
+ $out = $im->convert(preset=>'gray')
+ or die "Cannot convert to gray:", $im->errstr;
+ # FIXME we can't save 1 channel ppm files yet
+ $out->write(file=>'testout/t67_gray.ppm', type=>'pnm')
+ or print "# Cannot save testout/t67_gray.ppm:", $out->errstr;
+ $out = $im->convert(preset=>'blue')
+ or die "Cannot convert blue:", $im->errstr;
+ # FIXME we can't save 1 channel ppm files yet
+ $out->write(file=>'testout/t67_blue.ppm', type=>'pnm')
+ or print "# Cannot save testout/t67_blue.ppm:", $out->errstr;
+}
+else {
+ die "could not load testout/scale.ppm\n";
+}
Please sign in to comment.
Something went wrong with that request. Please try again.