X-Git-Url: http://git.imager.perl.org/imager.git/blobdiff_plain/7a0584ef504892468c40ee8adbbad6c1b1551242..95c08d719e77cfa1a9c893abbf110ad0a09f6c00:/image.c diff --git a/image.c b/image.c index a4c2b309..bf25b7d2 100644 --- a/image.c +++ b/image.c @@ -1,5 +1,5 @@ -#include "image.h" -#include "io.h" +#include "imager.h" +#include "imageri.h" /* =head1 NAME @@ -25,7 +25,7 @@ color objects for Imager. Some of these functions are internal. -=over 4 +=over =cut */ @@ -37,7 +37,70 @@ Some of these functions are internal. #define minmax(a,b,i) ( ((a>=i)?a: ( (b<=i)?b:i )) ) /* Hack around an obscure linker bug on solaris - probably due to builtin gcc thingies */ -void fake() { ceil(1); } +static void fake(void) { ceil(1); } + +static int i_ppix_d(i_img *im, int x, int y, const i_color *val); +static int i_gpix_d(i_img *im, int x, int y, i_color *val); +static int i_glin_d(i_img *im, int l, int r, int y, i_color *vals); +static int i_plin_d(i_img *im, int l, int r, int y, const i_color *vals); +static int i_ppixf_d(i_img *im, int x, int y, const i_fcolor *val); +static int i_gpixf_d(i_img *im, int x, int y, i_fcolor *val); +static int i_glinf_d(i_img *im, int l, int r, int y, i_fcolor *vals); +static int i_plinf_d(i_img *im, int l, int r, int y, const i_fcolor *vals); +static int i_gsamp_d(i_img *im, int l, int r, int y, i_sample_t *samps, const int *chans, int chan_count); +static int i_gsampf_d(i_img *im, int l, int r, int y, i_fsample_t *samps, const int *chans, int chan_count); + +/* +=item i_img_alloc() +=category Image Implementation + +Allocates a new i_img structure. + +When implementing a new image type perform the following steps in your +image object creation function: + +=over + +=item 1. + +allocate the image with i_img_alloc(). + +=item 2. + +initialize any function pointers or other data as needed, you can +overwrite the whole block if you need to. + +=item 3. + +initialize Imager's internal data by calling i_img_init() on the image +object. + +=back + +=cut +*/ + +i_img * +i_img_alloc(void) { + return mymalloc(sizeof(i_img)); +} + +/* +=item i_img_init(C) +=category Image Implementation + +Imager internal initialization of images. + +Currently this does very little, in the future it may be used to +support threads, or color profiles. + +=cut +*/ + +void +i_img_init(i_img *img) { + img->im_data = NULL; +} /* =item ICL_new_internal(r, g, b, a) @@ -58,7 +121,7 @@ ICL_new_internal(unsigned char r,unsigned char g,unsigned char b,unsigned char a mm_log((1,"ICL_new_internal(r %d,g %d,b %d,a %d)\n", r, g, b, a)); - if ( (cl=mymalloc(sizeof(i_color))) == NULL) m_fatal(2,"malloc() error\n"); + if ( (cl=mymalloc(sizeof(i_color))) == NULL) i_fatal(2,"malloc() error\n"); cl->rgba.r = r; cl->rgba.g = g; cl->rgba.b = b; @@ -87,7 +150,7 @@ ICL_set_internal(i_color *cl,unsigned char r,unsigned char g,unsigned char b,uns mm_log((1,"ICL_set_internal(cl* %p,r %d,g %d,b %d,a %d)\n",cl,r,g,b,a)); if (cl == NULL) if ( (cl=mymalloc(sizeof(i_color))) == NULL) - m_fatal(2,"malloc() error\n"); + i_fatal(2,"malloc() error\n"); cl->rgba.r=r; cl->rgba.g=g; cl->rgba.b=b; @@ -129,7 +192,7 @@ Dump color information to log - strictly for debugging. */ void -ICL_info(i_color *cl) { +ICL_info(i_color const *cl) { mm_log((1,"i_color_info(cl* %p)\n",cl)); mm_log((1,"i_color_info: (%d,%d,%d,%d)\n",cl->rgba.r,cl->rgba.g,cl->rgba.b,cl->rgba.a)); } @@ -150,10 +213,108 @@ ICL_DESTROY(i_color *cl) { myfree(cl); } +/* +=item i_fcolor_new(double r, double g, double b, double a) + +=cut +*/ +i_fcolor *i_fcolor_new(double r, double g, double b, double a) { + i_fcolor *cl = NULL; + + mm_log((1,"i_fcolor_new(r %g,g %g,b %g,a %g)\n", r, g, b, a)); + + if ( (cl=mymalloc(sizeof(i_fcolor))) == NULL) i_fatal(2,"malloc() error\n"); + cl->rgba.r = r; + cl->rgba.g = g; + cl->rgba.b = b; + cl->rgba.a = a; + mm_log((1,"(%p) <- i_fcolor_new\n",cl)); + + return cl; +} + +/* +=item i_fcolor_destroy(i_fcolor *cl) + +=cut +*/ +void i_fcolor_destroy(i_fcolor *cl) { + myfree(cl); +} + +/* +=item IIM_base_8bit_direct (static) + +A static i_img object used to initialize direct 8-bit per sample images. + +=cut +*/ +static i_img IIM_base_8bit_direct = +{ + 0, /* channels set */ + 0, 0, 0, /* xsize, ysize, bytes */ + ~0U, /* ch_mask */ + i_8_bits, /* bits */ + i_direct_type, /* type */ + 0, /* virtual */ + NULL, /* idata */ + { 0, 0, NULL }, /* tags */ + NULL, /* ext_data */ + + i_ppix_d, /* i_f_ppix */ + i_ppixf_d, /* i_f_ppixf */ + i_plin_d, /* i_f_plin */ + i_plinf_d, /* i_f_plinf */ + i_gpix_d, /* i_f_gpix */ + i_gpixf_d, /* i_f_gpixf */ + i_glin_d, /* i_f_glin */ + i_glinf_d, /* i_f_glinf */ + i_gsamp_d, /* i_f_gsamp */ + i_gsampf_d, /* i_f_gsampf */ + + NULL, /* i_f_gpal */ + NULL, /* i_f_ppal */ + NULL, /* i_f_addcolors */ + NULL, /* i_f_getcolors */ + NULL, /* i_f_colorcount */ + NULL, /* i_f_maxcolors */ + NULL, /* i_f_findcolor */ + NULL, /* i_f_setcolors */ + + NULL, /* i_f_destroy */ + + i_gsamp_bits_fb, + NULL, /* i_f_psamp_bits */ +}; + +/*static void set_8bit_direct(i_img *im) { + im->i_f_ppix = i_ppix_d; + im->i_f_ppixf = i_ppixf_d; + im->i_f_plin = i_plin_d; + im->i_f_plinf = i_plinf_d; + im->i_f_gpix = i_gpix_d; + im->i_f_gpixf = i_gpixf_d; + im->i_f_glin = i_glin_d; + im->i_f_glinf = i_glinf_d; + im->i_f_gpal = NULL; + im->i_f_ppal = NULL; + im->i_f_addcolor = NULL; + im->i_f_getcolor = NULL; + im->i_f_colorcount = NULL; + im->i_f_findcolor = NULL; + }*/ + /* =item IIM_new(x, y, ch) -Creates a new image object I pixels wide, and I pixels high with I channels. +=item i_img_8_new(x, y, ch) + +=category Image creation/destruction + +=synopsis i_img *img = i_img_8_new(width, height, channels); + +Creates a new image object I pixels wide, and I pixels high with +I channels. =cut */ @@ -174,11 +335,10 @@ IIM_new(int x,int y,int ch) { void IIM_DESTROY(i_img *im) { mm_log((1,"IIM_DESTROY(im* %p)\n",im)); + i_img_destroy(im); /* myfree(cl); */ } - - /* =item i_img_new() @@ -194,21 +354,18 @@ i_img_new() { i_img *im; mm_log((1,"i_img_struct()\n")); - if ( (im=mymalloc(sizeof(i_img))) == NULL) - m_fatal(2,"malloc() error\n"); + + im = i_img_alloc(); + *im = IIM_base_8bit_direct; im->xsize=0; im->ysize=0; im->channels=3; im->ch_mask=MAXINT; im->bytes=0; - im->data=NULL; + im->idata=NULL; - im->i_f_ppix=i_ppix_d; - im->i_f_gpix=i_gpix_d; - im->i_f_plin=i_plin_d; - im->i_f_glin=i_glin_d; - im->ext_data=NULL; + i_img_init(im); mm_log((1,"(%p) <- i_img_struct\n",im)); return im; @@ -223,32 +380,17 @@ Re-new image reference (assumes 3 channels) x - xsize of destination image y - ysize of destination image +**FIXME** what happens if a live image is passed in here? + +Should this just call i_img_empty_ch()? + =cut */ i_img * i_img_empty(i_img *im,int x,int y) { mm_log((1,"i_img_empty(*im %p, x %d, y %d)\n",im, x, y)); - if (im==NULL) - if ( (im=mymalloc(sizeof(i_img))) == NULL) - m_fatal(2,"malloc() error\n"); - - im->xsize = x; - im->ysize = y; - im->channels = 3; - im->ch_mask = MAXINT; - im->bytes=x*y*im->channels; - if ( (im->data = mymalloc(im->bytes)) == NULL) m_fatal(2,"malloc() error\n"); - memset(im->data, 0, (size_t)im->bytes); - - im->i_f_ppix = i_ppix_d; - im->i_f_gpix = i_gpix_d; - im->i_f_plin = i_plin_d; - im->i_f_glin = i_glin_d; - im->ext_data = NULL; - - mm_log((1,"(%p) <- i_img_empty\n", im)); - return im; + return i_img_empty_ch(im, x, y, 3); } /* @@ -266,24 +408,42 @@ Re-new image reference i_img * i_img_empty_ch(i_img *im,int x,int y,int ch) { + int bytes; + mm_log((1,"i_img_empty_ch(*im %p, x %d, y %d, ch %d)\n", im, x, y, ch)); + + if (x < 1 || y < 1) { + i_push_error(0, "Image sizes must be positive"); + return NULL; + } + if (ch < 1 || ch > MAXCHANNELS) { + i_push_errorf(0, "channels must be between 1 and %d", MAXCHANNELS); + return NULL; + } + /* check this multiplication doesn't overflow */ + bytes = x*y*ch; + if (bytes / y / ch != x) { + i_push_errorf(0, "integer overflow calculating image allocation"); + return NULL; + } + if (im == NULL) - if ( (im=mymalloc(sizeof(i_img))) == NULL) - m_fatal(2,"malloc() error\n"); - + im = i_img_alloc(); + + memcpy(im, &IIM_base_8bit_direct, sizeof(i_img)); + i_tags_new(&im->tags); im->xsize = x; im->ysize = y; im->channels = ch; im->ch_mask = MAXINT; - im->bytes=x*y*im->channels; - if ( (im->data=mymalloc(im->bytes)) == NULL) m_fatal(2,"malloc() error\n"); - memset(im->data,0,(size_t)im->bytes); + im->bytes=bytes; + if ( (im->idata=mymalloc(im->bytes)) == NULL) + i_fatal(2,"malloc() error\n"); + memset(im->idata,0,(size_t)im->bytes); - im->i_f_ppix = i_ppix_d; - im->i_f_gpix = i_gpix_d; - im->i_f_plin = i_plin_d; - im->i_f_glin = i_glin_d; im->ext_data = NULL; + + i_img_init(im); mm_log((1,"(%p) <- i_img_empty_ch\n",im)); return im; @@ -302,8 +462,11 @@ Free image data. void i_img_exorcise(i_img *im) { mm_log((1,"i_img_exorcise(im* 0x%x)\n",im)); - if (im->data != NULL) { myfree(im->data); } - im->data = NULL; + i_tags_destroy(&im->tags); + if (im->i_f_destroy) + (im->i_f_destroy)(im); + if (im->idata != NULL) { myfree(im->idata); } + im->idata = NULL; im->xsize = 0; im->ysize = 0; im->channels = 0; @@ -316,18 +479,19 @@ i_img_exorcise(i_img *im) { } /* -=item i_img_destroy(im) - -Destroy image and free data via exorcise. +=item i_img_destroy(C) +=order 90 +=category Image creation/destruction +=synopsis i_img_destroy(img) - im - Image pointer +Destroy an image object =cut */ void i_img_destroy(i_img *im) { - mm_log((1,"i_img_destroy(im* 0x%x)\n",im)); + mm_log((1,"i_img_destroy(im %p)\n",im)); i_img_exorcise(im); if (im) { myfree(im); } } @@ -335,6 +499,8 @@ i_img_destroy(i_img *im) { /* =item i_img_info(im, info) +=category Image + Return image information im - Image pointer @@ -356,7 +522,7 @@ i_img_info(i_img *im,int *info) { mm_log((1,"i_img_info(im 0x%x)\n",im)); if (im != NULL) { mm_log((1,"i_img_info: xsize=%d ysize=%d channels=%d mask=%ud\n",im->xsize,im->ysize,im->channels,im->ch_mask)); - mm_log((1,"i_img_info: data=0x%d\n",im->data)); + mm_log((1,"i_img_info: idata=0x%d\n",im->idata)); info[0] = im->xsize; info[1] = im->ysize; info[2] = im->channels; @@ -370,9 +536,15 @@ i_img_info(i_img *im,int *info) { } /* -=item i_img_setmask(im, ch_mask) +=item i_img_setmask(C, C) +=category Image Information +=synopsis // only channel 0 writeable +=synopsis i_img_setmask(img, 0x01); -Set the image channel mask for I to I. +Set the image channel mask for C to C. + +The image channel mask gives some control over which channels can be +written to in the image. =cut */ @@ -381,9 +553,11 @@ i_img_setmask(i_img *im,int ch_mask) { im->ch_mask=ch_mask; } /* -=item i_img_getmask(im) +=item i_img_getmask(C) +=category Image Information +=synopsis int mask = i_img_getmask(img); -Get the image channel mask for I. +Get the image channel mask for C. =cut */ @@ -391,196 +565,59 @@ int i_img_getmask(i_img *im) { return im->ch_mask; } /* -=item i_img_getchannels(im) +=item i_img_getchannels(C) +=category Image Information +=synopsis int channels = i_img_getchannels(img); -Get the number of channels in I. +Get the number of channels in C. =cut */ int i_img_getchannels(i_img *im) { return im->channels; } - -/* -=item i_ppix(im, x, y, col) - -Sets the pixel at (I,I) in I to I. - -Returns true if the pixel could be set, false if x or y is out of -range. - -=cut -*/ -int -i_ppix(i_img *im, int x, int y, i_color *val) { return im->i_f_ppix(im, x, y, val); } - /* -=item i_gpix(im, x, y, &col) +=item i_img_get_width(C) +=category Image Information +=synopsis i_img_dim width = i_img_get_width(im); -Get the pixel at (I,I) in I into I. - -Returns true if the pixel could be retrieved, false otherwise. - -=cut -*/ -int -i_gpix(i_img *im, int x, int y, i_color *val) { return im->i_f_gpix(im, x, y, val); } - -/* -=item i_ppix_d(im, x, y, col) - -Internal function. - -This is the function kept in the i_f_ppix member of an i_img object. -It does a normal store of a pixel into the image with range checking. - -Returns true if the pixel could be set, false otherwise. +Returns the width in pixels of the image. =cut */ -int -i_ppix_d(i_img *im, int x, int y, i_color *val) { - int ch; - - if ( x>-1 && xxsize && y>-1 && yysize ) { - for(ch=0;chchannels;ch++) - if (im->ch_mask&(1<data[(x+y*im->xsize)*im->channels+ch]=val->channel[ch]; - return 0; - } - return -1; /* error was clipped */ +i_img_dim +i_img_get_width(i_img *im) { + return im->xsize; } /* -=item i_gpix_d(im, x, y, &col) +=item i_img_get_height(C) +=category Image Information +=synopsis i_img_dim height = i_img_get_height(im); -Internal function. - -This is the function kept in the i_f_gpix member of an i_img object. -It does normal retrieval of a pixel from the image with range checking. - -Returns true if the pixel could be set, false otherwise. +Returns the height in pixels of the image. =cut */ -int -i_gpix_d(i_img *im, int x, int y, i_color *val) { - int ch; - if (x>-1 && xxsize && y>-1 && yysize) { - for(ch=0;chchannels;ch++) - val->channel[ch]=im->data[(x+y*im->xsize)*im->channels+ch]; - return 0; - } - return -1; /* error was cliped */ -} - -/* -=item i_glin_d(im, l, r, y, vals) - -Reads a line of data from the image, storing the pixels at vals. - -The line runs from (l,y) inclusive to (r,y) non-inclusive - -vals should point at space for (r-l) pixels. - -l should never be less than zero (to avoid confusion about where to -put the pixels in vals). - -Returns the number of pixels copied (eg. if r, l or y is out of range) - -=cut */ -int -i_glin_d(i_img *im, int l, int r, int y, i_color *vals) { - int x, ch; - int count; - int i; - unsigned char *data; - if (y >=0 && y < im->ysize && l < im->xsize && l >= 0) { - if (r > im->xsize) - r = im->xsize; - data = im->data + (l+y*im->xsize) * im->channels; - count = r - l; - for (i = 0; i < count; ++i) { - for (ch = 0; ch < im->channels; ++ch) - vals[i].channel[ch] = *data++; - } - return count; - } - else { - return 0; - } -} -/* -=item i_plin_d(im, l, r, y, vals) - -Writes a line of data into the image, using the pixels at vals. - -The line runs from (l,y) inclusive to (r,y) non-inclusive - -vals should point at (r-l) pixels. - -l should never be less than zero (to avoid confusion about where to -get the pixels in vals). - -Returns the number of pixels copied (eg. if r, l or y is out of range) - -=cut */ -int -i_plin_d(i_img *im, int l, int r, int y, i_color *vals) { - int x, ch; - int count; - int i; - unsigned char *data; - if (y >=0 && y < im->ysize && l < im->xsize && l >= 0) { - if (r > im->xsize) - r = im->xsize; - data = im->data + (l+y*im->xsize) * im->channels; - count = r - l; - for (i = 0; i < count; ++i) { - for (ch = 0; ch < im->channels; ++ch) { - if (im->ch_mask & (1 << ch)) - *data = vals[i].channel[ch]; - ++data; - } - } - return count; - } - else { - return 0; - } +i_img_dim +i_img_get_height(i_img *im) { + return im->ysize; } /* -=item i_ppix_pch(im, x, y, ch) - -Get the value from the channel I for pixel (I,I) from I -scaled to [0,1]. - -Returns zero if x or y is out of range. +=item i_copyto_trans(C, C, C, C, C, C, C, C, C) -Warning: this ignores the vptr interface for images. - -=cut -*/ -float -i_gpix_pch(i_img *im,int x,int y,int ch) { - if (x>-1 && xxsize && y>-1 && yysize) return ((float)im->data[(x+y*im->xsize)*im->channels+ch]/255); - else return 0; -} - - -/* -=item i_copyto_trans(im, src, x1, y1, x2, y2, tx, ty, trans) +=category Image -(x1,y1) (x2,y2) specifies the region to copy (in the source coordinates) -(tx,ty) specifies the upper left corner for the target image. -pass NULL in trans for non transparent i_colors. +(C,C) (C,C) specifies the region to copy (in the +source coordinates) (C,C) specifies the upper left corner for +the target image. pass NULL in C for non transparent i_colors. =cut */ void -i_copyto_trans(i_img *im,i_img *src,int x1,int y1,int x2,int y2,int tx,int ty,i_color *trans) { +i_copyto_trans(i_img *im,i_img *src,int x1,int y1,int x2,int y2,int tx,int ty,const i_color *trans) { i_color pv; int x,y,t,ttx,tty,tt,ch; @@ -610,215 +647,67 @@ i_copyto_trans(i_img *im,i_img *src,int x1,int y1,int x2,int y2,int tx,int ty,i_ } /* -=item i_copyto(dest, src, x1, y1, x2, y2, tx, ty) +=item i_copy(source) -Copies image data from the area (x1,y1)-[x2,y2] in the source image to -a rectangle the same size with it's top-left corner at (tx,ty) in the -destination image. +=category Image -If x1 > x2 or y1 > y2 then the corresponding co-ordinates are swapped. +Creates a new image that is a copy of the image C. + +Tags are not copied, only the image data. + +Returns: i_img * =cut */ -void -i_copyto(i_img *im, i_img *src, int x1, int y1, int x2, int y2, int tx, int ty) { - i_color pv; - int x, y, t, ttx, tty; +i_img * +i_copy(i_img *src) { + int y, y1, x1; + i_img *im = i_sametype(src, src->xsize, src->ysize); - if (x2xsize; + y1 = src->ysize; + if (src->type == i_direct_type) { + if (src->bits == i_8_bits) { + i_color *pv; + pv = mymalloc(sizeof(i_color) * x1); + + for (y = 0; y < y1; ++y) { + i_glin(src, 0, x1, y, pv); + i_plin(im, 0, x1, y, pv); + } + myfree(pv); + } + else { + i_fcolor *pv; + + pv = mymalloc(sizeof(i_fcolor) * x1); + for (y = 0; y < y1; ++y) { + i_glinf(src, 0, x1, y, pv); + i_plinf(im, 0, x1, y, pv); + } + myfree(pv); } - tty++; } -} + else { + i_palidx *vals; -/* -=item i_copy(im, src) + vals = mymalloc(sizeof(i_palidx) * x1); + for (y = 0; y < y1; ++y) { + i_gpal(src, 0, x1, y, vals); + i_ppal(im, 0, x1, y, vals); + } + myfree(vals); + } -Copies the contents of the image I over the image I. + return im; +} -=cut -*/ - -void -i_copy(i_img *im, i_img *src) { - i_color *pv; - int x,y,y1,x1; - - mm_log((1,"i_copy(im* %p,src %p)\n", im, src)); - - x1 = src->xsize; - y1 = src->ysize; - i_img_empty_ch(im, x1, y1, src->channels); - pv = mymalloc(sizeof(i_color) * x1); - - for (y = 0; y < y1; ++y) { - i_glin(src, 0, x1, y, pv); - i_plin(im, 0, x1, y, pv); - } -} - - -/* -=item i_rubthru(im, src, tx, ty) - -Takes the image I and applies it at an original (I,I) in I. - -The alpha channel of each pixel in I is used to control how much -the existing colour in I is replaced, if it is 255 then the colour -is completely replaced, if it is 0 then the original colour is left -unmodified. - -=cut -*/ - -void -i_rubthru(i_img *im,i_img *src,int tx,int ty) { - i_color pv, orig, dest; - int x, y, ttx, tty; - - mm_log((1,"i_rubthru(im %p, src %p, tx %d, ty %d)\n", im, src, tx, ty)); - - if (im->channels != 3) { fprintf(stderr,"Destination is not in rgb mode.\n"); exit(3); } - if (src->channels != 4) { fprintf(stderr,"Source is not in rgba mode.\n"); exit(3); } - - ttx = tx; - for(x=0; xxsize; x++) { - tty=ty; - for(y=0;yysize;y++) { - /* fprintf(stderr,"reading (%d,%d) writing (%d,%d).\n",x,y,ttx,tty); */ - i_gpix(src, x, y, &pv); - i_gpix(im, ttx, tty, &orig); - dest.rgb.r = (pv.rgba.a*pv.rgba.r+(255-pv.rgba.a)*orig.rgb.r)/255; - dest.rgb.g = (pv.rgba.a*pv.rgba.g+(255-pv.rgba.a)*orig.rgb.g)/255; - dest.rgb.b = (pv.rgba.a*pv.rgba.b+(255-pv.rgba.a)*orig.rgb.b)/255; - i_ppix(im, ttx, tty, &dest); - tty++; - } - ttx++; - } -} - - -/* -=item i_flipxy(im, axis) - -Flips the image inplace around the axis specified. -Returns 0 if parameters are invalid. - - im - Image pointer - axis - 0 = x, 1 = y, 2 = both - -=cut -*/ - -undef_int -i_flipxy(i_img *im, int direction) { - int x, x2, y, y2, xm, ym; - int xs = im->xsize; - int ys = im->ysize; - - mm_log((1, "i_flipxy(im %p, direction %d)\n", im, direction )); - - if (!im) return 0; - - switch (direction) { - case XAXIS: /* Horizontal flip */ - xm = xs/2; - ym = ys; - for(y=0; yxsize * Value); + hsize = (int)(0.5 + im->xsize * Value); + if (hsize < 1) { + hsize = 1; + Value = 1.0 / im->xsize; + } vsize = im->ysize; jEnd = hsize; iEnd = vsize; - - TempJump1 = (hsize - 1) * 3; - TempJump2 = hsize * (vsize - 1) * 3 + TempJump1; } else { hsize = im->xsize; - vsize = (int) ((float) im->ysize * Value); - + vsize = (int)(0.5 + im->ysize * Value); + + if (vsize < 1) { + vsize = 1; + Value = 1.0 / im->ysize; + } + jEnd = vsize; iEnd = hsize; - - TempJump1 = 0; - TempJump2 = 0; } - new_img=i_img_empty_ch(NULL,hsize,vsize,im->channels); - - if (Value >=1) LanczosWidthFactor = 1; - else LanczosWidthFactor = (int) (1.0/Value); + new_img = i_img_empty_ch(NULL, hsize, vsize, im->channels); + if (!new_img) { + i_push_error(0, "cannot create output image"); + return NULL; + } + /* 1.4 is a magic number, setting it to 2 will cause rather blurred images */ + LanczosWidthFactor = (Value >= 1) ? 1 : (int) (1.4/Value); lMax = LanczosWidthFactor << 1; - l0 = (float *) mymalloc(lMax * sizeof(float)); - l1 = (float *) mymalloc(lMax * sizeof(float)); + l0 = mymalloc(lMax * sizeof(float)); + l1 = mymalloc(lMax * sizeof(float)); for (j=0; jchannels; k++) PictureValue[k] = 0.0; - for (l=0; l < lMax; l++) { - i_gpix(im,T+l+1, i, &val1); - i_gpix(im,T-lMax+l+1, i, &val2); - for (k=0; kchannels; k++) { - PictureValue[k] += l1[l] * val1.channel[k]; - PictureValue[k] += l0[lMax-l-1] * val2.channel[k]; + for (l=0; l= im->xsize) ? im->xsize-1 : Mx; + + i_gpix(im, Mx, i, &val1); + i_gpix(im, mx, i, &val2); + + if (has_alpha) { + i_sample_t alpha1 = val1.channel[color_chans]; + i_sample_t alpha2 = val2.channel[color_chans]; + for (k=0; k < color_chans; k++) { + PictureValue[k] += l1[l] * val1.channel[k] * alpha1 / 255; + PictureValue[k] += l0[lMax-l-1] * val2.channel[k] * alpha2 / 255; + } + PictureValue[color_chans] += l1[l] * val1.channel[color_chans]; + PictureValue[color_chans] += l0[lMax-l-1] * val2.channel[color_chans]; + } + else { + for (k=0; kchannels; k++) { + PictureValue[k] += l1[l] * val1.channel[k]; + PictureValue[k] += l0[lMax-l-1] * val2.channel[k]; + } + } + } + + if (has_alpha) { + float fa = PictureValue[color_chans] / LanczosWidthFactor; + int alpha = minmax(0, 255, fa+0.5); + if (alpha) { + for (k = 0; k < color_chans; ++k) { + psave = (short)(0.5+(PictureValue[k] / LanczosWidthFactor * 255 / fa)); + val.channel[k]=minmax(0,255,psave); + } + val.channel[color_chans] = alpha; + } + else { + /* zero alpha, so the pixel has no color */ + for (k = 0; k < im->channels; ++k) + val.channel[k] = 0; } } - for(k=0;kchannels;k++) { - psave = (short)( PictureValue[k] / LanczosWidthFactor); - val.channel[k]=minmax(0,255,psave); + else { + for(k=0;kchannels;k++) { + psave = (short)(0.5+(PictureValue[k] / LanczosWidthFactor)); + val.channel[k]=minmax(0,255,psave); + } } - i_ppix(new_img,j,i,&val); + i_ppix(new_img, j, i, &val); } } else { @@ -923,18 +873,52 @@ i_scaleaxis(i_img *im, float Value, int Axis) { for (i=0; ichannels; k++) PictureValue[k] = 0.0; for (l=0; l < lMax; l++) { - i_gpix(im,i, T+l+1, &val1); - i_gpix(im,i, T-lMax+l+1, &val2); - for (k=0; kchannels; k++) { - PictureValue[k] += l1[l] * val1.channel[k]; - PictureValue[k] += l0[lMax-l-1] * val2.channel[k]; + int mx = T-lMax+l+1; + int Mx = T+l+1; + mx = (mx < 0) ? 0 : mx; + Mx = (Mx >= im->ysize) ? im->ysize-1 : Mx; + + i_gpix(im, i, Mx, &val1); + i_gpix(im, i, mx, &val2); + if (has_alpha) { + i_sample_t alpha1 = val1.channel[color_chans]; + i_sample_t alpha2 = val2.channel[color_chans]; + for (k=0; k < color_chans; k++) { + PictureValue[k] += l1[l] * val1.channel[k] * alpha1 / 255; + PictureValue[k] += l0[lMax-l-1] * val2.channel[k] * alpha2 / 255; + } + PictureValue[color_chans] += l1[l] * val1.channel[color_chans]; + PictureValue[color_chans] += l0[lMax-l-1] * val2.channel[color_chans]; + } + else { + for (k=0; kchannels; k++) { + PictureValue[k] += l1[l] * val1.channel[k]; + PictureValue[k] += l0[lMax-l-1] * val2.channel[k]; + } } } - for (k=0; kchannels; k++) { - psave = (short)( PictureValue[k] / LanczosWidthFactor); - val.channel[k]=minmax(0,255,psave); + if (has_alpha) { + float fa = PictureValue[color_chans] / LanczosWidthFactor; + int alpha = minmax(0, 255, fa+0.5); + if (alpha) { + for (k = 0; k < color_chans; ++k) { + psave = (short)(0.5+(PictureValue[k] / LanczosWidthFactor * 255 / fa)); + val.channel[k]=minmax(0,255,psave); + } + val.channel[color_chans] = alpha; + } + else { + for (k = 0; k < im->channels; ++k) + val.channel[k] = 0; + } + } + else { + for(k=0;kchannels;k++) { + psave = (short)(0.5+(PictureValue[k] / LanczosWidthFactor)); + val.channel[k]=minmax(0,255,psave); + } } - i_ppix(new_img,i,j,&val); + i_ppix(new_img, i, j, &val); } } @@ -942,7 +926,7 @@ i_scaleaxis(i_img *im, float Value, int Axis) { myfree(l0); myfree(l1); - mm_log((1,"(0x%x) <- i_scaleaxis\n",new_img)); + mm_log((1,"(%p) <- i_scaleaxis\n", new_img)); return new_img; } @@ -969,7 +953,16 @@ i_scale_nn(i_img *im, float scx, float scy) { mm_log((1,"i_scale_nn(im 0x%x,scx %.2f,scy %.2f)\n",im,scx,scy)); nxsize = (int) ((float) im->xsize * scx); + if (nxsize < 1) { + nxsize = 1; + scx = 1.0 / im->xsize; + } nysize = (int) ((float) im->ysize * scy); + if (nysize < 1) { + nysize = 1; + scy = 1.0 / im->ysize; + } + im_assert(scx != 0 && scy != 0); new_img=i_img_empty_ch(NULL,nxsize,nysize,im->channels); @@ -983,6 +976,77 @@ i_scale_nn(i_img *im, float scx, float scy) { return new_img; } +/* +=item i_sametype(C, C, C) + +=category Image creation/destruction +=synopsis i_img *img = i_sametype(src, width, height); + +Returns an image of the same type (sample size, channels, paletted/direct). + +For paletted images the palette is copied from the source. + +=cut +*/ + +i_img *i_sametype(i_img *src, int xsize, int ysize) { + if (src->type == i_direct_type) { + if (src->bits == 8) { + return i_img_empty_ch(NULL, xsize, ysize, src->channels); + } + else if (src->bits == i_16_bits) { + return i_img_16_new(xsize, ysize, src->channels); + } + else if (src->bits == i_double_bits) { + return i_img_double_new(xsize, ysize, src->channels); + } + else { + i_push_error(0, "Unknown image bits"); + return NULL; + } + } + else { + i_color col; + int i; + + i_img *targ = i_img_pal_new(xsize, ysize, src->channels, i_maxcolors(src)); + for (i = 0; i < i_colorcount(src); ++i) { + i_getcolors(src, i, &col, 1); + i_addcolors(targ, &col, 1); + } + + return targ; + } +} + +/* +=item i_sametype_chans(C, C, C, C) + +=category Image creation/destruction +=synopsis i_img *img = i_sametype_chans(src, width, height, channels); + +Returns an image of the same type (sample size). + +For paletted images the equivalent direct type is returned. + +=cut +*/ + +i_img *i_sametype_chans(i_img *src, int xsize, int ysize, int channels) { + if (src->bits == 8) { + return i_img_empty_ch(NULL, xsize, ysize, channels); + } + else if (src->bits == i_16_bits) { + return i_img_16_new(xsize, ysize, channels); + } + else if (src->bits == i_double_bits) { + return i_img_double_new(xsize, ysize, channels); + } + else { + i_push_error(0, "Unknown image bits"); + return NULL; + } +} /* =item i_transform(im, opx, opxl, opy, opyl, parm, parmlen) @@ -1025,8 +1089,8 @@ i_transform(i_img *im, int *opx,int opxl,int *opy,int opyl,double parm[],int par parm[1]=(double)ny; /* fprintf(stderr,"(%d,%d) ->",nx,ny); */ - rx=op_run(opx,opxl,parm,parmlen); - ry=op_run(opy,opyl,parm,parmlen); + rx=i_op_run(opx,opxl,parm,parmlen); + ry=i_op_run(opy,opyl,parm,parmlen); /* fprintf(stderr,"(%f,%f)\n",rx,ry); */ i_gpix(im,rx,ry,&val); i_ppix(new_img,nx,ny,&val); @@ -1048,6 +1112,7 @@ can return zero. =cut */ + float i_img_diff(i_img *im1,i_img *im2) { int x,y,ch,xb,yb,chb; @@ -1073,6 +1138,50 @@ i_img_diff(i_img *im1,i_img *im2) { return tdiff; } +/* +=item i_img_diffd(im1, im2) + +Calculates the sum of the squares of the differences between +correspoding channels in two images. + +If the images are not the same size then only the common area is +compared, hence even if images are different sizes this function +can return zero. + +This is like i_img_diff() but looks at floating point samples instead. + +=cut +*/ + +double +i_img_diffd(i_img *im1,i_img *im2) { + int x,y,ch,xb,yb,chb; + double tdiff; + i_fcolor val1,val2; + + mm_log((1,"i_img_diffd(im1 0x%x,im2 0x%x)\n",im1,im2)); + + xb=(im1->xsizexsize)?im1->xsize:im2->xsize; + yb=(im1->ysizeysize)?im1->ysize:im2->ysize; + chb=(im1->channelschannels)?im1->channels:im2->channels; + + mm_log((1,"i_img_diff: xb=%d xy=%d chb=%d\n",xb,yb,chb)); + + tdiff=0; + for(y=0;yxsize; + int ysize = im->ysize; + int samp_cnt = 3 * xsize; + + if (im->channels >= 3) { + samp_chans = NULL; + } + else { + channels[0] = channels[1] = channels[2] = 0; + samp_chans = channels; + } + + ct = octt_new(); - mm_log((1,"i_count_colors(im 0x%08X,maxc %d)\n")); + samp = (i_sample_t *) mymalloc( xsize * 3 * sizeof(i_sample_t)); - xsize=im->xsize; - ysize=im->ysize; - ct=octt_new(); - - colorcnt=0; - for(y=0;y maxc) { octt_delete(ct); return -1; } + colorcnt = 0; + for(y = 0; y < ysize; ) { + i_gsamp(im, 0, xsize, y++, samp, samp_chans, 3); + for(x = 0; x < samp_cnt; ) { + colorcnt += octt_add(ct, samp[x], samp[x+1], samp[x+2]); + x += 3; + if (colorcnt > maxc) { + octt_delete(ct); + return -1; + } + } } + myfree(samp); octt_delete(ct); return colorcnt; } +/* sorts the array ra[0..n-1] into increasing order using heapsort algorithm + * (adapted from the Numerical Recipes) + */ +/* Needed by get_anonymous_color_histo */ +static void +hpsort(unsigned int n, unsigned *ra) { + unsigned int i, + ir, + j, + l, + rra; + + if (n < 2) return; + l = n >> 1; + ir = n - 1; + for(;;) { + if (l > 0) { + rra = ra[--l]; + } + else { + rra = ra[ir]; + ra[ir] = ra[0]; + if (--ir == 0) { + ra[0] = rra; + break; + } + } + i = l; + j = 2 * l + 1; + while (j <= ir) { + if (j < ir && ra[j] < ra[j+1]) j++; + if (rra < ra[j]) { + ra[i] = ra[j]; + i = j; + j++; j <<= 1; j--; + } + else break; + } + ra[i] = rra; + } +} -symbol_table_t symbol_table={i_has_format,ICL_set_internal,ICL_info, - i_img_new,i_img_empty,i_img_empty_ch,i_img_exorcise, - i_img_info,i_img_setmask,i_img_getmask,i_ppix,i_gpix, - i_box,i_draw,i_arc,i_copyto,i_copyto_trans,i_rubthru}; - - -/* -=item i_gen_reader(i_gen_read_data *info, char *buf, int length) - -Performs general read buffering for file readers that permit reading -to be done through a callback. - -The final callback gets two parameters, a I value, and a I -value, where I is the amount of data that the file library needs -to read, and I is the amount of space available in the buffer -maintained by these functions. - -This means if you need to read from a stream that you don't know the -length of, you can return I bytes, taking the performance hit of -possibly expensive callbacks (eg. back to perl code), or if you are -reading from a stream where it doesn't matter if some data is lost, or -if the total length of the stream is known, you can return I -bytes. - -=cut -*/ - +/* This function constructs an ordered list which represents how much the + * different colors are used. So for instance (100, 100, 500) means that one + * color is used for 500 pixels, another for 100 pixels and another for 100 + * pixels. It's tuned for performance. You might not like the way I've hardcoded + * the maxc ;-) and you might want to change the name... */ +/* Uses octt_histo */ int -i_gen_reader(i_gen_read_data *gci, char *buf, int length) { - int total; - - if (length < gci->length - gci->cpos) { - /* simplest case */ - memcpy(buf, gci->buffer+gci->cpos, length); - gci->cpos += length; - return length; - } +i_get_anonymous_color_histo(i_img *im, unsigned int **col_usage, int maxc) { + struct octt *ct; + int x,y; + int colorcnt; + unsigned int *col_usage_it; + i_sample_t * samp; + int channels[3]; + int *samp_chans; - total = 0; - memcpy(buf, gci->buffer+gci->cpos, gci->length-gci->cpos); - total += gci->length - gci->cpos; - length -= gci->length - gci->cpos; - buf += gci->length - gci->cpos; - if (length < (int)sizeof(gci->buffer)) { - int did_read; - int copy_size; - while (length - && (did_read = (gci->cb)(gci->userdata, gci->buffer, length, - sizeof(gci->buffer))) > 0) { - gci->cpos = 0; - gci->length = did_read; - - copy_size = min(length, gci->length); - memcpy(buf, gci->buffer, copy_size); - gci->cpos += copy_size; - buf += copy_size; - total += copy_size; - length -= copy_size; - } + int xsize = im->xsize; + int ysize = im->ysize; + int samp_cnt = 3 * xsize; + ct = octt_new(); + + samp = (i_sample_t *) mymalloc( xsize * 3 * sizeof(i_sample_t)); + + if (im->channels >= 3) { + samp_chans = NULL; } else { - /* just read the rest - too big for our buffer*/ - int did_read; - while ((did_read = (gci->cb)(gci->userdata, buf, length, length)) > 0) { - length -= did_read; - total += did_read; - buf += did_read; + channels[0] = channels[1] = channels[2] = 0; + samp_chans = channels; + } + + colorcnt = 0; + for(y = 0; y < ysize; ) { + i_gsamp(im, 0, xsize, y++, samp, samp_chans, 3); + for(x = 0; x < samp_cnt; ) { + colorcnt += octt_add(ct, samp[x], samp[x+1], samp[x+2]); + x += 3; + if (colorcnt > maxc) { + octt_delete(ct); + return -1; + } } } - return total; + myfree(samp); + /* Now that we know the number of colours... */ + col_usage_it = *col_usage = (unsigned int *) mymalloc(colorcnt * sizeof(unsigned int)); + octt_histo(ct, &col_usage_it); + hpsort(colorcnt, *col_usage); + octt_delete(ct); + return colorcnt; } /* -=item i_gen_read_data_new(i_read_callback_t cb, char *userdata) +=back -For use by callback file readers to initialize the reader buffer. +=head2 8-bit per sample image internal functions -Allocates, initializes and returns the reader buffer. +These are the functions installed in an 8-bit per sample image. -See also L and L. +=over -=cut -*/ -i_gen_read_data * -i_gen_read_data_new(i_read_callback_t cb, char *userdata) { - i_gen_read_data *self = mymalloc(sizeof(i_gen_read_data)); - self->cb = cb; - self->userdata = userdata; - self->length = 0; - self->cpos = 0; +=item i_ppix_d(im, x, y, col) - return self; -} +Internal function. -/* -=item free_gen_read_data(i_gen_read_data *) +This is the function kept in the i_f_ppix member of an i_img object. +It does a normal store of a pixel into the image with range checking. -Cleans up. +Returns 0 if the pixel could be set, -1 otherwise. =cut */ -void free_gen_read_data(i_gen_read_data *self) { - myfree(self); +static +int +i_ppix_d(i_img *im, int x, int y, const i_color *val) { + int ch; + + if ( x>-1 && xxsize && y>-1 && yysize ) { + for(ch=0;chchannels;ch++) + if (im->ch_mask&(1<idata[(x+y*im->xsize)*im->channels+ch]=val->channel[ch]; + return 0; + } + return -1; /* error was clipped */ } /* -=item i_gen_writer(i_gen_write_data *info, char const *data, int size) +=item i_gpix_d(im, x, y, &col) -Performs write buffering for a callback based file writer. +Internal function. -Failures are considered fatal, if a write fails then data will be -dropped. +This is the function kept in the i_f_gpix member of an i_img object. +It does normal retrieval of a pixel from the image with range checking. + +Returns 0 if the pixel could be set, -1 otherwise. =cut */ +static int -i_gen_writer( -i_gen_write_data *self, -char const *data, +i_gpix_d(i_img *im, int x, int y, i_color *val) { + int ch; + if (x>-1 && xxsize && y>-1 && yysize) { + for(ch=0;chchannels;ch++) + val->channel[ch]=im->idata[(x+y*im->xsize)*im->channels+ch]; + return 0; + } + for(ch=0;chchannels;ch++) val->channel[ch] = 0; + return -1; /* error was cliped */ +} + +/* +=item i_glin_d(im, l, r, y, vals) + +Reads a line of data from the image, storing the pixels at vals. + +The line runs from (l,y) inclusive to (r,y) non-inclusive + +vals should point at space for (r-l) pixels. + +l should never be less than zero (to avoid confusion about where to +put the pixels in vals). + +Returns the number of pixels copied (eg. if r, l or y is out of range) + +=cut +*/ +static +int +i_glin_d(i_img *im, int l, int r, int y, i_color *vals) { + int ch, count, i; + unsigned char *data; + if (y >=0 && y < im->ysize && l < im->xsize && l >= 0) { + if (r > im->xsize) + r = im->xsize; + data = im->idata + (l+y*im->xsize) * im->channels; + count = r - l; + for (i = 0; i < count; ++i) { + for (ch = 0; ch < im->channels; ++ch) + vals[i].channel[ch] = *data++; + } + return count; + } + else { + return 0; + } +} + +/* +=item i_plin_d(im, l, r, y, vals) + +Writes a line of data into the image, using the pixels at vals. + +The line runs from (l,y) inclusive to (r,y) non-inclusive + +vals should point at (r-l) pixels. + +l should never be less than zero (to avoid confusion about where to +get the pixels in vals). + +Returns the number of pixels copied (eg. if r, l or y is out of range) + +=cut +*/ +static +int +i_plin_d(i_img *im, int l, int r, int y, const i_color *vals) { + int ch, count, i; + unsigned char *data; + if (y >=0 && y < im->ysize && l < im->xsize && l >= 0) { + if (r > im->xsize) + r = im->xsize; + data = im->idata + (l+y*im->xsize) * im->channels; + count = r - l; + for (i = 0; i < count; ++i) { + for (ch = 0; ch < im->channels; ++ch) { + if (im->ch_mask & (1 << ch)) + *data = vals[i].channel[ch]; + ++data; + } + } + return count; + } + else { + return 0; + } +} + +/* +=item i_ppixf_d(im, x, y, val) + +=cut +*/ +static +int +i_ppixf_d(i_img *im, int x, int y, const i_fcolor *val) { + int ch; + + if ( x>-1 && xxsize && y>-1 && yysize ) { + for(ch=0;chchannels;ch++) + if (im->ch_mask&(1<idata[(x+y*im->xsize)*im->channels+ch] = + SampleFTo8(val->channel[ch]); + } + return 0; + } + return -1; /* error was clipped */ +} + +/* +=item i_gpixf_d(im, x, y, val) + +=cut +*/ +static +int +i_gpixf_d(i_img *im, int x, int y, i_fcolor *val) { + int ch; + if (x>-1 && xxsize && y>-1 && yysize) { + for(ch=0;chchannels;ch++) { + val->channel[ch] = + Sample8ToF(im->idata[(x+y*im->xsize)*im->channels+ch]); + } + return 0; + } + return -1; /* error was cliped */ +} + +/* +=item i_glinf_d(im, l, r, y, vals) + +Reads a line of data from the image, storing the pixels at vals. + +The line runs from (l,y) inclusive to (r,y) non-inclusive + +vals should point at space for (r-l) pixels. + +l should never be less than zero (to avoid confusion about where to +put the pixels in vals). + +Returns the number of pixels copied (eg. if r, l or y is out of range) + +=cut +*/ +static +int +i_glinf_d(i_img *im, int l, int r, int y, i_fcolor *vals) { + int ch, count, i; + unsigned char *data; + if (y >=0 && y < im->ysize && l < im->xsize && l >= 0) { + if (r > im->xsize) + r = im->xsize; + data = im->idata + (l+y*im->xsize) * im->channels; + count = r - l; + for (i = 0; i < count; ++i) { + for (ch = 0; ch < im->channels; ++ch) + vals[i].channel[ch] = Sample8ToF(*data++); + } + return count; + } + else { + return 0; + } +} + +/* +=item i_plinf_d(im, l, r, y, vals) + +Writes a line of data into the image, using the pixels at vals. + +The line runs from (l,y) inclusive to (r,y) non-inclusive + +vals should point at (r-l) pixels. + +l should never be less than zero (to avoid confusion about where to +get the pixels in vals). + +Returns the number of pixels copied (eg. if r, l or y is out of range) + +=cut +*/ +static +int +i_plinf_d(i_img *im, int l, int r, int y, const i_fcolor *vals) { + int ch, count, i; + unsigned char *data; + if (y >=0 && y < im->ysize && l < im->xsize && l >= 0) { + if (r > im->xsize) + r = im->xsize; + data = im->idata + (l+y*im->xsize) * im->channels; + count = r - l; + for (i = 0; i < count; ++i) { + for (ch = 0; ch < im->channels; ++ch) { + if (im->ch_mask & (1 << ch)) + *data = SampleFTo8(vals[i].channel[ch]); + ++data; + } + } + return count; + } + else { + return 0; + } +} + +/* +=item i_gsamp_d(i_img *im, int l, int r, int y, i_sample_t *samps, int *chans, int chan_count) + +Reads sample values from im for the horizontal line (l, y) to (r-1,y) +for the channels specified by chans, an array of int with chan_count +elements. + +Returns the number of samples read (which should be (r-l) * bits_set(chan_mask) + +=cut +*/ +static +int +i_gsamp_d(i_img *im, int l, int r, int y, i_sample_t *samps, + const int *chans, int chan_count) { + int ch, count, i, w; + unsigned char *data; + + if (y >=0 && y < im->ysize && l < im->xsize && l >= 0) { + if (r > im->xsize) + r = im->xsize; + data = im->idata + (l+y*im->xsize) * im->channels; + w = r - l; + count = 0; + + if (chans) { + /* make sure we have good channel numbers */ + for (ch = 0; ch < chan_count; ++ch) { + if (chans[ch] < 0 || chans[ch] >= im->channels) { + i_push_errorf(0, "No channel %d in this image", chans[ch]); + return 0; + } + } + for (i = 0; i < w; ++i) { + for (ch = 0; ch < chan_count; ++ch) { + *samps++ = data[chans[ch]]; + ++count; + } + data += im->channels; + } + } + else { + if (chan_count <= 0 || chan_count > im->channels) { + i_push_errorf(0, "chan_count %d out of range, must be >0, <= channels", + chan_count); + return 0; + } + for (i = 0; i < w; ++i) { + for (ch = 0; ch < chan_count; ++ch) { + *samps++ = data[ch]; + ++count; + } + data += im->channels; + } + } + + return count; + } + else { + return 0; + } +} + +/* +=item i_gsampf_d(i_img *im, int l, int r, int y, i_fsample_t *samps, int *chans, int chan_count) + +Reads sample values from im for the horizontal line (l, y) to (r-1,y) +for the channels specified by chan_mask, where bit 0 is the first +channel. + +Returns the number of samples read (which should be (r-l) * bits_set(chan_mask) + +=cut +*/ +static +int +i_gsampf_d(i_img *im, int l, int r, int y, i_fsample_t *samps, + const int *chans, int chan_count) { + int ch, count, i, w; + unsigned char *data; + for (ch = 0; ch < chan_count; ++ch) { + if (chans[ch] < 0 || chans[ch] >= im->channels) { + i_push_errorf(0, "No channel %d in this image", chans[ch]); + } + } + if (y >=0 && y < im->ysize && l < im->xsize && l >= 0) { + if (r > im->xsize) + r = im->xsize; + data = im->idata + (l+y*im->xsize) * im->channels; + w = r - l; + count = 0; + + if (chans) { + /* make sure we have good channel numbers */ + for (ch = 0; ch < chan_count; ++ch) { + if (chans[ch] < 0 || chans[ch] >= im->channels) { + i_push_errorf(0, "No channel %d in this image", chans[ch]); + return 0; + } + } + for (i = 0; i < w; ++i) { + for (ch = 0; ch < chan_count; ++ch) { + *samps++ = Sample8ToF(data[chans[ch]]); + ++count; + } + data += im->channels; + } + } + else { + if (chan_count <= 0 || chan_count > im->channels) { + i_push_errorf(0, "chan_count %d out of range, must be >0, <= channels", + chan_count); + return 0; + } + for (i = 0; i < w; ++i) { + for (ch = 0; ch < chan_count; ++ch) { + *samps++ = Sample8ToF(data[ch]); + ++count; + } + data += im->channels; + } + } + return count; + } + else { + return 0; + } +} + +/* +=back + +=head2 Image method wrappers + +These functions provide i_fsample_t functions in terms of their +i_sample_t versions. + +=over + +=item i_ppixf_fp(i_img *im, int x, int y, i_fcolor *pix) + +=cut +*/ + +int i_ppixf_fp(i_img *im, int x, int y, const i_fcolor *pix) { + i_color temp; + int ch; + + for (ch = 0; ch < im->channels; ++ch) + temp.channel[ch] = SampleFTo8(pix->channel[ch]); + + return i_ppix(im, x, y, &temp); +} + +/* +=item i_gpixf_fp(i_img *im, int x, int y, i_fcolor *pix) + +=cut +*/ +int i_gpixf_fp(i_img *im, int x, int y, i_fcolor *pix) { + i_color temp; + int ch; + + if (i_gpix(im, x, y, &temp)) { + for (ch = 0; ch < im->channels; ++ch) + pix->channel[ch] = Sample8ToF(temp.channel[ch]); + return 0; + } + else + return -1; +} + +/* +=item i_plinf_fp(i_img *im, int l, int r, int y, i_fcolor *pix) + +=cut +*/ +int i_plinf_fp(i_img *im, int l, int r, int y, const i_fcolor *pix) { + i_color *work; + + if (y >= 0 && y < im->ysize && l < im->xsize && l >= 0) { + if (r > im->xsize) + r = im->xsize; + if (r > l) { + int ret; + int i, ch; + work = mymalloc(sizeof(i_color) * (r-l)); + for (i = 0; i < r-l; ++i) { + for (ch = 0; ch < im->channels; ++ch) + work[i].channel[ch] = SampleFTo8(pix[i].channel[ch]); + } + ret = i_plin(im, l, r, y, work); + myfree(work); + + return ret; + } + else { + return 0; + } + } + else { + return 0; + } +} + +/* +=item i_glinf_fp(i_img *im, int l, int r, int y, i_fcolor *pix) + +=cut +*/ +int i_glinf_fp(i_img *im, int l, int r, int y, i_fcolor *pix) { + i_color *work; + + if (y >= 0 && y < im->ysize && l < im->xsize && l >= 0) { + if (r > im->xsize) + r = im->xsize; + if (r > l) { + int ret; + int i, ch; + work = mymalloc(sizeof(i_color) * (r-l)); + ret = i_plin(im, l, r, y, work); + for (i = 0; i < r-l; ++i) { + for (ch = 0; ch < im->channels; ++ch) + pix[i].channel[ch] = Sample8ToF(work[i].channel[ch]); + } + myfree(work); + + return ret; + } + else { + return 0; + } + } + else { + return 0; + } +} + +/* +=item i_gsampf_fp(i_img *im, int l, int r, int y, i_fsample_t *samp, int *chans, int chan_count) + +=cut +*/ +int i_gsampf_fp(i_img *im, int l, int r, int y, i_fsample_t *samp, + int const *chans, int chan_count) { + i_sample_t *work; + + if (y >= 0 && y < im->ysize && l < im->xsize && l >= 0) { + if (r > im->xsize) + r = im->xsize; + if (r > l) { + int ret; + int i; + work = mymalloc(sizeof(i_sample_t) * (r-l)); + ret = i_gsamp(im, l, r, y, work, chans, chan_count); + for (i = 0; i < ret; ++i) { + samp[i] = Sample8ToF(work[i]); + } + myfree(work); + + return ret; + } + else { + return 0; + } + } + else { + return 0; + } +} + +/* +=back + +=head2 Palette wrapper functions + +Used for virtual images, these forward palette calls to a wrapped image, +assuming the wrapped image is the first pointer in the structure that +im->ext_data points at. + +=over + +=item i_addcolors_forward(i_img *im, const i_color *colors, int count) + +=cut +*/ +int i_addcolors_forward(i_img *im, const i_color *colors, int count) { + return i_addcolors(*(i_img **)im->ext_data, colors, count); +} + +/* +=item i_getcolors_forward(i_img *im, int i, i_color *color, int count) + +=cut +*/ +int i_getcolors_forward(i_img *im, int i, i_color *color, int count) { + return i_getcolors(*(i_img **)im->ext_data, i, color, count); +} + +/* +=item i_setcolors_forward(i_img *im, int i, const i_color *color, int count) + +=cut +*/ +int i_setcolors_forward(i_img *im, int i, const i_color *color, int count) { + return i_setcolors(*(i_img **)im->ext_data, i, color, count); +} + +/* +=item i_colorcount_forward(i_img *im) + +=cut +*/ +int i_colorcount_forward(i_img *im) { + return i_colorcount(*(i_img **)im->ext_data); +} + +/* +=item i_maxcolors_forward(i_img *im) + +=cut +*/ +int i_maxcolors_forward(i_img *im) { + return i_maxcolors(*(i_img **)im->ext_data); +} + +/* +=item i_findcolor_forward(i_img *im, const i_color *color, i_palidx *entry) + +=cut +*/ +int i_findcolor_forward(i_img *im, const i_color *color, i_palidx *entry) { + return i_findcolor(*(i_img **)im->ext_data, color, entry); +} + +/* +=back + +=head2 Fallback handler + +=over + +=item i_gsamp_bits_fb + +=cut +*/ + +int +i_gsamp_bits_fb(i_img *im, int l, int r, int y, unsigned *samps, + const int *chans, int chan_count, int bits) { + if (bits < 1 || bits > 32) { + i_push_error(0, "Invalid bits, must be 1..32"); + return -1; + } + + if (y >=0 && y < im->ysize && l < im->xsize && l >= 0) { + double scale; + int ch, count, i, w; + + if (bits == 32) + scale = 4294967295.0; + else + scale = (double)(1 << bits) - 1; + + if (r > im->xsize) + r = im->xsize; + w = r - l; + count = 0; + + if (chans) { + /* make sure we have good channel numbers */ + for (ch = 0; ch < chan_count; ++ch) { + if (chans[ch] < 0 || chans[ch] >= im->channels) { + i_push_errorf(0, "No channel %d in this image", chans[ch]); + return -1; + } + } + for (i = 0; i < w; ++i) { + i_fcolor c; + i_gpixf(im, l+i, y, &c); + for (ch = 0; ch < chan_count; ++ch) { + *samps++ = (unsigned)(c.channel[ch] * scale + 0.5); + ++count; + } + } + } + else { + if (chan_count <= 0 || chan_count > im->channels) { + i_push_error(0, "Invalid channel count"); + return -1; + } + for (i = 0; i < w; ++i) { + i_fcolor c; + i_gpixf(im, l+i, y, &c); + for (ch = 0; ch < chan_count; ++ch) { + *samps++ = (unsigned)(c.channel[ch] * scale + 0.5); + ++count; + } + } + } + + return count; + } + else { + i_push_error(0, "Image position outside of image"); + return -1; + } +} + +/* +=back + +=head2 Stream reading and writing wrapper functions + +=over + +=item i_gen_reader(i_gen_read_data *info, char *buf, int length) + +Performs general read buffering for file readers that permit reading +to be done through a callback. + +The final callback gets two parameters, a I value, and a I +value, where I is the amount of data that the file library needs +to read, and I is the amount of space available in the buffer +maintained by these functions. + +This means if you need to read from a stream that you don't know the +length of, you can return I bytes, taking the performance hit of +possibly expensive callbacks (eg. back to perl code), or if you are +reading from a stream where it doesn't matter if some data is lost, or +if the total length of the stream is known, you can return I +bytes. + +=cut +*/ + +int +i_gen_reader(i_gen_read_data *gci, char *buf, int length) { + int total; + + if (length < gci->length - gci->cpos) { + /* simplest case */ + memcpy(buf, gci->buffer+gci->cpos, length); + gci->cpos += length; + return length; + } + + total = 0; + memcpy(buf, gci->buffer+gci->cpos, gci->length-gci->cpos); + total += gci->length - gci->cpos; + length -= gci->length - gci->cpos; + buf += gci->length - gci->cpos; + if (length < (int)sizeof(gci->buffer)) { + int did_read; + int copy_size; + while (length + && (did_read = (gci->cb)(gci->userdata, gci->buffer, length, + sizeof(gci->buffer))) > 0) { + gci->cpos = 0; + gci->length = did_read; + + copy_size = i_min(length, gci->length); + memcpy(buf, gci->buffer, copy_size); + gci->cpos += copy_size; + buf += copy_size; + total += copy_size; + length -= copy_size; + } + } + else { + /* just read the rest - too big for our buffer*/ + int did_read; + while ((did_read = (gci->cb)(gci->userdata, buf, length, length)) > 0) { + length -= did_read; + total += did_read; + buf += did_read; + } + } + return total; +} + +/* +=item i_gen_read_data_new(i_read_callback_t cb, char *userdata) + +For use by callback file readers to initialize the reader buffer. + +Allocates, initializes and returns the reader buffer. + +See also L and L. + +=cut +*/ +i_gen_read_data * +i_gen_read_data_new(i_read_callback_t cb, char *userdata) { + i_gen_read_data *self = mymalloc(sizeof(i_gen_read_data)); + self->cb = cb; + self->userdata = userdata; + self->length = 0; + self->cpos = 0; + + return self; +} + +/* +=item i_free_gen_read_data(i_gen_read_data *) + +Cleans up. + +=cut +*/ +void i_free_gen_read_data(i_gen_read_data *self) { + myfree(self); +} + +/* +=item i_gen_writer(i_gen_write_data *info, char const *data, int size) + +Performs write buffering for a callback based file writer. + +Failures are considered fatal, if a write fails then data will be +dropped. + +=cut +*/ +int +i_gen_writer( +i_gen_write_data *self, +char const *data, int size) { if (self->filledto && self->filledto+size > self->maxlength) { @@ -1300,7 +2183,7 @@ int size) Allocates and initializes the data structure used by i_gen_writer. -This should be released with L +This should be released with L =cut */ @@ -1310,7 +2193,7 @@ i_gen_write_data *i_gen_write_data_new(i_write_callback_t cb, i_gen_write_data *self = mymalloc(sizeof(i_gen_write_data)); self->cb = cb; self->userdata = userdata; - self->maxlength = min(max_length, sizeof(self->buffer)); + self->maxlength = i_min(max_length, sizeof(self->buffer)); if (self->maxlength < 0) self->maxlength = sizeof(self->buffer); self->filledto = 0; @@ -1319,7 +2202,7 @@ i_gen_write_data *i_gen_write_data_new(i_write_callback_t cb, } /* -=item free_gen_write_data(i_gen_write_data *info, int flush) +=item i_free_gen_write_data(i_gen_write_data *info, int flush) Cleans up the write buffer. @@ -1333,7 +2216,7 @@ ie. if it fails. =cut */ -int free_gen_write_data(i_gen_write_data *info, int flush) +int i_free_gen_write_data(i_gen_write_data *info, int flush) { int result = !flush || info->filledto == 0 || @@ -1343,9 +2226,288 @@ int free_gen_write_data(i_gen_write_data *info, int flush) return result; } +struct magic_entry { + unsigned char *magic; + size_t magic_size; + char *name; + unsigned char *mask; +}; + +static int +test_magic(unsigned char *buffer, size_t length, struct magic_entry const *magic) { + if (length < magic->magic_size) + return 0; + if (magic->mask) { + int i; + unsigned char *bufp = buffer, + *maskp = magic->mask, + *magicp = magic->magic; + + for (i = 0; i < magic->magic_size; ++i) { + int mask = *maskp == 'x' ? 0xFF : *maskp == ' ' ? 0 : *maskp; + ++maskp; + + if ((*bufp++ & mask) != (*magicp++ & mask)) + return 0; + } + + return 1; + } + else { + return !memcmp(magic->magic, buffer, magic->magic_size); + } +} + +/* +=item i_test_format_probe(io_glue *data, int length) + +Check the beginning of the supplied file for a 'magic number' + +=cut +*/ + +#define FORMAT_ENTRY(magic, type) \ + { (unsigned char *)(magic ""), sizeof(magic)-1, type } +#define FORMAT_ENTRY2(magic, type, mask) \ + { (unsigned char *)(magic ""), sizeof(magic)-1, type, (unsigned char *)(mask) } + +const char * +i_test_format_probe(io_glue *data, int length) { + static const struct magic_entry formats[] = { + FORMAT_ENTRY("\xFF\xD8", "jpeg"), + FORMAT_ENTRY("GIF87a", "gif"), + FORMAT_ENTRY("GIF89a", "gif"), + FORMAT_ENTRY("MM\0*", "tiff"), + FORMAT_ENTRY("II*\0", "tiff"), + FORMAT_ENTRY("BM", "bmp"), + FORMAT_ENTRY("\x89PNG\x0d\x0a\x1a\x0a", "png"), + FORMAT_ENTRY("P1", "pnm"), + FORMAT_ENTRY("P2", "pnm"), + FORMAT_ENTRY("P3", "pnm"), + FORMAT_ENTRY("P4", "pnm"), + FORMAT_ENTRY("P5", "pnm"), + FORMAT_ENTRY("P6", "pnm"), + FORMAT_ENTRY("/* XPM", "xpm"), + FORMAT_ENTRY("\x8aMNG", "mng"), + FORMAT_ENTRY("\x8aJNG", "jng"), + /* SGI RGB - with various possible parameters to avoid false positives + on similar files + values are: 2 byte magic, rle flags (0 or 1), bytes/sample (1 or 2) + */ + FORMAT_ENTRY("\x01\xDA\x00\x01", "sgi"), + FORMAT_ENTRY("\x01\xDA\x00\x02", "sgi"), + FORMAT_ENTRY("\x01\xDA\x01\x01", "sgi"), + FORMAT_ENTRY("\x01\xDA\x01\x02", "sgi"), + + FORMAT_ENTRY2("FORM ILBM", "ilbm", "xxxx xxxx"), + + /* different versions of PCX format + http://www.fileformat.info/format/pcx/ + */ + FORMAT_ENTRY("\x0A\x00\x01", "pcx"), + FORMAT_ENTRY("\x0A\x02\x01", "pcx"), + FORMAT_ENTRY("\x0A\x03\x01", "pcx"), + FORMAT_ENTRY("\x0A\x04\x01", "pcx"), + FORMAT_ENTRY("\x0A\x05\x01", "pcx"), + + /* FITS - http://fits.gsfc.nasa.gov/ */ + FORMAT_ENTRY("SIMPLE =", "fits"), + + /* PSD - Photoshop */ + FORMAT_ENTRY("8BPS\x00\x01", "psd"), + + /* EPS - Encapsulated Postscript */ + /* only reading 18 chars, so we don't include the F in EPSF */ + FORMAT_ENTRY("%!PS-Adobe-2.0 EPS", "eps"), + + /* Utah RLE */ + FORMAT_ENTRY("\x52\xCC", "utah"), + + /* GZIP compressed, only matching deflate for now */ + FORMAT_ENTRY("\x1F\x8B\x08", "gzip"), + + /* bzip2 compressed */ + FORMAT_ENTRY("BZh", "bzip2"), + + /* WEBP + http://code.google.com/speed/webp/docs/riff_container.html */ + FORMAT_ENTRY2("RIFF WEBP", "webp", "xxxx xxxx"), + + /* JPEG 2000 + This might match a little loosely */ + FORMAT_ENTRY("\x00\x00\x00\x0CjP \x0D\x0A\x87\x0A", "jp2"), + }; + static const struct magic_entry more_formats[] = { + /* these were originally both listed as ico, but cur files can + include hotspot information */ + FORMAT_ENTRY("\x00\x00\x01\x00", "ico"), /* Windows icon */ + FORMAT_ENTRY("\x00\x00\x02\x00", "cur"), /* Windows cursor */ + FORMAT_ENTRY2("\x00\x00\x00\x00\x00\x00\x00\x07", + "xwd", " xxxx"), /* X Windows Dump */ + }; + + unsigned int i; + unsigned char head[18]; + ssize_t rc; + + io_glue_commit_types(data); + rc = data->readcb(data, head, 18); + if (rc == -1) return NULL; + data->seekcb(data, -rc, SEEK_CUR); + + for(i=0; iname; + } + + if ((rc == 18) && + tga_header_verify(head)) + return "tga"; + + for(i=0; iname; + } + + return NULL; +} + +/* +=item i_img_is_monochrome(img, &zero_is_white) + +=category Image Information + +Tests an image to check it meets our monochrome tests. + +The idea is that a file writer can use this to test where it should +write the image in whatever bi-level format it uses, eg. C for +C. + +For performance of encoders we require monochrome images: + +=over + +=item * + +be paletted + +=item * + +have a palette of two colors, containing only C<(0,0,0)> and +C<(255,255,255)> in either order. + +=back + +C is set to non-zero if the first palette entry is white. + +=cut +*/ + +int +i_img_is_monochrome(i_img *im, int *zero_is_white) { + if (im->type == i_palette_type + && i_colorcount(im) == 2) { + i_color colors[2]; + i_getcolors(im, 0, colors, 2); + if (im->channels == 3) { + if (colors[0].rgb.r == 255 && + colors[0].rgb.g == 255 && + colors[0].rgb.b == 255 && + colors[1].rgb.r == 0 && + colors[1].rgb.g == 0 && + colors[1].rgb.b == 0) { + *zero_is_white = 1; + return 1; + } + else if (colors[0].rgb.r == 0 && + colors[0].rgb.g == 0 && + colors[0].rgb.b == 0 && + colors[1].rgb.r == 255 && + colors[1].rgb.g == 255 && + colors[1].rgb.b == 255) { + *zero_is_white = 0; + return 1; + } + } + else if (im->channels == 1) { + if (colors[0].channel[0] == 255 && + colors[1].channel[0] == 0) { + *zero_is_white = 1; + return 1; + } + else if (colors[0].channel[0] == 0 && + colors[1].channel[0] == 255) { + *zero_is_white = 0; + return 1; + } + } + } + + *zero_is_white = 0; + return 0; +} + +/* +=item i_get_file_background(im, &bg) + +=category Files + +Retrieve the file write background color tag from the image. + +If not present, returns black. + +=cut +*/ + +void +i_get_file_background(i_img *im, i_color *bg) { + if (!i_tags_get_color(&im->tags, "i_background", 0, bg)) { + /* black default */ + bg->channel[0] = bg->channel[1] = bg->channel[2] = 0; + } + /* always full alpha */ + bg->channel[3] = 255; +} + +/* +=item i_get_file_backgroundf(im, &bg) + +=category Files + +Retrieve the file write background color tag from the image as a +floating point color. + +Implemented in terms of i_get_file_background(). + +If not present, returns black. + +=cut +*/ + +void +i_get_file_backgroundf(i_img *im, i_fcolor *fbg) { + i_color bg; + + i_get_file_background(im, &bg); + fbg->rgba.r = Sample8ToF(bg.rgba.r); + fbg->rgba.g = Sample8ToF(bg.rgba.g); + fbg->rgba.b = Sample8ToF(bg.rgba.b); + fbg->rgba.a = 1.0; +} + /* =back +=head1 AUTHOR + +Arnar M. Hrafnkelsson + +Tony Cook + =head1 SEE ALSO L, L