diff --git a/.gitattributes b/.gitattributes index fa20a1fcdd003c1f829a7f250bcdc86f710f8ddd..20ed0021f6e0141653bcc0218dcc63d1fcba6045 100644 --- a/.gitattributes +++ b/.gitattributes @@ -201,6 +201,7 @@ testfont binary /src/modules/Image/illustration.pike foreign_ident /src/modules/Image/image.c foreign_ident /src/modules/Image/image.h foreign_ident +/src/modules/Image/layers.c foreign_ident /src/modules/Image/matrix.c foreign_ident /src/modules/Image/mkwmml.pike foreign_ident /src/modules/Image/operator.c foreign_ident diff --git a/src/modules/Image/layers.c b/src/modules/Image/layers.c new file mode 100644 index 0000000000000000000000000000000000000000..2ef545d765df87f27782ba62451e368cfb5ee445 --- /dev/null +++ b/src/modules/Image/layers.c @@ -0,0 +1,1440 @@ +/* +**! module Image +**! note +**! $Id: layers.c,v 1.1 1999/04/17 19:41:58 mirar Exp $ +**! class Layer +*/ + +#include "global.h" +#include <config.h> + +RCSID("$Id: layers.c,v 1.1 1999/04/17 19:41:58 mirar Exp $"); + +#include "config.h" + +#include "stralloc.h" +#include "pike_macros.h" +#include "object.h" +#include "constants.h" +#include "interpret.h" +#include "svalue.h" +#include "array.h" +#include "mapping.h" +#include "threads.h" +#include "builtin_functions.h" +#include "dmalloc.h" +#include "operators.h" +#include "module_support.h" +#include "opcodes.h" + +#include "image.h" + +extern struct program *image_program; +struct program *image_layer_program; + +static struct mapping *colors=NULL; +static struct object *colortable=NULL; +static struct array *colornames=NULL; + +struct program *image_layer_program=NULL; +extern struct program *image_colortable_program; + +static const rgb_group black={0,0,0}; +static const rgb_group white={COLORMAX,COLORMAX,COLORMAX}; + +typedef void lm_row_func(rgb_group *s, + rgb_group *l, + rgb_group *d, + rgb_group *sa, + rgb_group *la, /* may be NULL */ + rgb_group *da, + int len, + float alpha); + + +struct layer +{ + int xsize; /* underlaying image size */ + int ysize; + + int xoffs,yoffs; /* clip offset */ + + struct object *image; /* image object */ + struct object *alpha; /* alpha object or null */ + + struct image *img; /* image object storage */ + struct image *alp; /* alpha object storage */ + + float alpha_value; /* overall alpha value (1.0=opaque) */ + + rgb_group fill; /* fill color ("outside" the layer) */ + rgb_group fill_alpha; /* fill alpha */ + + int tiled; /* true if tiled */ + + lm_row_func *row_func;/* layer mode */ +}; + +#define THIS ((struct layer *)(fp->current_storage)) +#define THISOBJ (fp->current_object) + +static void lm_normal(rgb_group *s,rgb_group *l,rgb_group *d, +rgb_group *sa,rgb_group *la,rgb_group *da, + int len,float alpha); +static void lm_dissolve(rgb_group *s,rgb_group *l,rgb_group *d, +rgb_group *sa,rgb_group *la,rgb_group *da, + int len,float alpha); +static void lm_behind(rgb_group *s,rgb_group *l,rgb_group *d, +rgb_group *sa,rgb_group *la,rgb_group *da, + int len,float alpha); +static void lm_multiply(rgb_group *s,rgb_group *l,rgb_group *d, +rgb_group *sa,rgb_group *la,rgb_group *da, + int len,float alpha); +static void lm_screen(rgb_group *s,rgb_group *l,rgb_group *d, +rgb_group *sa,rgb_group *la,rgb_group *da, + int len,float alpha); +static void lm_overlay(rgb_group *s,rgb_group *l,rgb_group *d, +rgb_group *sa,rgb_group *la,rgb_group *da, + int len,float alpha); +static void lm_difference(rgb_group *s,rgb_group *l,rgb_group *d, +rgb_group *sa,rgb_group *la,rgb_group *da, + int len,float alpha); +static void lm_addition(rgb_group *s,rgb_group *l,rgb_group *d, +rgb_group *sa,rgb_group *la,rgb_group *da, + int len,float alpha); +static void lm_subtract(rgb_group *s,rgb_group *l,rgb_group *d, +rgb_group *sa,rgb_group *la,rgb_group *da, + int len,float alpha); +static void lm_darken(rgb_group *s,rgb_group *l,rgb_group *d, +rgb_group *sa,rgb_group *la,rgb_group *da, + int len,float alpha); +static void lm_lighten(rgb_group *s,rgb_group *l,rgb_group *d, +rgb_group *sa,rgb_group *la,rgb_group *da, + int len,float alpha); +static void lm_hue(rgb_group *s,rgb_group *l,rgb_group *d, +rgb_group *sa,rgb_group *la,rgb_group *da, + int len,float alpha); +static void lm_saturation(rgb_group *s,rgb_group *l,rgb_group *d, +rgb_group *sa,rgb_group *la,rgb_group *da, + int len,float alpha); +static void lm_color(rgb_group *s,rgb_group *l,rgb_group *d, +rgb_group *sa,rgb_group *la,rgb_group *da, + int len,float alpha); +static void lm_value(rgb_group *s,rgb_group *l,rgb_group *d, +rgb_group *sa,rgb_group *la,rgb_group *da, + int len,float alpha); +static void lm_divide(rgb_group *s,rgb_group *l,rgb_group *d, +rgb_group *sa,rgb_group *la,rgb_group *da, + int len,float alpha); +static void lm_erase(rgb_group *s,rgb_group *l,rgb_group *d, +rgb_group *sa,rgb_group *la,rgb_group *da, + int len,float alpha); +static void lm_replace(rgb_group *s,rgb_group *l,rgb_group *d, +rgb_group *sa,rgb_group *la,rgb_group *da, + int len,float alpha); + +struct layer_mode_desc +{ + char *name; + lm_row_func *func; + struct pike_string *ps; +} layer_mode[]= +{ + {"normal", lm_normal, NULL }, +/* {"dissolve", lm_dissolve, NULL }, */ +/* {"behind", lm_behind, NULL }, */ +/* {"multiply", lm_multiply, NULL }, */ +/* {"screen", lm_screen, NULL }, */ +/* {"overlay", lm_overlay, NULL }, */ +/* {"difference", lm_difference, NULL }, */ +/* {"addition", lm_addition, NULL }, */ +/* {"subtract", lm_subtract, NULL }, */ +/* {"darken", lm_darken, NULL }, */ +/* {"lighten", lm_lighten, NULL }, */ +/* {"hue", lm_hue, NULL }, */ +/* {"saturation", lm_saturation, NULL }, */ +/* {"color", lm_color, NULL }, */ +/* {"value", lm_value, NULL }, */ +/* {"divide", lm_divide, NULL }, */ +/* {"erase", lm_erase, NULL }, */ +/* {"replace", lm_replace, NULL }, */ +} ; + +#define LAYER_MODES ((int)NELEM(layer_mode)) + +/*** layer object : init and exit *************************/ + +static void init_layer(struct object *dummy) +{ + THIS->xsize=0; + THIS->ysize=0; + THIS->xoffs=0; + THIS->yoffs=0; + THIS->image=NULL; + THIS->alpha=NULL; + THIS->img=NULL; + THIS->alp=NULL; + THIS->fill=black; + THIS->fill_alpha=black; + THIS->tiled=0; + THIS->alpha_value=1.0; + THIS->row_func=lm_normal; +} + +static void free_layer(struct layer *l) +{ + if (THIS->image) free_object(THIS->image); + if (THIS->alpha) free_object(THIS->alpha); + THIS->image=NULL; + THIS->alpha=NULL; + THIS->img=NULL; + THIS->alp=NULL; +} + +static void exit_layer(struct object *dummy) +{ + free_layer(THIS); +} + +/* +**! method object set_image(object(Image.Image) image) +**! method object set_image(object(Image.Image) image,object(Image.Image) alpha_channel) +**! method object|int(0) image() +**! method object|int(0) alpha() +**! Set/change/get image and alpha channel for the layer. +**! You could also cancel the channels giving 0 +**! instead of an image object. +**! note: +**! image and alpha channel must be of the same size, +**! or canceled. +*/ + +static void image_layer_set_image(INT32 args) +{ + struct image *img; + + if (THIS->image) + free_object(THIS->image); + THIS->image=NULL; + THIS->img=NULL; + + if (THIS->alpha) + free_object(THIS->alpha); + THIS->alpha=NULL; + THIS->alp=NULL; + + if (args>=1) + if ( sp[-args].type!=T_OBJECT ) + { + if (sp[-args].type!=T_INT || + sp[-args].u.integer!=0) + SIMPLE_BAD_ARG_ERROR("Image.Layer->set_image",1, + "object(image)|int(0)"); + } + else if ((img=(struct image*) + get_storage(sp[-args].u.object,image_program))) + { + THIS->image=sp[-args].u.object; + add_ref(THIS->image); + THIS->img=img; + THIS->xsize=img->xsize; + THIS->ysize=img->ysize; + } + else + SIMPLE_BAD_ARG_ERROR("Image.Layer->set_image",1, + "object(image)|int(0)"); + + if (args>=2) + if ( sp[1-args].type!=T_OBJECT ) + { + if (sp[1-args].type!=T_INT || + sp[1-args].u.integer!=0) + SIMPLE_BAD_ARG_ERROR("Image.Layer->set_image",2, + "object(image)|int(0)"); + } + else if ((img=(struct image*) + get_storage(sp[1-args].u.object,image_program))) + { + if (THIS->img && + (img->xsize!=THIS->xsize || + img->ysize!=THIS->ysize)) + SIMPLE_BAD_ARG_ERROR("Image.Layer->set_image",2, + "image of same size"); + if (!THIS->img) + { + THIS->xsize=img->xsize; + THIS->ysize=img->ysize; + } + THIS->alpha=sp[1-args].u.object; + add_ref(THIS->alpha); + THIS->alp=img; + } + else + SIMPLE_BAD_ARG_ERROR("Image.Layer->set_image",2, + "object(image)|int(0)"); + + pop_n_elems(args); + ref_push_object(THISOBJ); +} + +static void image_layer_image(INT32 args) +{ + pop_n_elems(args); + if (THIS->image) + ref_push_object(THIS->image); + else + push_int(0); +} + +static void image_layer_alpha(INT32 args) +{ + pop_n_elems(args); + if (THIS->alpha) + ref_push_object(THIS->alpha); + else + push_int(0); +} + +/* +**! method object set_alpha_value(float value) +**! method float alpha_value() +**! Set/get the general alpha value of this layer. +**! This is a float value between 0 and 1, +**! and is multiplied with the alpha channel. +*/ + +static void image_layer_set_alpha_value(INT32 args) +{ + float f; + get_all_args("Image.Layer->set_alpha_value",args,"%F",&f); + if (f<0.0 || f>=1.0) + SIMPLE_BAD_ARG_ERROR("Image.Layer->set_alpha_value",1,"float(0..1)"); + THIS->alpha_value=f; +} + +static void image_layer_alpha_value(INT32 args) +{ + pop_n_elems(args); + push_float(THIS->alpha_value); +} + +/* +**! method object set_mode(string mode) +**! method string mode() +**! Set/get layer mode. Mode is one of these: +**! +**! <tr><td valign=top align=left> +**! <b><tt>normal</tt><b></br> +**! ? +**! </td><td align=left valign=top> +**! <illustration> return lena(); </illustration> +**! <td></tr> +**! +**! <tr><td valign=top align=left> +**! <b><tt>addition</tt><b></br> +**! ? +**! </td><td align=left valign=top> +**! <illustration> return lena(); </illustration> +**! <td></tr> +**! +**! <tr><td valign=top align=left> +**! <b><tt>behind</tt><b></br> +**! ? +**! </td><td align=left valign=top> +**! <illustration> return lena(); </illustration> +**! <td></tr> +**! +**! <tr><td valign=top align=left> +**! <b><tt>color</tt><b></br> +**! ? +**! </td><td align=left valign=top> +**! <illustration> return lena(); </illustration> +**! <td></tr> +**! +**! <tr><td valign=top align=left> +**! <b><tt>darken</tt><b></br> +**! ? +**! </td><td align=left valign=top> +**! <illustration> return lena(); </illustration> +**! <td></tr> +**! +**! <tr><td valign=top align=left> +**! <b><tt>difference</tt><b></br> +**! ? +**! </td><td align=left valign=top> +**! <illustration> return lena(); </illustration> +**! <td></tr> +**! +**! <tr><td valign=top align=left> +**! <b><tt>dissolve</tt><b></br> +**! ? +**! </td><td align=left valign=top> +**! <illustration> return lena(); </illustration> +**! <td></tr> +**! +**! <tr><td valign=top align=left> +**! <b><tt>divide</tt><b></br> +**! ? +**! </td><td align=left valign=top> +**! <illustration> return lena(); </illustration> +**! <td></tr> +**! +**! <tr><td valign=top align=left> +**! <b><tt>erase</tt><b></br> +**! ? +**! </td><td align=left valign=top> +**! <illustration> return lena(); </illustration> +**! <td></tr> +**! +**! <tr><td valign=top align=left> +**! <b><tt>hue</tt><b></br> +**! ? +**! </td><td align=left valign=top> +**! <illustration> return lena(); </illustration> +**! <td></tr> +**! +**! <tr><td valign=top align=left> +**! <b><tt>lighten</tt><b></br> +**! ? +**! </td><td align=left valign=top> +**! <illustration> return lena(); </illustration> +**! <td></tr> +**! +**! <tr><td valign=top align=left> +**! <b><tt>multiply</tt><b></br> +**! ? +**! </td><td align=left valign=top> +**! <illustration> return lena(); </illustration> +**! <td></tr> +**! +**! <tr><td valign=top align=left> +**! <b><tt>overlay</tt><b></br> +**! ? +**! </td><td align=left valign=top> +**! <illustration> return lena(); </illustration> +**! <td></tr> +**! +**! <tr><td valign=top align=left> +**! <b><tt>replace</tt><b></br> +**! ? +**! </td><td align=left valign=top> +**! <illustration> return lena(); </illustration> +**! <td></tr> +**! +**! <tr><td valign=top align=left> +**! <b><tt>saturation</tt><b></br> +**! ? +**! </td><td align=left valign=top> +**! <illustration> return lena(); </illustration> +**! <td></tr> +**! +**! <tr><td valign=top align=left> +**! <b><tt>screen</tt><b></br> +**! ? +**! </td><td align=left valign=top> +**! <illustration> return lena(); </illustration> +**! <td></tr> +**! +**! <tr><td valign=top align=left> +**! <b><tt>subtract</tt><b></br> +**! ? +**! </td><td align=left valign=top> +**! <illustration> return lena(); </illustration> +**! <td></tr> +**! +**! <tr><td valign=top align=left> +**! <b><tt>value</tt><b></br> +**! ? +**! </td><td align=left valign=top> +**! <illustration> return lena(); </illustration> +**! <td></tr> +**! +**! note: +**! image and alpha channel must be of the same size, +**! or canceled. +*/ + +static void image_layer_set_mode(INT32 args) +{ + int i; + if (args!=1) + SIMPLE_TOO_FEW_ARGS_ERROR("Image.Layer->set_mode",1); + if (sp[-args].type!=T_STRING) + SIMPLE_BAD_ARG_ERROR("Image.Layer->set_mode",1,"string"); + + for (i=0; i<LAYER_MODES; i++) + if (sp[-args].u.string==layer_mode[i].ps) + { + THIS->row_func=layer_mode[i].func; + pop_n_elems(args); + ref_push_object(THISOBJ); + return; + } + + SIMPLE_BAD_ARG_ERROR("Image.Layer->set_mode",1,"string"); +} + +static void image_layer_mode(INT32 args) +{ + int i; + pop_n_elems(args); + + for (i=0; i<LAYER_MODES; i++) + if (THIS->row_func==layer_mode[i].func) + { + ref_push_string(layer_mode[i].ps); + return; + } + + fatal("illegal mode: %p\n",layer_mode[i].func); +} + +/* +**! method object set_fill(Color color) +**! method object set_fill(Color color,Color alpha) +**! method object fill() +**! method object fill_alpha() +**! Set/query fill color and alpha, ie the color used "outside" the +**! image. This is mostly useful if you want to "frame" +**! a layer. +*/ + +static void image_layer_set_fill(INT32 args) +{ + if (!args) + SIMPLE_TOO_FEW_ARGS_ERROR("Image.Layer->set_fill",1); + + if (sp[-args].type==T_INT && !sp[-args].u.integer) + THIS->fill=black; + else + if (!image_color_arg(-args,&(THIS->fill))) + SIMPLE_BAD_ARG_ERROR("Image.Layer->set_fill",1,"color"); + + if (args>1) + if (sp[1-args].type==T_INT && !sp[1-args].u.integer) + THIS->fill_alpha=white; + else + if (!image_color_arg(1-args,&(THIS->fill_alpha))) + SIMPLE_BAD_ARG_ERROR("Image.Layer->set_fill",2,"color"); + else + THIS->fill_alpha=white; + + pop_n_elems(args); + ref_push_object(THISOBJ); +} + +static void image_layer_fill(INT32 args) +{ + pop_n_elems(args); + _image_make_rgb_color(THIS->fill.r,THIS->fill.g,THIS->fill.b); +} + +static void image_layer_fill_alpha(INT32 args) +{ + pop_n_elems(args); + _image_make_rgb_color(THIS->fill_alpha.r, + THIS->fill_alpha.g, + THIS->fill_alpha.b); +} + +/* +**! method object set_offset(int x,int y) +**! method int xoffset() +**! method int yoffset() +**! Set/query layer offset. +*/ + +static void image_layer_set_offset(INT32 args) +{ + get_all_args("Image.Layer->set_offset",args,"%i%i", + &(THIS->xoffs),&(THIS->yoffs)); + pop_n_elems(args); + ref_push_object(THISOBJ); +} + +static void image_layer_xoffset(INT32 args) +{ + pop_n_elems(args); + push_int(THIS->xoffs); +} + +static void image_layer_yoffset(INT32 args) +{ + pop_n_elems(args); + push_int(THIS->yoffs); +} + +/* +**! method object set_tiled(int yes) +**! method int tiled() +**! Set/query <i>tiled</i> flag. If set, the +**! image and alpha channel will be tiled rather +**! then framed by the <ref>fill</ref> values. +*/ + +static void image_layer_set_tiled(INT32 args) +{ + get_all_args("Image.Layer->set_offset",args,"%i", + &(THIS->tiled)); + THIS->tiled=!!THIS->tiled; + pop_n_elems(args); + ref_push_object(THISOBJ); +} + +static void image_layer_tiled(INT32 args) +{ + pop_n_elems(args); + push_int(THIS->tiled); +} + +/* +**! method void create(object image,object alpha,string mode) +**! method void create(mapping info) +**! method void create() +**! +**! method void create(int xsize,int ysize,object color) +**! method void create(object color) +**! The Layer construct either three arguments, +**! the image object, alpha channel and mode, or +**! a mapping with optional elements: +**! <pre> +**! "image":image, +**! // default: black +**! +**! "alpha":alpha, +**! // alpha channel object +**! // default: full opaque +**! +**! "mode":string mode, +**! // layer mode, see <ref>mode</ref>. +**! // default: "normal" +**! +**! "alpha_value":float(0.0-1.0), +**! // layer general alpha value +**! // default is 1.0; this is multiplied +**! // with the alpha channel. +**! +**! "xoffset":int, +**! "yoffset":int, +**! // offset of this layer +**! +**! "fill":Color, +**! "fill_alpha":Color, +**! // fill color, ie what color is used +**! // "outside" the image. default: black +**! // and black (full transparency). +**! +**! "tiled":int(0|1), +**! // select tiling; if 1, the image +**! // will be tiled. deafult: 0, off +**! </pre> +**! The layer can also be created "empty", +**! either giving a size and color - +**! this will give a filled opaque square, +**! or a color, which will set the "fill" +**! values and fill the whole layer with an +**! opaque color. +**! +**! All values can be modified after object creation. +**! +**! note: +**! image and alpha channel must be of the same size. +**! +*/ + +static INLINE void try_parameter(char *a,void (*f)(INT32)) +{ + stack_dup(); + push_text(a); + f_index(2); + + if (!IS_UNDEFINED(sp-1)) + f(1); + pop_stack(); +} + +static INLINE void try_parameter_pair(char *a,char *b,void (*f)(INT32)) +{ + stack_dup(); /* map map */ + stack_dup(); /* map map map */ + push_text(a); /* map map map a */ + f_index(2); /* map map map[a] */ + stack_swap(); /* map map[a] map */ + push_text(b); /* map map[a] map b */ + f_index(2); /* map map[a] map[b] */ + + if (!IS_UNDEFINED(sp-2) || + !IS_UNDEFINED(sp-1)) + { + f(2); + pop_stack(); + } + else + pop_n_elems(2); +} + +static void image_layer_create(INT32 args) +{ + if (!args) + return; + if (sp[-args].type==T_MAPPING) + { + pop_n_elems(args-1); + try_parameter_pair("image","alpha",image_layer_set_image); + + try_parameter("mode",image_layer_set_mode); + try_parameter("alpha_value",image_layer_set_alpha_value); + + try_parameter_pair("xoffset","yoffset",image_layer_set_offset); + try_parameter_pair("fill","fill_alpha",image_layer_set_fill); + try_parameter("tiled",image_layer_set_tiled); + pop_stack(); + return; + } + else if (sp[-args].type==T_INT) + { + rgb_group col=black,alpha=white; + + get_all_args("Image.Layer",args,"%i%i",&(THIS->xsize),&(THIS->ysize)); + if (args>2) + if (!image_color_arg(2-args,&col)) + SIMPLE_BAD_ARG_ERROR("Image.Layer",3,"Image.Color"); + + if (args>3) + if (!image_color_arg(3-args,&alpha)) + SIMPLE_BAD_ARG_ERROR("Image.Layer",4,"Image.Color"); + + push_int(THIS->xsize); + push_int(THIS->ysize); + push_int(col.r); + push_int(col.g); + push_int(col.b); + push_object(clone_object(image_program,5)); + + push_int(THIS->xsize); + push_int(THIS->ysize); + push_int(alpha.r); + push_int(alpha.g); + push_int(alpha.b); + push_object(clone_object(image_program,5)); + + image_layer_set_image(2); + + pop_n_elems(args); + } + else if (sp[-args].type==T_OBJECT) + { + if (args>2) + { + image_layer_set_mode(args-2); + args=2; + } + image_layer_set_image(args); + pop_stack(); + } + else + SIMPLE_BAD_ARG_ERROR("Image.Layer",1,"mapping|int|Image.Image"); +} + +/*** layer object *****************************************/ + +static void image_layer_cast(INT32 args) +{ + if (!args) + SIMPLE_TOO_FEW_ARGS_ERROR("Image.Layer->cast",1); + if (sp[-args].type==T_STRING||sp[-args].u.string->size_shift) + { + if (strncmp(sp[-args].u.string->str,"mapping",7)==0) + { + int n=0; + pop_n_elems(args); + + push_text("xsize"); push_int(THIS->xsize); n++; + push_text("ysize"); push_int(THIS->ysize); n++; + push_text("image"); image_layer_image(0); n++; + push_text("alpha"); image_layer_image(0); n++; + push_text("xoffset"); push_int(THIS->xoffs); n++; + push_text("yoffset"); push_int(THIS->yoffs); n++; + push_text("alpha_value"); push_float(THIS->alpha_value); n++; + push_text("fill"); image_layer_fill(0); n++; + push_text("fill_alpha"); image_layer_fill_alpha(0); n++; + push_text("tiled"); push_int(THIS->tiled); n++; + push_text("mode"); image_layer_mode(0); n++; + + f_aggregate_mapping(n*2); + + return; + } + } + SIMPLE_BAD_ARG_ERROR("Image.Colortable->cast",1, + "string(\"mapping\"|\"array\"|\"string\")"); + +} + +/*** layer helpers ************************************/ + +static INLINE void smear_color(rgb_group *d,rgb_group s,int len) +{ + while (len--) + *(d++)=s; +} + +#define ALPHA_METHOD_INT + +#ifdef ALPHA_METHOD_INT + +#define CCUT(Z) ((COLORTYPE)((Z)/COLORMAX)) + +#define COMBINE_ALPHA_SUM(aS,aL) \ + CCUT((COLORMAX*(int)(aL))+(COLORMAX-(int)(aL))*(aS)) +#define COMBINE_ALPHA_SUM_V(aS,aL,V) \ + COMBINE_ALPHA_SUM(aS,(aL)*(V)) + +#define COMBINE_ALPHA(S,L,aS,aL) \ + ( (COLORTYPE)((((S)*((int)(COLORMAX-(aL)))*(aS))+ \ + ((L)*((int)(aL))*COLORMAX))/ \ + (((COLORMAX*(int)(aL))+(COLORMAX-(int)(aL))*(aS))) ) ) + +#define COMBINE_ALPHA_V(S,L,aS,aL,V) \ + COMBINE_ALPHA(S,(int)((L)*(V)),aS,aL) + +#else +#ifdef ALPHA_METHOD_FLOAT + +#define qMAX (1.0/COLORMAX) +#define C2F(Z) (qMAX*Z) +#define CCUT(Z) ((COLORTYPE)(qMAX*Z)) + +#define COMBINE_ALPHA(S,L,aS,aL) \ + ( (COLORTYPE)( ( (S)*(1.0-C2F(aL)*C2F(aS)) + (L)*C2F(aL) ) / + ( C2F(aL)+(1-C2F(aL))*C2F(aS)) ) ) + +#define COMBINE_ALPHA_V(S,L,aS,aL,V) \ + COMBINE_ALPHA(S,(L)*(V),aS,aL) + +#define COMBINE_ALPHA_SUM(aS,aL) \ + ((COLORTYPE)(COLORMAX*(C2F(aL)+(1.0-C2F(aL))*aS)) +#define COMBINE_ALPHA_SUM_V(aS,aL,V) \ + COMBINE_ALPHA_SUM(aS,(aL)*(V)) + +#else /* unknown ALPHA_METHOD */ +#error unknown ALPHA_METHOD +#endif ALPHA_METHOD_FLOAT + +#endif ALPHA_INT_IS_FASTER + + +/*** layer mode definitions ***************************/ + +static void lm_normal(rgb_group *s,rgb_group *l,rgb_group *d, + rgb_group *sa,rgb_group *la,rgb_group *da, + int len,float alpha) +{ + /* la may be NULL, no other */ + + if (alpha==0.0) /* optimized */ + { + MEMCPY(s,d,sizeof(rgb_group)*len); + MEMCPY(sa,da,sizeof(rgb_group)*len); + return; + } + else if (alpha==1.0) + { + if (!la) /* no layer alpha => full opaque */ + { + MEMCPY(s,d,sizeof(rgb_group)*len); + smear_color(da,white,len); + } + else + while (len--) + { +#define ALPHA_ADD(S,L,D,SA,LA,DA,C) \ + if (!LA->C) d->C=S->C,DA->C=SA->C; \ + else if (!SA->C) D->C=l->C,DA->C=LA->C; \ + else if (LA->C==COLORMAX) D->C=l->C,DA->C=LA->C; \ + else \ + D->C=COMBINE_ALPHA(S->C,l->C,SA->C,LA->C), \ + DA->C=COMBINE_ALPHA_SUM(SA->C,LA->C); + + if (la->r==COLORMAX && la->g==COLORMAX && la->b==COLORMAX) + { + *d=*l; + *da=*la; + } + else if (la->r==0 && la->g==0 && la->b==0) + { + *d=*s; + *da=*sa; + } + else + { + ALPHA_ADD(s,l,d,sa,la,da,r); + ALPHA_ADD(s,l,d,sa,la,da,g); + ALPHA_ADD(s,l,d,sa,la,da,b); + } +#undef ALPHA_ADD + l++; s++; la++; sa++; d++; da++; + } + } + else + { + if (!la) /* no layer alpha => alpha value opaque */ + while (len--) + { +#define ALPHA_ADD_V_NOLA(L,S,D,SA,DA,V,C) \ + do { \ + if (!SA->C) D->C=l->C,DA->C=0; \ + else \ + { \ + if (SA->C==COLORMAX) \ + D->C=COMBINE_ALPHA_V(S->C,l->C,COLORMAX,255,V); \ + else D->C=COMBINE_ALPHA_V(S->C,l->C,SA->C,255,V); \ + DA->C=COMBINE_ALPHA_SUM_V(SA->C,255,V); \ + } \ + } while(0) + + ALPHA_ADD_V_NOLA(s,l,d,sa,da,alpha,r); + ALPHA_ADD_V_NOLA(s,l,d,sa,da,alpha,g); + ALPHA_ADD_V_NOLA(s,l,d,sa,da,alpha,b); + +#undef ALPHA_ADD_V_NOLA + l++; s++; la++; sa++; da++; d++; + } + else + while (len--) + { +#define ALPHA_ADD_V(L,S,D,LA,SA,DA,V,C) \ + do { \ + if (!LA->C) \ + { \ + D->C=COMBINE_ALPHA_V(S->C,l->C,SA->C,0,V); \ + DA->C=COMBINE_ALPHA_SUM_V(0,SA->C,V); \ + } \ + else if (!SA->C) \ + { \ + D->C=COMBINE_ALPHA_V(S->C,l->C,0,LA->C,V); \ + DA->C=COMBINE_ALPHA_SUM_V(LA->C,0,V); \ + } \ + else \ + { \ + D->C=COMBINE_ALPHA_V(S->C,l->C,SA->C,LA->C,V); \ + DA->C=COMBINE_ALPHA_SUM_V(LA->C,SA->C,V); \ + } \ + } while (0) + + ALPHA_ADD_V(s,l,d,sa,la,da,alpha,r); + ALPHA_ADD_V(s,l,d,sa,la,da,alpha,g); + ALPHA_ADD_V(s,l,d,sa,la,da,alpha,b); + +#undef ALPHA_ADD_V + l++; s++; la++; sa++; da++; d++; + } + return; + } +} + + +/*** the add-layer function ***************************/ + +static void INLINE img_lay_first_line(struct layer *l, + int xoffs,int xsize, + int y, + rgb_group *d,rgb_group *da) +{ + if (!l->tiled) + { + rgb_group *s,*sa; + int len; + + if (y<l->yoffs || + y>=l->yoffs+l->ysize || + l->xoffs+l->xsize<xoffs || + l->xoffs>xoffs+xsize) /* outside */ + { + smear_color(d,l->fill,xsize); + smear_color(da,l->fill_alpha,xsize); + return; + } + + if (l->img) s=l->img->img+y*l->xsize; else s=NULL; + if (l->alp) sa=l->alp->img+y*l->xsize; else sa=NULL; + len=l->xsize; + + if (l->xoffs>xoffs) + { + /* fill to the left */ + smear_color(d,l->fill,l->xoffs-xoffs); + smear_color(da,l->fill_alpha,l->xoffs-xoffs); + + xsize-=l->xoffs-xoffs; + d+=l->xoffs-xoffs; + da+=l->xoffs-xoffs; + } + else + { + if (s) s+=xoffs-l->xoffs; + if (sa) sa+=xoffs-l->xoffs; + len-=xoffs-l->xoffs; + } + if (len<xsize) /* copy bit, fill right */ + { + if (s) MEMCPY(d,s,len*sizeof(rgb_group)); + else smear_color(d,l->fill,len); + if (sa) MEMCPY(da,sa,len*sizeof(rgb_group)); + else smear_color(da,white,len); + + smear_color(d+len,l->fill,xsize-len); + smear_color(da+len,l->fill_alpha,xsize-len); + } + else /* copy rest */ + { + if (s) MEMCPY(d,s,xsize*sizeof(rgb_group)); + else smear_color(d,l->fill,xsize); + if (sa) MEMCPY(da,sa,xsize*sizeof(rgb_group)); + else smear_color(da,white,xsize); + } + return; + } + else + { + rgb_group *s,*sa; + + y-=l->yoffs; + y%=l->ysize; + if (y<0) y+=l->ysize; + + if (l->img) s=l->img->img+y*l->xsize; + else smear_color(d,l->fill,xsize),s=NULL; + + if (l->alp) sa=l->alp->img+y*l->xsize; + else smear_color(da,white,xsize),sa=NULL; + + xoffs-=l->xoffs; /* position in source */ + xoffs%=l->xsize; + if (xoffs<0) xoffs+=l->xsize; + if (xoffs) + { + int len=l->xsize-xoffs; + if (len>l->xsize) len=l->xsize; + fprintf(stderr,"len=%d xoffs=%d\n",len,xoffs); + if (s) MEMCPY(d,s+xoffs,len*sizeof(rgb_group)); + if (sa) MEMCPY(da,sa+xoffs,len*sizeof(rgb_group)); + da+=len; + d+=len; + xsize-=len; + } + while (xsize>l->xsize) + { + fprintf(stderr,"s=%p xsize=%d d=%p\n",s,xsize,d); + if (s) MEMCPY(d,s,l->xsize*sizeof(rgb_group)); + if (sa) MEMCPY(d,sa,l->xsize*sizeof(rgb_group)); + da+=l->xsize; + d+=l->xsize; + xsize-=l->xsize; + } + if (s) MEMCPY(d,s,xsize*sizeof(rgb_group)); + if (sa) MEMCPY(d,sa,xsize*sizeof(rgb_group)); + } +} + +#define SNUMPIXS 64 /* pixels in short-stroke buffer */ + +static INLINE void img_lay_stroke(struct layer *ly, + rgb_group *stmp, + rgb_group *satmp, + int *sinited, + rgb_group *l, + rgb_group *la, + rgb_group *s, + rgb_group *sa, + rgb_group *d, + rgb_group *da, + int len) +{ + if ((l && la) || + (l && + ly->fill_alpha.r==0 && ly->fill_alpha.g==0 && ly->fill_alpha.b==0)) + { + (ly->row_func)(s,l,d,sa,la,da,len,ly->alpha_value); + return; + } + if (!*sinited) + { + smear_color(stmp,ly->fill,SNUMPIXS); + smear_color(satmp,ly->fill_alpha,SNUMPIXS); + sinited[0]=1; + } + + if (!la && + ly->fill_alpha.r==0 && ly->fill_alpha.g==0 && ly->fill_alpha.b==0) + { + while (len>SNUMPIXS) + { + (ly->row_func)(s,l?l:stmp,d,sa,NULL,da,SNUMPIXS,ly->alpha_value); + s+=SNUMPIXS; l+=SNUMPIXS; d+=SNUMPIXS; + sa+=SNUMPIXS; la+=SNUMPIXS; da+=SNUMPIXS; + } + (ly->row_func)(s,l?l:stmp,d,sa,NULL,da,len,ly->alpha_value); + } + else + { + while (len>SNUMPIXS) + { + (ly->row_func)(s,l?l:stmp,d,sa,la?la:satmp,da, + SNUMPIXS,ly->alpha_value); + s+=SNUMPIXS; l+=SNUMPIXS; d+=SNUMPIXS; + sa+=SNUMPIXS; la+=SNUMPIXS; da+=SNUMPIXS; + } + (ly->row_func)(s,l?l:stmp,d,sa,la?la:satmp,da,len,ly->alpha_value); + } +} + +static INLINE void img_lay_line(struct layer *ly, + rgb_group *s,rgb_group *sa, + int xoffs,int xsize, + int y, + rgb_group *d,rgb_group *da) +{ + rgb_group stmp[SNUMPIXS]; + rgb_group satmp[SNUMPIXS]; + int sinited=0; + + if (!ly->tiled) + { + int len; + rgb_group *l,*la; + + if (y<ly->yoffs || + y>=ly->yoffs+ly->ysize || + ly->xoffs+ly->xsize<xoffs || + ly->xoffs>xoffs+xsize) /* outside */ + { + img_lay_stroke(ly,stmp,satmp,&sinited,NULL,NULL,s,sa,d,da,xsize); + return; + } + + if (ly->img) l=ly->img->img+y*ly->xsize; else l=NULL; + if (ly->alp) la=ly->alp->img+y*ly->xsize; else la=NULL; + len=ly->xsize; + + if (ly->xoffs>xoffs) + { + /* fill to the left */ + img_lay_stroke(ly,stmp,satmp,&sinited,NULL,NULL, + s,sa,d,da,ly->xoffs-xoffs); + + xsize-=ly->xoffs-xoffs; + d+=ly->xoffs-xoffs; + da+=ly->xoffs-xoffs; + s+=ly->xoffs-xoffs; + sa+=ly->xoffs-xoffs; + } + else + { + if (l) l+=xoffs-ly->xoffs; + if (la) la+=xoffs-ly->xoffs; + len-=xoffs-ly->xoffs; + } + if (len<xsize) /* copy stroke, fill right */ + { + img_lay_stroke(ly,stmp,satmp,&sinited,l,la, + s,sa,d,da,ly->xoffs-xoffs); + + img_lay_stroke(ly,stmp,satmp,&sinited,NULL,NULL, + s+len,sa+len,d+len,da+len,xsize-len); + } + else /* copy rest */ + { + img_lay_stroke(ly,stmp,satmp,&sinited,l,la, + s,sa,d,da,xsize); + } + return; + } + else + { + rgb_group *l,*la; + + if (ly->img) l=ly->img->img+y*ly->xsize; else l=NULL; + if (ly->alp) la=ly->alp->img+y*ly->xsize; else la=NULL; + + y-=ly->yoffs; + y%=ly->ysize; + if (y<0) y+=ly->ysize; + + xoffs-=ly->xoffs; /* position in source */ + if (xoffs%ly->xsize) + { + int len=ly->xsize-(xoffs%ly->xsize); + img_lay_stroke(ly,stmp,satmp,&sinited,l?l+(xoffs%ly->xsize):NULL, + la?la+(xoffs%ly->xsize):NULL, + s,sa,d,da,len); + da+=len; + d+=len; + sa+=len; + s+=len; + xsize-=len; + } + while (xsize>ly->xsize) + { + img_lay_stroke(ly,stmp,satmp,&sinited,l,la, + s,sa,d,da,ly->xsize); + da+=ly->xsize; + d+=ly->xsize; + sa+=ly->xsize; + s+=ly->xsize; + xsize-=ly->xsize; + } + if (xsize) + img_lay_stroke(ly,stmp,satmp,&sinited,l,la, + s,sa,d,da,xsize); + } +} + + +void img_lay(struct layer **layer, + int layers, + struct layer *dest) +{ + rgb_group *line1,*line2,*aline1,*aline2,*tmp; + rgb_group *d,*da; + int width=dest->xsize; + int y,z; + int xoffs=dest->xoffs,xsize=dest->xsize; + + line1=malloc(sizeof(rgb_group)*width); + line2=malloc(sizeof(rgb_group)*width); + aline1=malloc(sizeof(rgb_group)*width); + aline2=malloc(sizeof(rgb_group)*width); + if (!line1 || !line2 || !aline1 || !aline2) + { + if (line1) free(line1); + if (line2) free(line2); + if (aline1) free(aline1); + if (aline2) free(aline2); + resource_error(NULL,0,0,"memory",sizeof(rgb_group)*4*width, + "Out of memory.\n"); + } + + da=dest->alp->img; + d=dest->img->img; + + /* loop over lines */ + for (y=0; y<dest->ysize; y++) + { + if (layers>1) + { + /* add the bottom layer first */ + if (layer[0]->row_func==lm_normal) /* cheat */ + img_lay_first_line(layer[0],xoffs,xsize,y+dest->yoffs, + line1,aline1),z=1; + else + { + smear_color(line1,black,xsize); + smear_color(aline1,black,xsize); + z=0; + } + + /* loop over the rest of the layers, except the last */ + for (; z<layers-2; z++) + { + img_lay_line(layer[z],line1,aline1, + xoffs,xsize,y,line2,aline2); + /* swap buffers */ + tmp=line1; line1=line2; line2=tmp; + tmp=aline1; aline1=aline2; aline2=tmp; + } + + /* make the last layer on the destionation */ + img_lay_line(layer[layers-1],line1,aline1, + xoffs,xsize,y+dest->yoffs,d,da); + } + else + { + /* make the layer to destination*/ + img_lay_first_line(layer[0],xoffs,xsize,y+dest->yoffs,d,da); + } + d+=dest->xsize; + da+=dest->xsize; + } + + free(line1); + free(aline1); + free(line2); + free(aline2); +} + +/* +**! module Image +**! method Image.Layer lay(array(Image.Layer|mapping)) +**! method Image.Layer lay(array(Image.Layer|mapping),int xoffset,int yoffset,int xsize,int ysize) +**! Combine layers. +**! returns a new layer object. +**! +**! see also: Image.Layer +*/ + +void image_lay(INT32 args) +{ + int layers,i; + struct layer **l; + struct object *o; + struct layer *dest; + struct array *a; + int gotoffs; + int xoffset=0,yoffset=0,xsize=0,ysize=0; + + if (!args) + SIMPLE_TOO_FEW_ARGS_ERROR("Image.lay",1); + + if (sp[-args].type!=T_ARRAY) + SIMPLE_BAD_ARG_ERROR("Image.lay",1, + "array(Image.Layer|mapping)"); + + if (args>1) + { + get_all_args("Image.lay",args-1,"%i%i%i%i", + &xoffset,&yoffset,&xsize,&ysize); + if (xsize<1) + SIMPLE_BAD_ARG_ERROR("Image.lay",4,"int(1..)"); + if (ysize<1) + SIMPLE_BAD_ARG_ERROR("Image.lay",5,"int(1..)"); + } + + layers=(a=sp[-args].u.array)->size; + + if (!layers) /* dummy return empty layer */ + { + pop_n_elems(args); + push_object(clone_object(image_layer_program,0)); + return; + } + + l=(struct layer**)xalloc(sizeof(struct layer)*layers); + + for (i=0; i<layers; i++) + { + if (a->item[i].type==T_OBJECT) + { + if (!(l[i]=(struct layer*)get_storage(a->item[i].u.object, + image_layer_program))) + SIMPLE_BAD_ARG_ERROR("Image.lay",1, + "array(Image.Layer|mapping)"); + } + else if (a->item[i].type==T_MAPPING) + { + push_svalue(a->item+i); + push_object(o=clone_object(image_layer_program,1)); + args++; + l[i]=(struct layer*)get_storage(o,image_layer_program); + } + else + SIMPLE_BAD_ARG_ERROR("Image.lay",1, + "array(Image.Layer|mapping)"); + } + + if (xsize==0) /* figure offset and size */ + { + xoffset=l[0]->xoffs; + yoffset=l[0]->yoffs; + xsize=l[0]->xsize; + ysize=l[0]->ysize; + for (i=1; i<layers; i++) + { + int t; + if (l[i]->xoffs<xoffset) + t=xoffset-l[i]->xoffs,xoffset-=t,xsize+=t; + if (l[i]->yoffs<yoffset) + t=yoffset-l[i]->yoffs,yoffset-=t,ysize+=t; + if (l[i]->xsize+l[i]->xoffs-xoffset>xsize) + xsize=l[i]->xsize+l[i]->xoffs-xoffset>xsize; + if (l[i]->ysize+l[i]->yoffs-yoffset>ysize) + ysize=l[i]->ysize+l[i]->yoffs-yoffset>ysize; + } + } + + fprintf(stderr,"%d,%d @ %d,%d\n",xsize,ysize,xoffset,yoffset); + + /* get destination layer */ + push_int(xsize); + push_int(ysize); + push_object(o=clone_object(image_layer_program,2)); + + dest=(struct layer*)get_storage(o,image_layer_program); + dest->xoffs=xoffset; + dest->yoffs=yoffset; + + /* ok, do it! */ + img_lay(l,layers,dest); + + sp--; + pop_n_elems(args); + push_object(o); +} + +/******************************************************/ + +void init_image_layers(void) +{ + char buf[100]; + char buf2[sizeof(INT32)]; + int i; + + for (i=0; i<LAYER_MODES; i++) + layer_mode[i].ps=make_shared_string(layer_mode[i].name); + + start_new_program(); + + ADD_STORAGE(struct layer); + set_init_callback(init_layer); + set_exit_callback(exit_layer); + +#define tLayerMap tMap(tString,tOr4(tString,tColor,tFloat,tInt)) + + ADD_FUNCTION("create",image_layer_create, + tOr4(tFunc(,tVoid), + tFunc(tObj tOr(tObj,tVoid) tOr(tString,tVoid),tVoid), + tFunc(tLayerMap,tVoid), + tFunc(tInt tInt + tOr(tColor,tVoid) tOr(tColor,tVoid),tVoid)),0); + + ADD_FUNCTION("cast",image_layer_cast, + tFunc(tString,tMapping),0); + + /* query */ + + ADD_FUNCTION("image",image_layer_image,tFunc(,tObj),0); + ADD_FUNCTION("alpha",image_layer_alpha,tFunc(,tObj),0); + ADD_FUNCTION("mode",image_layer_mode,tFunc(,tStr),0); + + ADD_FUNCTION("xoffset",image_layer_xoffset,tFunc(,tInt),0); + ADD_FUNCTION("yoffset",image_layer_yoffset,tFunc(,tInt),0); + + ADD_FUNCTION("alpha_value",image_layer_alpha_value,tFunc(,tFloat),0); + ADD_FUNCTION("fill",image_layer_fill,tFunc(,tObj),0); + ADD_FUNCTION("fill_alpha",image_layer_fill_alpha,tFunc(,tObj),0); + + ADD_FUNCTION("tiled",image_layer_tiled,tFunc(,tInt01),0); + + image_layer_program=end_program(); + + add_program_constant("Layer",image_layer_program,0); + + ADD_FUNCTION("lay",image_lay, + tOr(tFunc(tArr(tOr(tObj,tLayerMap)),tObj), + tFunc(tArr(tOr(tObj,tLayerMap)) + tInt tInt tInt tInt,tObj)),0); +} + +void exit_image_layers(void) +{ + int i; + + for (i=0; i<LAYER_MODES; i++) + free_string(layer_mode[i].ps); + + if (image_layer_program) + { + free_program(image_layer_program); + image_layer_program=NULL; + } +}