add colorchannels(), alphachannel() and colormodel() methods
authorTony Cook <tony@develop-help.com>
Thu, 26 Mar 2015 04:36:49 +0000 (15:36 +1100)
committerTony Cook <tony@develop-help.com>
Thu, 26 Mar 2015 04:36:49 +0000 (15:36 +1100)
also add model parameter to Imager->new()

15 files changed:
Imager.pm
Imager.xs
apidocs.perl
image.c
imager.h
imdatatypes.h
imext.c
imext.h
imexttypes.h
immacros.h
lib/Imager/APIRef.pod
lib/Imager/ImageTypes.pod
t/100-base/010-introvert.t
t/450-api/100-inline.t
xt/x20spell.t

index 4db2139..4fc3243 100644 (file)
--- a/Imager.pm
+++ b/Imager.pm
@@ -696,6 +696,10 @@ sub new {
       return;
     }
   }
+  elsif (%hsh) {
+    Imager->_set_error("new: supply xsize and ysize or a file access parameter or no parameters");
+    return;
+  }
 
   return $self;
 }
@@ -911,16 +915,29 @@ sub _sametype {
 # Sets an image to a certain size and channel number
 # if there was previously data in the image it is discarded
 
+my %model_channels =
+  (
+   gray => 1,
+   graya => 2,
+   rgb => 3,
+   rgba => 4,
+  );
+
 sub img_set {
   my $self=shift;
 
   my %hsh=(xsize=>100, ysize=>100, channels=>3, bits=>8, type=>'direct', @_);
 
-  if (defined($self->{IMG})) {
-    # let IIM_DESTROY destroy it, it's possible this image is
-    # referenced from a virtual image (like masked)
-    #i_img_destroy($self->{IMG});
-    undef($self->{IMG});
+  undef($self->{IMG});
+
+  if ($hsh{model}) {
+    if (my $channels = $model_channels{$hsh{model}}) {
+      $hsh{channels} = $channels;
+    }
+    else {
+      $self->_set_error("new: unknown value for model '$hsh{model}'");
+      return;
+    }
   }
 
   if ($hsh{type} eq 'paletted' || $hsh{type} eq 'pseudo') {
@@ -939,7 +956,7 @@ sub img_set {
   }
 
   unless ($self->{IMG}) {
-    $self->{ERRSTR} = Imager->_error_as_msg();
+    $self->_set_error(Imager->_error_as_msg());
     return;
   }
 
@@ -3901,6 +3918,37 @@ sub getchannels {
   return i_img_getchannels($self->{IMG});
 }
 
+my @model_names = qw(unknown gray graya rgb rgba);
+
+sub colormodel {
+  my ($self, %opts) = @_;
+
+  $self->_valid_image("colormodel")
+    or return;
+
+  my $model = i_img_color_model($self->{IMG});
+
+  return $opts{numeric} ? $model : $model_names[$model];
+}
+
+sub colorchannels {
+  my ($self) = @_;
+
+  $self->_valid_image("colorchannels")
+    or return;
+
+  return i_img_color_channels($self->{IMG});
+}
+
+sub alphachannel {
+  my ($self) = @_;
+
+  $self->_valid_image("alphachannel")
+    or return;
+
+  return scalar(i_img_alpha_channel($self->{IMG}));
+}
+
 # Get channel mask
 
 sub getmask {
@@ -4668,6 +4716,9 @@ addtag() -  L<Imager::ImageTypes/addtag()> - add image tags
 align_string() - L<Imager::Draw/align_string()> - draw text aligned on a
 point
 
+alphachannel() - L<Imager::ImageTypes/alphachannel()> - return the
+channel index of the alpha channel (if any).
+
 arc() - L<Imager::Draw/arc()> - draw a filled arc
 
 bits() - L<Imager::ImageTypes/bits()> - number of bits per sample for the
@@ -4682,9 +4733,15 @@ circle() - L<Imager::Draw/circle()> - draw a filled circle
 close_log() - L<Imager::ImageTypes/close_log()> - close the Imager
 debugging log.
 
+colorchannels() - L<Imager::ImageTypes/colorchannels()> - the number
+of channels used for color.
+
 colorcount() - L<Imager::ImageTypes/colorcount()> - the number of
 colors in an image's palette (paletted images only)
 
+colormodel() - L<Imager::ImageTypes/colorcount()> - how color is
+represented.
+
 combine() - L<Imager::Transformations/combine()> - combine channels
 from one or more images.
 
index 99e4173..6a205ba 100644 (file)
--- a/Imager.xs
+++ b/Imager.xs
@@ -1720,6 +1720,22 @@ IV
 i_img_get_height(im)
     Imager::ImgRaw     im
 
+int
+i_img_color_model(im)
+    Imager::ImgRaw     im
+
+int
+i_img_color_channels(im)
+    Imager::ImgRaw     im
+
+int
+i_img_alpha_channel(im)
+    Imager::ImgRaw     im
+  CODE:
+    if (!i_img_alpha_channel(im, &RETVAL))
+      XSRETURN(0);
+  OUTPUT:
+    RETVAL
 
 void
 i_img_is_monochrome(im)
index 55804e0..927939a 100644 (file)
@@ -193,7 +193,12 @@ close OUT;
 
 
 sub make_func_list {
-  my @funcs = qw(i_img i_color i_fcolor i_fill_t mm_log mm_log i_img_color_channels i_img_has_alpha i_img_dim i_DF i_DFc i_DFp i_DFcp i_psamp_bits i_gsamp_bits i_psamp i_psampf);
+  my @funcs =
+    qw(i_img i_color i_fcolor i_fill_t mm_log mm_log i_color_model_t
+       im_context_t i_img_dim i_img_dim_u im_slot_t
+       i_polygon_t i_poly_fill_mode_t i_mutex_t
+       i_img_has_alpha i_DF i_DFc i_DFp i_DFcp i_psamp_bits i_gsamp_bits
+       i_psamp i_psampf);
   open FUNCS, "< imexttypes.h"
     or die "Cannot open imexttypes.h: $!\n";
   my $in_struct;
diff --git a/image.c b/image.c
index 03dc6e2..f280a28 100644 (file)
--- a/image.c
+++ b/image.c
@@ -404,6 +404,85 @@ i_img_get_height(i_img *im) {
   return im->ysize;
 }
 
+/*
+=item i_img_color_model(im)
+=category Image Information
+=synopsis i_color_model_t cm = i_img_color_model(im);
+
+Returns the color model for the image.
+
+A future version of Imager will allow for images with extra channels
+beyond gray/rgb and alpha.
+
+=cut
+*/
+i_color_model_t
+i_img_color_model(i_img *im) {
+  return (i_color_model_t)im->channels;
+}
+
+/*
+=item i_img_alpha_channel(im, &channel)
+=category Image Information
+=synopsis int alpha_channel;
+=synopsis int has_alpha = i_img_alpha_channel(im, &alpha_channel);
+
+Work out the alpha channel for an image.
+
+If the image has an alpha channel, sets C<*channel> to the alpha
+channel index and returns non-zero.
+
+If the image has no alpha channel, returns zero and C<*channel> is not
+modified.
+
+C<channel> may be C<NULL>.
+
+=cut
+*/
+
+int
+i_img_alpha_channel(i_img *im, int *channel) {
+  i_color_model_t model = i_img_color_model(im);
+  switch (model) {
+  case icm_gray_alpha:
+  case icm_rgb_alpha:
+    if (channel) *channel = (int)model - 1;
+    return 1;
+
+  default:
+    return 0;
+  }
+}
+
+/*
+=item i_img_color_channels(im)
+=category Image Information
+=synopsis int color_channels = i_img_color_channels(im);
+
+Returns the number of color channels in the image.  For now this is
+always 1 (for grayscale) or 3 (for RGB) but may be 0 in some special
+cases in a future release of Imager.
+
+=cut
+*/
+
+int
+i_img_color_channels(i_img *im) {
+  i_color_model_t model = i_img_color_model(im);
+  switch (model) {
+  case icm_gray_alpha:
+  case icm_rgb_alpha:
+    return (int)model - 1;
+
+  case icm_gray:
+  case icm_rgb:
+    return (int)model;
+
+  default:
+    return 0;
+  }
+}
+
 /*
 =item i_copyto_trans(C<im>, C<src>, C<x1>, C<y1>, C<x2>, C<y2>, C<tx>, C<ty>, C<trans>)
 
index 2780286..f5bb2da 100644 (file)
--- a/imager.h
+++ b/imager.h
@@ -64,6 +64,9 @@ int    i_img_getmask    (i_img *im);
 int    i_img_getchannels(i_img *im);
 i_img_dim i_img_get_width(i_img *im);
 i_img_dim i_img_get_height(i_img *im);
+i_color_model_t i_img_color_model(i_img *im);
+int i_img_alpha_channel(i_img *im, int *channel);
+int i_img_color_channels(i_img *im);
 
 /* Base functions */
 
index 30590ce..dcb8e9a 100644 (file)
@@ -732,6 +732,54 @@ typedef struct {
 
 typedef struct i_render_tag i_render;
 
+/*
+=item i_color_model_t
+=category Data Types
+=order 95
+
+Returned by L<i_img_color_model(im)> to indicate the color model of
+the image.
+
+An enumerated type with the following possible values:
+
+=over
+
+=item *
+
+C<icm_unknown> - the image has no usable color data.  In future
+versions of Imager this will be returned in a few limited cases,
+eg. when the source image is CMYK and the user has requested no color
+translation is done.
+
+=item *
+
+C<icm_gray> - gray scale with no alpha channel.
+
+=item *
+
+C<icm_gray_alpha> - gray scale with an alpha channel.
+
+=item *
+
+C<icm_rgb> - RGB
+
+=item *
+
+C<icm_rgb_alpha> - RGB with an alpha channel.
+
+=back
+
+=cut
+*/
+
+typedef enum {
+  icm_unknown,
+  icm_gray,
+  icm_gray_alpha,
+  icm_rgb,
+  icm_rgb_alpha
+} i_color_model_t;
+
 #ifdef IMAGER_FORMAT_ATTR
 #define I_FORMAT_ATTR(format_index, va_index) \
   __attribute ((format (printf, format_index, va_index)))
diff --git a/imext.c b/imext.c
index 98f2c45..36ba2b6 100644 (file)
--- a/imext.c
+++ b/imext.c
@@ -210,7 +210,12 @@ im_ext_funcs imager_function_table =
     i_poly_poly_aa,
     i_poly_poly_aa_cfill,
     i_poly_aa_m,
-    i_poly_aa_cfill_m
+    i_poly_aa_cfill_m,
+
+    /* level 10 */
+    i_img_alpha_channel,
+    i_img_color_model,
+    i_img_color_channels
   };
 
 /* in general these functions aren't called by Imager internally, but
diff --git a/imext.h b/imext.h
index 6561e9a..c288a42 100644 (file)
--- a/imext.h
+++ b/imext.h
@@ -243,6 +243,10 @@ extern im_ext_funcs *imager_function_ext_table;
 
 #define im_push_errorf (im_extt->f_im_push_errorf)
 
+#define i_img_alpha_channel(im, channel) ((im_extt->f_i_img_alpha_channel)((im), (channel)))
+#define i_img_color_model(im) ((im_extt->f_i_img_color_model)((im)))
+#define i_img_color_channels(im) ((im_extt->f_i_img_color_channels)((im)))
+
 #ifdef IMAGER_LOG
 #ifndef IMAGER_NO_CONTEXT
 #define mm_log(x) { i_lhead(__FILE__,__LINE__); i_loog x; } 
index 354b211..320bea1 100644 (file)
@@ -34,7 +34,7 @@
  will result in an increment of IMAGER_API_LEVEL.
 */
 
-#define IMAGER_API_LEVEL 9
+#define IMAGER_API_LEVEL 10
 
 typedef struct {
   int version;
@@ -266,9 +266,12 @@ typedef struct {
                             const double *y, i_poly_fill_mode_t mode,
                             i_fill_t *fill);
 
-  /* IMAGER_API_LEVEL 10 functions will be added here */
-
+  /* IMAGER_API_LEVEL 10 */
+  int (*f_i_img_alpha_channel)(i_img *im, int *channel);
+  i_color_model_t (*f_i_img_color_model)(i_img *im);
+  int (*f_i_img_color_channels)(i_img *im);
 
+  /* IMAGER_API_LEVEL 11 functions will be added here */
 } im_ext_funcs;
 
 #define PERL_FUNCTION_TABLE_NAME "Imager::__ext_func_table"
index 0dbe5e5..8b45f58 100644 (file)
@@ -16,19 +16,7 @@ Return true if the image has an alpha channel.
 =cut
 */
 
-#define i_img_has_alpha(im) ((im)->channels == 2 || (im)->channels == 4)
-
-/*
-=item i_img_color_channels(C<im>)
-
-=category Image Information
-
-The number of channels holding color information.
-
-=cut
-*/
-
-#define i_img_color_channels(im) (i_img_has_alpha(im) ? (im)->channels - 1 : (im)->channels)
+#define i_img_has_alpha(im) (i_img_alpha_channel((im), NULL))
 
 /*
 =item i_psamp(im, left, right, y, samples, channels, channel_count)
index 89554be..69e1b14 100644 (file)
@@ -114,11 +114,18 @@ Imager::APIRef - Imager's C API - reference.
   int channels = i_img_getchannels(img);
   i_img_dim width = i_img_get_width(im);
   i_img_dim height = i_img_get_height(im);
+  i_color_model_t cm = i_img_color_model(im);
+  int alpha_channel;
+  int has_alpha = i_img_alpha_channel(im, &alpha_channel);
+  int color_channels = i_img_color_channels(im);
 
   # Image quantization
 
   # Logging
 
+  # mutex
+  i_mutex_t mutex;
+
   # Mutex functions
   i_mutex_t m = i_mutex_new();
   i_mutex_destroy(m);
@@ -387,6 +394,44 @@ A signed integer type that represents an image dimension or ordinate.
 May be larger than int on some platforms.
 
 
+=for comment
+From: File imdatatypes.h
+
+=item i_color_model_t
+
+Returned by L</i_img_color_model(im)> to indicate the color model of
+the image.
+
+An enumerated type with the following possible values:
+
+=over
+
+=item *
+
+C<icm_unknown> - the image has no usable color data.  In future
+versions of Imager this will be returned in a few limited cases,
+eg. when the source image is CMYK and the user has requested no color
+translation is done.
+
+=item *
+
+C<icm_gray> - gray scale with no alpha channel.
+
+=item *
+
+C<icm_gray_alpha> - gray scale with an alpha channel.
+
+=item *
+
+C<icm_rgb> - RGB
+
+=item *
+
+C<icm_rgb_alpha> - RGB with an alpha channel.
+
+=back
+
+
 =for comment
 From: File imdatatypes.h
 
@@ -1769,14 +1814,49 @@ From: File image.c
 
 =over
 
-=item i_img_color_channels(C<im>)
+=item i_img_alpha_channel(im, &channel)
+
+  int alpha_channel;
+  int has_alpha = i_img_alpha_channel(im, &alpha_channel);
+
+Work out the alpha channel for an image.
 
+If the image has an alpha channel, sets C<*channel> to the alpha
+channel index and returns non-zero.
 
-The number of channels holding color information.
+If the image has no alpha channel, returns zero and C<*channel> is not
+modified.
+
+C<channel> may be C<NULL>.
 
 
 =for comment
-From: File immacros.h
+From: File image.c
+
+=item i_img_color_channels(im)
+
+  int color_channels = i_img_color_channels(im);
+
+Returns the number of color channels in the image.  For now this is
+always 1 (for grayscale) or 3 (for RGB) but may be 0 in some special
+cases in a future release of Imager.
+
+
+=for comment
+From: File image.c
+
+=item i_img_color_model(im)
+
+  i_color_model_t cm = i_img_color_model(im);
+
+Returns the color model for the image.
+
+A future version of Imager will allow for images with extra channels
+beyond gray/rgb and alpha.
+
+
+=for comment
+From: File image.c
 
 =item i_img_get_height(C<im>)
 
@@ -1944,6 +2024,24 @@ This is an internal function called by the mm_log() macro.
 From: File log.c
 
 
+=back
+
+=head2 mutex
+
+=over
+
+=item i_mutex_t
+X<i_mutex>
+
+  i_mutex_t mutex;
+
+Opaque type for Imager's mutex API.
+
+
+=for comment
+From: File imdatatypes.h
+
+
 =back
 
 =head2 Mutex functions
@@ -2430,6 +2528,22 @@ will change:
 
 =item *
 
+B<i_img_dim_u>
+
+=item *
+
+B<i_poly_fill_mode_t>
+
+=item *
+
+B<i_polygon_t>
+
+=item *
+
+B<im_context_t>
+
+=item *
+
 B<im_lhead>
 
 =item *
@@ -2438,6 +2552,10 @@ B<im_loog>
 
 =item *
 
+B<im_slot_t>
+
+=item *
+
 B<mm_log>
 
 
index 80ac92b..de5e233 100644 (file)
@@ -173,6 +173,11 @@ values are from 1 to 4.
 
 =item *
 
+C<model> - this overrides the value, if any, supplied for C<channels>.
+This can be one of C<gray>, C<graya>, C<rgb> or C<rgba>.
+
+=item *
+
 C<bits> - The storage type for samples in the image.  Default: 8.
 Valid values are:
 
@@ -286,6 +291,11 @@ parameter:
   my $img = Imager->new(file => $filename)
     or die Imager->errstr;
 
+If none of C<xsize>, C<ysize>, C<file>, C<fh>, C<fd>, C<callback>,
+C<readcb> or C<data> is supplied, and other parameters I<are> supplied
+C<< Imager->new >> will return failure rather than returning an empty
+image object.
+
 =item img_set()
 
 img_set destroys the image data in the object and creates a new one
@@ -321,11 +331,60 @@ value of C<getwidth()> is undef.
 Same details apply as for L</getwidth()>.
 
 =item getchannels()
+X<getchannels() method>X<methods, getchannels()>
 
   print "Image has ",$img->getchannels(), " channels\n";
 
-To get the number of channels in an image C<getchannels()> is used.
+Returns the number of channels in an image.
+
+Note: previously the number of channels in an image mapped directly to
+the color model of the image, ie a 4 channel image was always RGBA.
+This may change in a future release of Imager.
+
+Returns an empty list if the image object is not initialized.
+
+=item colorchannels()
+X<colorchannels() method>X<methods, colorchannels()>
+
+Returns the number of color channels.
+
+Currently this is always 1 or 3, but may be 0 for some rare images in
+a future version of Imager.
+
+Returns an empty list if the image object is not initialized.
+
+=item colormodel()
+X<colormodel method>X<methods, colormodel>
+
+Returns the color model of the image, including whether there is an
+alpha channel.
 
+By default this is returned as a string, one of C<unknown>, C<gray>,
+C<graya>, C<rgb> or C<rgba>.
+
+If you call C<colormodel()> with a true numeric parameter:
+
+  my $model = $img->colormodel(numeric => 1);
+
+then the color model is returned as a number, mapped as follows:
+
+  Numeric  String
+  -------  ------
+      0    unknown
+      1    gray
+      2    graya
+      3    rgb
+      4    rgba
+
+=item alphachannel()
+X<alphachannel() method>X<methods, alphachannel()>
+
+Returns the channel index of the alpha channel of the image.
+
+This is 1 for grayscale images with alpha, 3 for RGB images with alpha
+and will return C<undef> for all other images.
+
+Returns an empty list if the image object is not initialized.
 
 =item bits()
 
@@ -340,6 +399,8 @@ channel in a pixel, 8 for a normal image, 16 for 16-bit image and
     # slower but more precise
   }
 
+Returns an empty list if the image object is not initialized.
+
 =item type()
 
 The type() method returns either 'direct' for direct color images or
@@ -352,6 +413,8 @@ The type() method returns either 'direct' for direct color images or
     }
   }
 
+Returns an empty list if the image object is not initialized.
+
 =item virtual()
 
 The virtual() method returns non-zero if the image contains no actual
@@ -398,6 +461,8 @@ those 2 colors are black and white, in either order.
 If a real bi-level organization image is ever added to Imager, this
 function will return true for that too.
 
+Returns an empty list if the image object is not initialized.
+
 =back
 
 =head2 Direct Type Images
index 8e46f58..a52aadd 100644 (file)
@@ -3,7 +3,7 @@
 # to make sure we get expected values
 
 use strict;
-use Test::More tests => 466;
+use Test::More tests => 492;
 
 BEGIN { use_ok(Imager => qw(:handy :all)) }
 
@@ -151,6 +151,10 @@ is($impal2->bits, 8, "check bits");
 is($impal2->type, 'paletted', "check type");
 is($impal2->getwidth, 200, "check width");
 is($impal2->getheight, 201, "check height");
+is($impal2->colormodel, "rgb", "check color model (string)");
+is($impal2->colormodel(numeric => 1), 3, "check color model (numeric)");
+is($impal2->alphachannel, undef, "check alpha channel (has none)");
+is($impal2->colorchannels, 3, "check colorchannels");
 
 {
   my $red_idx = $impal2->addcolors(colors=>[$red]);
@@ -227,6 +231,12 @@ is($impal2->getheight, 201, "check height");
   is($im->errstr, "getmask: empty input image", "check message");
   is($im->setmask, undef, "can't set mask of empty image");
   is($im->errstr, "setmask: empty input image", "check message");
+  is($im->colorchannels, undef, "can't get colorchannels of empty image");
+  is($im->errstr, "colorchannels: empty input image", "check message");
+  is($im->alphachannel, undef, "can't get alphachannel of empty image");
+  is($im->errstr, "alphachannel: empty input image", "check message");
+  is($im->colormodel, undef, "can't get colormodel of empty image");
+  is($im->errstr, "colormodel: empty input image", "check message");
 }
 
 { # basic checks, 8-bit direct images
@@ -1121,6 +1131,25 @@ my $psamp_outside_error = "Image position outside of image";
      "check error message");
 }
 
+{
+  my @tests =
+    (
+     [ "gray",  1, undef ],
+     [ "graya", 1, 1     ],
+     [ "rgb",   3, undef ],
+     [ "rgba",  3, 3     ],
+    );
+  for my $test (@tests) {
+    my ($model, $color_channels, $alpha) = @$test;
+    my $im = Imager->new(model => $model, xsize => 10, ysize => 10)
+      or die "Cannot create $model image:", Imager->errstr;
+    ok($im, "make a $model image via model");
+    is($im->colormodel, $model, "check colormodel is $model");
+    is($im->alphachannel, $alpha, "check alphachannel");
+    is($im->colorchannels, $color_channels, "check colorchannels");
+  }
+}
+
 Imager->close_log();
 
 unless ($ENV{IMAGER_KEEP_FILES}) {
index c9a1f93..9d2cd4f 100644 (file)
@@ -21,7 +21,7 @@ plan skip_all => "perl 5.005_04, 5.005_05 too buggy"
 
 print STDERR "Inline version $Inline::VERSION\n";
 
-plan tests => 117;
+plan tests => 120;
 require Inline;
 Inline->import(with => 'Imager');
 Inline->import("FORCE"); # force rebuild
@@ -467,6 +467,25 @@ test_slots() {
   return 1;
 }
 
+int
+color_channels(Imager im) {
+  return i_img_color_channels(im);
+}
+
+int
+color_model(Imager im) {
+  return (int)i_img_color_model(im);
+}
+
+int
+alpha_channel(Imager im) {
+  int channel;
+  if (!i_img_alpha_channel(im, &channel))
+    channel = -1;
+
+  return channel;
+}
+
 EOS
 
 my $im = Imager->new(xsize=>50, ysize=>50);
@@ -683,6 +702,13 @@ for my $bits (8, 16) {
   is($im->type, "paletted", "make sure we kept the image type");
 }
 
+{
+  my $rgb = Imager->new(xsize => 10, ysize => 10);
+  is(color_model($rgb), 3, "check i_img_color_model() api");
+  is(color_channels($rgb), 3, "check i_img_color_channels() api");
+  is(alpha_channel($rgb), -1, "check i_img_alpha_channel() api");
+}
+
 ok(test_mutex(), "call mutex APIs");
 
 ok(test_slots(), "call slot APIs");
index 9661e0e..5a86172 100644 (file)
@@ -48,6 +48,7 @@ dpi
 eg
 equalities
 gaussian
+grayscale
 ie
 infix
 invocant