- extra concept index entries
[imager.git] / filters.c
CommitLineData
02d1d628 1#include "image.h"
290bdf77 2#include "imagei.h"
02d1d628
AMH
3#include <stdlib.h>
4#include <math.h>
5
6
7/*
8=head1 NAME
9
10filters.c - implements filters that operate on images
11
12=head1 SYNOPSIS
13
14
15 i_contrast(im, 0.8);
16 i_hardinvert(im);
b6381851 17 i_unsharp_mask(im, 2.0, 1.0);
02d1d628
AMH
18 // and more
19
20=head1 DESCRIPTION
21
22filters.c implements basic filters for Imager. These filters
23should be accessible from the filter interface as defined in
24the pod for Imager.
25
26=head1 FUNCTION REFERENCE
27
28Some of these functions are internal.
29
b8c2033e 30=over
02d1d628
AMH
31
32=cut
33*/
34
35
36
37
b2778574
AMH
38/*
39=item saturate(in)
40
41Clamps the input value between 0 and 255. (internal)
42
43 in - input integer
44
45=cut
46*/
47
48static
49unsigned char
50saturate(int in) {
51 if (in>255) { return 255; }
52 else if (in>0) return in;
53 return 0;
54}
02d1d628
AMH
55
56
57
58/*
59=item i_contrast(im, intensity)
60
61Scales the pixel values by the amount specified.
62
63 im - image object
64 intensity - scalefactor
65
66=cut
67*/
68
69void
70i_contrast(i_img *im, float intensity) {
71 int x, y;
72 unsigned char ch;
73 unsigned int new_color;
74 i_color rcolor;
75
76 mm_log((1,"i_contrast(im %p, intensity %f)\n", im, intensity));
77
78 if(intensity < 0) return;
79
80 for(y = 0; y < im->ysize; y++) for(x = 0; x < im->xsize; x++) {
81 i_gpix(im, x, y, &rcolor);
82
83 for(ch = 0; ch < im->channels; ch++) {
84 new_color = (unsigned int) rcolor.channel[ch];
85 new_color *= intensity;
86
87 if(new_color > 255) {
88 new_color = 255;
89 }
90 rcolor.channel[ch] = (unsigned char) new_color;
91 }
92 i_ppix(im, x, y, &rcolor);
93 }
94}
95
96
97/*
98=item i_hardinvert(im)
99
100Inverts the pixel values of the input image.
101
102 im - image object
103
104=cut
105*/
106
107void
108i_hardinvert(i_img *im) {
109 int x, y;
110 unsigned char ch;
111
112 i_color rcolor;
113
114 mm_log((1,"i_hardinvert(im %p)\n", im));
115
116 for(y = 0; y < im->ysize; y++) {
117 for(x = 0; x < im->xsize; x++) {
118 i_gpix(im, x, y, &rcolor);
119
120 for(ch = 0; ch < im->channels; ch++) {
121 rcolor.channel[ch] = 255 - rcolor.channel[ch];
122 }
123
124 i_ppix(im, x, y, &rcolor);
125 }
126 }
127}
128
129
130
131/*
132=item i_noise(im, amount, type)
133
134Inverts the pixel values by the amount specified.
135
136 im - image object
137 amount - deviation in pixel values
138 type - noise individual for each channel if true
139
140=cut
141*/
142
8a00cb26 143#ifdef WIN32
02d1d628
AMH
144/* random() is non-ASCII, even if it is better than rand() */
145#define random() rand()
146#endif
147
148void
149i_noise(i_img *im, float amount, unsigned char type) {
150 int x, y;
151 unsigned char ch;
152 int new_color;
153 float damount = amount * 2;
154 i_color rcolor;
155 int color_inc = 0;
156
157 mm_log((1,"i_noise(im %p, intensity %.2f\n", im, amount));
158
159 if(amount < 0) return;
160
161 for(y = 0; y < im->ysize; y++) for(x = 0; x < im->xsize; x++) {
162 i_gpix(im, x, y, &rcolor);
163
164 if(type == 0) {
165 color_inc = (amount - (damount * ((float)random() / RAND_MAX)));
166 }
167
168 for(ch = 0; ch < im->channels; ch++) {
169 new_color = (int) rcolor.channel[ch];
170
171 if(type != 0) {
172 new_color += (amount - (damount * ((float)random() / RAND_MAX)));
173 } else {
174 new_color += color_inc;
175 }
176
177 if(new_color < 0) {
178 new_color = 0;
179 }
180 if(new_color > 255) {
181 new_color = 255;
182 }
183
184 rcolor.channel[ch] = (unsigned char) new_color;
185 }
186
187 i_ppix(im, x, y, &rcolor);
188 }
189}
190
191
192/*
193=item i_noise(im, amount, type)
194
195Inverts the pixel values by the amount specified.
196
197 im - image object
198 amount - deviation in pixel values
199 type - noise individual for each channel if true
200
201=cut
202*/
203
204
205/*
206=item i_applyimage(im, add_im, mode)
207
208Apply's an image to another image
209
210 im - target image
211 add_im - image that is applied to target
212 mode - what method is used in applying:
213
214 0 Normal
215 1 Multiply
216 2 Screen
217 3 Overlay
218 4 Soft Light
219 5 Hard Light
220 6 Color dodge
221 7 Color Burn
222 8 Darker
223 9 Lighter
224 10 Add
225 11 Subtract
226 12 Difference
227 13 Exclusion
228
229=cut
230*/
231
232void i_applyimage(i_img *im, i_img *add_im, unsigned char mode) {
233 int x, y;
234 int mx, my;
235
236 mm_log((1, "i_applyimage(im %p, add_im %p, mode %d", im, add_im, mode));
237
238 mx = (add_im->xsize <= im->xsize) ? add_im->xsize : add_im->xsize;
239 my = (add_im->ysize <= im->ysize) ? add_im->ysize : add_im->ysize;
240
241 for(x = 0; x < mx; x++) {
242 for(y = 0; y < my; y++) {
243 }
244 }
245}
246
247
248/*
249=item i_bumpmap(im, bump, channel, light_x, light_y, st)
250
251Makes a bumpmap on image im using the bump image as the elevation map.
252
253 im - target image
254 bump - image that contains the elevation info
255 channel - to take the elevation information from
256 light_x - x coordinate of light source
257 light_y - y coordinate of light source
258 st - length of shadow
259
260=cut
261*/
262
263void
264i_bumpmap(i_img *im, i_img *bump, int channel, int light_x, int light_y, int st) {
265 int x, y, ch;
266 int mx, my;
267 i_color x1_color, y1_color, x2_color, y2_color, dst_color;
268 double nX, nY;
269 double tX, tY, tZ;
270 double aX, aY, aL;
271 double fZ;
272 unsigned char px1, px2, py1, py2;
273
274 i_img new_im;
275
276 mm_log((1, "i_bumpmap(im %p, add_im %p, channel %d, light_x %d, light_y %d, st %d)\n",
277 im, bump, channel, light_x, light_y, st));
278
279
280 if(channel >= bump->channels) {
281 mm_log((1, "i_bumpmap: channel = %d while bump image only has %d channels\n", channel, bump->channels));
282 return;
283 }
b2778574 284
02d1d628
AMH
285 mx = (bump->xsize <= im->xsize) ? bump->xsize : im->xsize;
286 my = (bump->ysize <= im->ysize) ? bump->ysize : im->ysize;
287
288 i_img_empty_ch(&new_im, im->xsize, im->ysize, im->channels);
b2778574 289
02d1d628
AMH
290 aX = (light_x > (mx >> 1)) ? light_x : mx - light_x;
291 aY = (light_y > (my >> 1)) ? light_y : my - light_y;
292
293 aL = sqrt((aX * aX) + (aY * aY));
294
295 for(y = 1; y < my - 1; y++) {
296 for(x = 1; x < mx - 1; x++) {
297 i_gpix(bump, x + st, y, &x1_color);
298 i_gpix(bump, x, y + st, &y1_color);
299 i_gpix(bump, x - st, y, &x2_color);
300 i_gpix(bump, x, y - st, &y2_color);
301
302 i_gpix(im, x, y, &dst_color);
303
304 px1 = x1_color.channel[channel];
305 py1 = y1_color.channel[channel];
306 px2 = x2_color.channel[channel];
307 py2 = y2_color.channel[channel];
308
309 nX = px1 - px2;
310 nY = py1 - py2;
311
312 nX += 128;
313 nY += 128;
314
315 fZ = (sqrt((nX * nX) + (nY * nY)) / aL);
316
317 tX = abs(x - light_x) / aL;
318 tY = abs(y - light_y) / aL;
319
320 tZ = 1 - (sqrt((tX * tX) + (tY * tY)) * fZ);
321
322 if(tZ < 0) tZ = 0;
323 if(tZ > 2) tZ = 2;
324
325 for(ch = 0; ch < im->channels; ch++)
326 dst_color.channel[ch] = (unsigned char) (float)(dst_color.channel[ch] * tZ);
327
328 i_ppix(&new_im, x, y, &dst_color);
329 }
330 }
331
332 i_copyto(im, &new_im, 0, 0, (int)im->xsize, (int)im->ysize, 0, 0);
333
334 i_img_exorcise(&new_im);
335}
336
337
338
b2778574
AMH
339
340typedef struct {
341 float x,y,z;
342} fvec;
343
344
345static
346float
347dotp(fvec *a, fvec *b) {
348 return a->x*b->x+a->y*b->y+a->z*b->z;
349}
350
351static
352void
353normalize(fvec *a) {
354 double d = sqrt(dotp(a,a));
355 a->x /= d;
356 a->y /= d;
357 a->z /= d;
358}
359
360
361/*
362 positive directions:
363
364 x - right,
365 y - down
366 z - out of the plane
367
368 I = Ia + Ip*( cd*Scol(N.L) + cs*(R.V)^n )
369
370 Here, the variables are:
371
372 * Ia - ambient colour
373 * Ip - intensity of the point light source
374 * cd - diffuse coefficient
375 * Scol - surface colour
376 * cs - specular coefficient
377 * n - objects shinyness
378 * N - normal vector
379 * L - lighting vector
380 * R - reflection vector
381 * V - vision vector
382
383 static void fvec_dump(fvec *x) {
384 printf("(%.2f %.2f %.2f)", x->x, x->y, x->z);
385 }
386*/
387
388/* XXX: Should these return a code for success? */
389
390
391
392
393/*
394=item i_bumpmap_complex(im, bump, channel, tx, ty, Lx, Ly, Lz, Ip, cd, cs, n, Ia, Il, Is)
395
396Makes a bumpmap on image im using the bump image as the elevation map.
397
398 im - target image
399 bump - image that contains the elevation info
400 channel - to take the elevation information from
401 tx - shift in x direction of where to start applying bumpmap
402 ty - shift in y direction of where to start applying bumpmap
403 Lx - x position/direction of light
404 Ly - y position/direction of light
405 Lz - z position/direction of light
406 Ip - light intensity
407 cd - diffuse coefficient
408 cs - specular coefficient
409 n - surface shinyness
410 Ia - ambient colour
411 Il - light colour
412 Is - specular colour
413
414if z<0 then the L is taken to be the direction the light is shining in. Otherwise
415the L is taken to be the position of the Light, Relative to the image.
416
417=cut
418*/
419
420
421void
422i_bumpmap_complex(i_img *im,
423 i_img *bump,
424 int channel,
425 int tx,
426 int ty,
427 float Lx,
428 float Ly,
429 float Lz,
430 float cd,
431 float cs,
432 float n,
433 i_color *Ia,
434 i_color *Il,
435 i_color *Is) {
436 i_img new_im;
437
438 int inflight;
439 int x, y, ch;
440 int mx, Mx, my, My;
441
442 float cdc[MAXCHANNELS];
443 float csc[MAXCHANNELS];
444
445 i_color x1_color, y1_color, x2_color, y2_color;
446
447 i_color Scol; /* Surface colour */
448
449 fvec L; /* Light vector */
450 fvec N; /* surface normal */
451 fvec R; /* Reflection vector */
452 fvec V; /* Vision vector */
453
454 mm_log((1, "i_bumpmap_complex(im %p, bump %p, channel %d, tx %d, ty %d, Lx %.2f, Ly %.2f, Lz %.2f, cd %.2f, cs %.2f, n %.2f, Ia %p, Il %p, Is %p)\n",
455 im, bump, channel, tx, ty, Lx, Ly, Lz, cd, cs, n, Ia, Il, Is));
456
457 if (channel >= bump->channels) {
458 mm_log((1, "i_bumpmap_complex: channel = %d while bump image only has %d channels\n", channel, bump->channels));
459 return;
460 }
461
462 for(ch=0; ch<im->channels; ch++) {
463 cdc[ch] = (float)Il->channel[ch]*cd/255.f;
464 csc[ch] = (float)Is->channel[ch]*cs/255.f;
465 }
466
467 mx = 1;
468 my = 1;
469 Mx = bump->xsize-1;
470 My = bump->ysize-1;
471
472 V.x = 0;
473 V.y = 0;
474 V.z = 1;
475
476 if (Lz < 0) { /* Light specifies a direction vector, reverse it to get the vector from surface to light */
477 L.x = -Lx;
478 L.y = -Ly;
479 L.z = -Lz;
480 normalize(&L);
481 } else { /* Light is the position of the light source */
482 inflight = 0;
483 L.x = -0.2;
484 L.y = -0.4;
485 L.z = 1;
486 normalize(&L);
487 }
488
489 i_img_empty_ch(&new_im, im->xsize, im->ysize, im->channels);
490
491 for(y = 0; y < im->ysize; y++) {
492 for(x = 0; x < im->xsize; x++) {
493 double dp1, dp2;
494 double dx = 0, dy = 0;
495
496 /* Calculate surface normal */
497 if (mx<x && x<Mx && my<y && y<My) {
498 i_gpix(bump, x + 1, y, &x1_color);
499 i_gpix(bump, x - 1, y, &x2_color);
500 i_gpix(bump, x, y + 1, &y1_color);
501 i_gpix(bump, x, y - 1, &y2_color);
502 dx = x2_color.channel[channel] - x1_color.channel[channel];
503 dy = y2_color.channel[channel] - y1_color.channel[channel];
504 } else {
505 dx = 0;
506 dy = 0;
507 }
508 N.x = -dx * 0.015;
509 N.y = -dy * 0.015;
510 N.z = 1;
511 normalize(&N);
512
513 /* Calculate Light vector if needed */
514 if (Lz>=0) {
515 L.x = Lx - x;
516 L.y = Ly - y;
517 L.z = Lz;
518 normalize(&L);
519 }
520
521 dp1 = dotp(&L,&N);
522 R.x = -L.x + 2*dp1*N.x;
523 R.y = -L.y + 2*dp1*N.y;
524 R.z = -L.z + 2*dp1*N.z;
525
526 dp2 = dotp(&R,&V);
527
528 dp1 = dp1<0 ?0 : dp1;
529 dp2 = pow(dp2<0 ?0 : dp2,n);
530
531 i_gpix(im, x, y, &Scol);
532
533 for(ch = 0; ch < im->channels; ch++)
534 Scol.channel[ch] =
535 saturate( Ia->channel[ch] + cdc[ch]*Scol.channel[ch]*dp1 + csc[ch]*dp2 );
536
537 i_ppix(&new_im, x, y, &Scol);
538 }
539 }
540
541 i_copyto(im, &new_im, 0, 0, (int)im->xsize, (int)im->ysize, 0, 0);
542 i_img_exorcise(&new_im);
543}
544
545
02d1d628
AMH
546/*
547=item i_postlevels(im, levels)
548
549Quantizes Images to fewer levels.
550
551 im - target image
552 levels - number of levels
553
554=cut
555*/
556
557void
558i_postlevels(i_img *im, int levels) {
559 int x, y, ch;
560 float pv;
561 int rv;
562 float av;
563
564 i_color rcolor;
565
566 rv = (int) ((float)(256 / levels));
567 av = (float)levels;
568
569 for(y = 0; y < im->ysize; y++) for(x = 0; x < im->xsize; x++) {
570 i_gpix(im, x, y, &rcolor);
571
572 for(ch = 0; ch < im->channels; ch++) {
573 pv = (((float)rcolor.channel[ch] / 255)) * av;
574 pv = (int) ((int)pv * rv);
575
576 if(pv < 0) pv = 0;
577 else if(pv > 255) pv = 255;
578
579 rcolor.channel[ch] = (unsigned char) pv;
580 }
581 i_ppix(im, x, y, &rcolor);
582 }
583}
584
585
586/*
587=item i_mosaic(im, size)
588
589Makes an image looks like a mosaic with tilesize of size
590
591 im - target image
592 size - size of tiles
593
594=cut
595*/
596
597void
598i_mosaic(i_img *im, int size) {
599 int x, y, ch;
600 int lx, ly, z;
601 long sqrsize;
602
603 i_color rcolor;
604 long col[256];
605
606 sqrsize = size * size;
607
608 for(y = 0; y < im->ysize; y += size) for(x = 0; x < im->xsize; x += size) {
609 for(z = 0; z < 256; z++) col[z] = 0;
610
611 for(lx = 0; lx < size; lx++) {
612 for(ly = 0; ly < size; ly++) {
613 i_gpix(im, (x + lx), (y + ly), &rcolor);
614
615 for(ch = 0; ch < im->channels; ch++) {
616 col[ch] += rcolor.channel[ch];
617 }
618 }
619 }
620
621 for(ch = 0; ch < im->channels; ch++)
622 rcolor.channel[ch] = (int) ((float)col[ch] / sqrsize);
623
624
625 for(lx = 0; lx < size; lx++)
626 for(ly = 0; ly < size; ly++)
627 i_ppix(im, (x + lx), (y + ly), &rcolor);
628
629 }
630}
631
02d1d628
AMH
632
633/*
634=item i_watermark(im, wmark, tx, ty, pixdiff)
635
636Applies a watermark to the target image
637
638 im - target image
639 wmark - watermark image
640 tx - x coordinate of where watermark should be applied
641 ty - y coordinate of where watermark should be applied
642 pixdiff - the magnitude of the watermark, controls how visible it is
643
644=cut
645*/
646
647void
648i_watermark(i_img *im, i_img *wmark, int tx, int ty, int pixdiff) {
649 int vx, vy, ch;
650 i_color val, wval;
651
985ffb60
AMH
652 int mx = wmark->xsize;
653 int my = wmark->ysize;
654
655 for(vx=0;vx<mx;vx++) for(vy=0;vy<my;vy++) {
02d1d628
AMH
656
657 i_gpix(im, tx+vx, ty+vy,&val );
658 i_gpix(wmark, vx, vy, &wval);
659
660 for(ch=0;ch<im->channels;ch++)
661 val.channel[ch] = saturate( val.channel[ch] + (pixdiff* (wval.channel[0]-128) )/128 );
662
663 i_ppix(im,tx+vx,ty+vy,&val);
664 }
665}
666
667
668/*
669=item i_autolevels(im, lsat, usat, skew)
670
671Scales and translates each color such that it fills the range completely.
672Skew is not implemented yet - purpose is to control the color skew that can
673occur when changing the contrast.
674
675 im - target image
676 lsat - fraction of pixels that will be truncated at the lower end of the spectrum
677 usat - fraction of pixels that will be truncated at the higher end of the spectrum
678 skew - not used yet
679
680=cut
681*/
682
683void
684i_autolevels(i_img *im, float lsat, float usat, float skew) {
685 i_color val;
686 int i, x, y, rhist[256], ghist[256], bhist[256];
687 int rsum, rmin, rmax;
688 int gsum, gmin, gmax;
689 int bsum, bmin, bmax;
690 int rcl, rcu, gcl, gcu, bcl, bcu;
691
692 mm_log((1,"i_autolevels(im %p, lsat %f,usat %f,skew %f)\n", im, lsat,usat,skew));
693
694 rsum=gsum=bsum=0;
695 for(i=0;i<256;i++) rhist[i]=ghist[i]=bhist[i] = 0;
696 /* create histogram for each channel */
697 for(y = 0; y < im->ysize; y++) for(x = 0; x < im->xsize; x++) {
698 i_gpix(im, x, y, &val);
699 rhist[val.channel[0]]++;
700 ghist[val.channel[1]]++;
701 bhist[val.channel[2]]++;
702 }
703
704 for(i=0;i<256;i++) {
705 rsum+=rhist[i];
706 gsum+=ghist[i];
707 bsum+=bhist[i];
708 }
709
710 rmin = gmin = bmin = 0;
711 rmax = gmax = bmax = 255;
712
713 rcu = rcl = gcu = gcl = bcu = bcl = 0;
714
715 for(i=0; i<256; i++) {
716 rcl += rhist[i]; if ( (rcl<rsum*lsat) ) rmin=i;
717 rcu += rhist[255-i]; if ( (rcu<rsum*usat) ) rmax=255-i;
718
719 gcl += ghist[i]; if ( (gcl<gsum*lsat) ) gmin=i;
720 gcu += ghist[255-i]; if ( (gcu<gsum*usat) ) gmax=255-i;
721
722 bcl += bhist[i]; if ( (bcl<bsum*lsat) ) bmin=i;
723 bcu += bhist[255-i]; if ( (bcu<bsum*usat) ) bmax=255-i;
724 }
725
726 for(y = 0; y < im->ysize; y++) for(x = 0; x < im->xsize; x++) {
727 i_gpix(im, x, y, &val);
728 val.channel[0]=saturate((val.channel[0]-rmin)*255/(rmax-rmin));
729 val.channel[1]=saturate((val.channel[1]-gmin)*255/(gmax-gmin));
730 val.channel[2]=saturate((val.channel[2]-bmin)*255/(bmax-bmin));
731 i_ppix(im, x, y, &val);
732 }
733}
734
735/*
736=item Noise(x,y)
737
738Pseudo noise utility function used to generate perlin noise. (internal)
739
740 x - x coordinate
741 y - y coordinate
742
743=cut
744*/
745
746static
747float
748Noise(int x, int y) {
749 int n = x + y * 57;
750 n = (n<<13) ^ n;
751 return ( 1.0 - ( (n * (n * n * 15731 + 789221) + 1376312589) & 0x7fffffff) / 1073741824.0);
752}
753
754/*
755=item SmoothedNoise1(x,y)
756
757Pseudo noise utility function used to generate perlin noise. (internal)
758
759 x - x coordinate
760 y - y coordinate
761
762=cut
763*/
764
765static
766float
767SmoothedNoise1(float x, float y) {
768 float corners = ( Noise(x-1, y-1)+Noise(x+1, y-1)+Noise(x-1, y+1)+Noise(x+1, y+1) ) / 16;
769 float sides = ( Noise(x-1, y) +Noise(x+1, y) +Noise(x, y-1) +Noise(x, y+1) ) / 8;
770 float center = Noise(x, y) / 4;
771 return corners + sides + center;
772}
773
774
775/*
776=item G_Interpolate(a, b, x)
777
778Utility function used to generate perlin noise. (internal)
779
780=cut
781*/
782
783static
784float C_Interpolate(float a, float b, float x) {
785 /* float ft = x * 3.1415927; */
786 float ft = x * PI;
787 float f = (1 - cos(ft)) * .5;
788 return a*(1-f) + b*f;
789}
790
791
792/*
793=item InterpolatedNoise(x, y)
794
795Utility function used to generate perlin noise. (internal)
796
797=cut
798*/
799
800static
801float
802InterpolatedNoise(float x, float y) {
803
804 int integer_X = x;
805 float fractional_X = x - integer_X;
806 int integer_Y = y;
807 float fractional_Y = y - integer_Y;
808
809 float v1 = SmoothedNoise1(integer_X, integer_Y);
810 float v2 = SmoothedNoise1(integer_X + 1, integer_Y);
811 float v3 = SmoothedNoise1(integer_X, integer_Y + 1);
812 float v4 = SmoothedNoise1(integer_X + 1, integer_Y + 1);
813
814 float i1 = C_Interpolate(v1 , v2 , fractional_X);
815 float i2 = C_Interpolate(v3 , v4 , fractional_X);
816
817 return C_Interpolate(i1 , i2 , fractional_Y);
818}
819
820
821
822/*
823=item PerlinNoise_2D(x, y)
824
825Utility function used to generate perlin noise. (internal)
826
827=cut
828*/
829
830static
831float
832PerlinNoise_2D(float x, float y) {
833 int i,frequency;
834 float amplitude;
835 float total = 0;
836 int Number_Of_Octaves=6;
837 int n = Number_Of_Octaves - 1;
838
839 for(i=0;i<n;i++) {
840 frequency = 2*i;
841 amplitude = PI;
842 total = total + InterpolatedNoise(x * frequency, y * frequency) * amplitude;
843 }
844
845 return total;
846}
847
848
849/*
850=item i_radnoise(im, xo, yo, rscale, ascale)
851
852Perlin-like radial noise.
853
854 im - target image
855 xo - x coordinate of center
856 yo - y coordinate of center
857 rscale - radial scale
858 ascale - angular scale
859
860=cut
861*/
862
863void
864i_radnoise(i_img *im, int xo, int yo, float rscale, float ascale) {
865 int x, y, ch;
866 i_color val;
867 unsigned char v;
868 float xc, yc, r;
869 double a;
870
871 for(y = 0; y < im->ysize; y++) for(x = 0; x < im->xsize; x++) {
872 xc = (float)x-xo+0.5;
873 yc = (float)y-yo+0.5;
874 r = rscale*sqrt(xc*xc+yc*yc)+1.2;
875 a = (PI+atan2(yc,xc))*ascale;
876 v = saturate(128+100*(PerlinNoise_2D(a,r)));
877 /* v=saturate(120+12*PerlinNoise_2D(xo+(float)x/scale,yo+(float)y/scale)); Good soft marble */
878 for(ch=0; ch<im->channels; ch++) val.channel[ch]=v;
879 i_ppix(im, x, y, &val);
880 }
881}
882
883
884/*
885=item i_turbnoise(im, xo, yo, scale)
886
887Perlin-like 2d noise noise.
888
889 im - target image
890 xo - x coordinate translation
891 yo - y coordinate translation
892 scale - scale of noise
893
894=cut
895*/
896
897void
898i_turbnoise(i_img *im, float xo, float yo, float scale) {
899 int x,y,ch;
900 unsigned char v;
901 i_color val;
902
903 for(y = 0; y < im->ysize; y++) for(x = 0; x < im->xsize; x++) {
904 /* v=saturate(125*(1.0+PerlinNoise_2D(xo+(float)x/scale,yo+(float)y/scale))); */
905 v = saturate(120*(1.0+sin(xo+(float)x/scale+PerlinNoise_2D(xo+(float)x/scale,yo+(float)y/scale))));
906 for(ch=0; ch<im->channels; ch++) val.channel[ch] = v;
907 i_ppix(im, x, y, &val);
908 }
909}
910
911
912
913/*
914=item i_gradgen(im, num, xo, yo, ival, dmeasure)
915
916Gradient generating function.
917
918 im - target image
919 num - number of points given
920 xo - array of x coordinates
921 yo - array of y coordinates
922 ival - array of i_color objects
923 dmeasure - distance measure to be used.
924 0 = Euclidean
925 1 = Euclidean squared
926 2 = Manhattan distance
927
928=cut
929*/
930
931
932void
933i_gradgen(i_img *im, int num, int *xo, int *yo, i_color *ival, int dmeasure) {
934
935 i_color val;
936 int p, x, y, ch;
937 int channels = im->channels;
938 int xsize = im->xsize;
939 int ysize = im->ysize;
f0960b14 940 int bytes;
02d1d628
AMH
941
942 float *fdist;
943
944 mm_log((1,"i_gradgen(im %p, num %d, xo %p, yo %p, ival %p, dmeasure %d)\n", im, num, xo, yo, ival, dmeasure));
945
946 for(p = 0; p<num; p++) {
947 mm_log((1,"i_gradgen: (%d, %d)\n", xo[p], yo[p]));
948 ICL_info(&ival[p]);
949 }
950
f0960b14
TC
951 /* on the systems I have sizeof(float) == sizeof(int) and thus
952 this would be same size as the arrays xo and yo point at, but this
953 may not be true for other systems
954
955 since the arrays here are caller controlled, I assume that on
956 overflow is a programming error rather than an end-user error, so
957 calling exit() is justified.
958 */
959 bytes = sizeof(float) * num;
960 if (bytes / num != sizeof(float)) {
961 fprintf(stderr, "integer overflow calculating memory allocation");
962 exit(1);
963 }
964 fdist = mymalloc( bytes ); /* checked 14jul05 tonyc */
02d1d628
AMH
965
966 for(y = 0; y<ysize; y++) for(x = 0; x<xsize; x++) {
967 float cs = 0;
968 float csd = 0;
969 for(p = 0; p<num; p++) {
970 int xd = x-xo[p];
971 int yd = y-yo[p];
972 switch (dmeasure) {
973 case 0: /* euclidean */
974 fdist[p] = sqrt(xd*xd + yd*yd); /* euclidean distance */
975 break;
976 case 1: /* euclidean squared */
977 fdist[p] = xd*xd + yd*yd; /* euclidean distance */
978 break;
979 case 2: /* euclidean squared */
b33c08f8 980 fdist[p] = i_max(xd*xd, yd*yd); /* manhattan distance */
02d1d628
AMH
981 break;
982 default:
983 m_fatal(3,"i_gradgen: Unknown distance measure\n");
984 }
985 cs += fdist[p];
986 }
987
988 csd = 1/((num-1)*cs);
989
990 for(p = 0; p<num; p++) fdist[p] = (cs-fdist[p])*csd;
991
992 for(ch = 0; ch<channels; ch++) {
993 int tres = 0;
994 for(p = 0; p<num; p++) tres += ival[p].channel[ch] * fdist[p];
995 val.channel[ch] = saturate(tres);
996 }
997 i_ppix(im, x, y, &val);
998 }
a73aeb5f 999 myfree(fdist);
02d1d628
AMH
1000
1001}
1002
02d1d628
AMH
1003void
1004i_nearest_color_foo(i_img *im, int num, int *xo, int *yo, i_color *ival, int dmeasure) {
1005
a743c0a6 1006 int p, x, y;
02d1d628
AMH
1007 int xsize = im->xsize;
1008 int ysize = im->ysize;
1009
1010 mm_log((1,"i_gradgen(im %p, num %d, xo %p, yo %p, ival %p, dmeasure %d)\n", im, num, xo, yo, ival, dmeasure));
1011
1012 for(p = 0; p<num; p++) {
1013 mm_log((1,"i_gradgen: (%d, %d)\n", xo[p], yo[p]));
1014 ICL_info(&ival[p]);
1015 }
1016
1017 for(y = 0; y<ysize; y++) for(x = 0; x<xsize; x++) {
1018 int midx = 0;
1019 float mindist = 0;
1020 float curdist = 0;
1021
1022 int xd = x-xo[0];
1023 int yd = y-yo[0];
1024
1025 switch (dmeasure) {
1026 case 0: /* euclidean */
1027 mindist = sqrt(xd*xd + yd*yd); /* euclidean distance */
1028 break;
1029 case 1: /* euclidean squared */
1030 mindist = xd*xd + yd*yd; /* euclidean distance */
1031 break;
1032 case 2: /* euclidean squared */
b33c08f8 1033 mindist = i_max(xd*xd, yd*yd); /* manhattan distance */
02d1d628
AMH
1034 break;
1035 default:
1036 m_fatal(3,"i_nearest_color: Unknown distance measure\n");
1037 }
1038
1039 for(p = 1; p<num; p++) {
1040 xd = x-xo[p];
1041 yd = y-yo[p];
1042 switch (dmeasure) {
1043 case 0: /* euclidean */
1044 curdist = sqrt(xd*xd + yd*yd); /* euclidean distance */
1045 break;
1046 case 1: /* euclidean squared */
1047 curdist = xd*xd + yd*yd; /* euclidean distance */
1048 break;
1049 case 2: /* euclidean squared */
b33c08f8 1050 curdist = i_max(xd*xd, yd*yd); /* manhattan distance */
02d1d628
AMH
1051 break;
1052 default:
1053 m_fatal(3,"i_nearest_color: Unknown distance measure\n");
1054 }
1055 if (curdist < mindist) {
1056 mindist = curdist;
1057 midx = p;
1058 }
1059 }
1060 i_ppix(im, x, y, &ival[midx]);
1061 }
1062}
1063
f0960b14
TC
1064/*
1065=item i_nearest_color(im, num, xo, yo, oval, dmeasure)
1066
1067This wasn't document - quoth Addi:
1068
1069 An arty type of filter
1070
1071FIXME: check IRC logs for actual text.
1072
1073Inputs:
1074
1075=over
1076
1077=item *
1078
1079i_img *im - image to render on.
1080
1081=item *
1082
1083int num - number of points/colors in xo, yo, oval
1084
1085=item *
1086
1087int *xo - array of I<num> x positions
1088
1089=item *
1090
1091int *yo - array of I<num> y positions
1092
1093=item *
1094
1095i_color *oval - array of I<num> colors
1096
1097xo, yo, oval correspond to each other, the point xo[i], yo[i] has a
1098color something like oval[i], at least closer to that color than other
1099points.
1100
1101=item *
1102
1103int dmeasure - how we measure the distance from some point P(x,y) to
1104any (xo[i], yo[i]).
1105
1106Valid values are:
1107
1108=over
1109
1110=item 0
1111
1112euclidean distance: sqrt((x2-x1)**2 + (y2-y1)**2)
1113
1114=item 1
1115
1116square of euclidean distance: ((x2-x1)**2 + (y2-y1)**2)
1117
1118=item 2
1119
1120manhattan distance: max((y2-y1)**2, (x2-x1)**2)
1121
1122=back
1123
1124An invalid value causes an error exit (the program is aborted).
1125
1126=back
1127
1128=cut
1129 */
02d1d628
AMH
1130void
1131i_nearest_color(i_img *im, int num, int *xo, int *yo, i_color *oval, int dmeasure) {
1132 i_color *ival;
1133 float *tval;
1134 float c1, c2;
1135 i_color val;
1136 int p, x, y, ch;
02d1d628
AMH
1137 int xsize = im->xsize;
1138 int ysize = im->ysize;
1139 int *cmatch;
1140
f0960b14 1141 mm_log((1,"i_nearest_color(im %p, num %d, xo %p, yo %p, oval %p, dmeasure %d)\n", im, num, xo, yo, oval, dmeasure));
02d1d628
AMH
1142
1143 tval = mymalloc( sizeof(float)*num*im->channels );
1144 ival = mymalloc( sizeof(i_color)*num );
1145 cmatch = mymalloc( sizeof(int)*num );
1146
1147 for(p = 0; p<num; p++) {
1148 for(ch = 0; ch<im->channels; ch++) tval[ p * im->channels + ch] = 0;
1149 cmatch[p] = 0;
1150 }
1151
1152
1153 for(y = 0; y<ysize; y++) for(x = 0; x<xsize; x++) {
1154 int midx = 0;
1155 float mindist = 0;
1156 float curdist = 0;
1157
1158 int xd = x-xo[0];
1159 int yd = y-yo[0];
1160
1161 switch (dmeasure) {
1162 case 0: /* euclidean */
1163 mindist = sqrt(xd*xd + yd*yd); /* euclidean distance */
1164 break;
1165 case 1: /* euclidean squared */
1166 mindist = xd*xd + yd*yd; /* euclidean distance */
1167 break;
1168 case 2: /* euclidean squared */
b33c08f8 1169 mindist = i_max(xd*xd, yd*yd); /* manhattan distance */
02d1d628
AMH
1170 break;
1171 default:
1172 m_fatal(3,"i_nearest_color: Unknown distance measure\n");
1173 }
1174
1175 for(p = 1; p<num; p++) {
1176 xd = x-xo[p];
1177 yd = y-yo[p];
1178 switch (dmeasure) {
1179 case 0: /* euclidean */
1180 curdist = sqrt(xd*xd + yd*yd); /* euclidean distance */
1181 break;
1182 case 1: /* euclidean squared */
1183 curdist = xd*xd + yd*yd; /* euclidean distance */
1184 break;
1185 case 2: /* euclidean squared */
b33c08f8 1186 curdist = i_max(xd*xd, yd*yd); /* manhattan distance */
02d1d628
AMH
1187 break;
1188 default:
1189 m_fatal(3,"i_nearest_color: Unknown distance measure\n");
1190 }
1191 if (curdist < mindist) {
1192 mindist = curdist;
1193 midx = p;
1194 }
1195 }
1196
1197 cmatch[midx]++;
1198 i_gpix(im, x, y, &val);
1199 c2 = 1.0/(float)(cmatch[midx]);
1200 c1 = 1.0-c2;
1201
02d1d628 1202 for(ch = 0; ch<im->channels; ch++)
f0960b14
TC
1203 tval[midx*im->channels + ch] =
1204 c1*tval[midx*im->channels + ch] + c2 * (float) val.channel[ch];
3bb1c1f3 1205
02d1d628
AMH
1206 }
1207
f0960b14
TC
1208 for(p = 0; p<num; p++) for(ch = 0; ch<im->channels; ch++)
1209 ival[p].channel[ch] = tval[p*im->channels + ch];
02d1d628
AMH
1210
1211 i_nearest_color_foo(im, num, xo, yo, ival, dmeasure);
1212}
6607600c 1213
b6381851
TC
1214/*
1215=item i_unsharp_mask(im, stddev, scale)
1216
1217Perform an usharp mask, which is defined as subtracting the blurred
1218image from double the original.
1219
1220=cut
1221*/
1222void i_unsharp_mask(i_img *im, double stddev, double scale) {
1223 i_img copy;
1224 int x, y, ch;
1225
1226 if (scale < 0)
1227 return;
1228 /* it really shouldn't ever be more than 1.0, but maybe ... */
1229 if (scale > 100)
1230 scale = 100;
1231
1232 i_copy(&copy, im);
1233 i_gaussian(&copy, stddev);
1234 if (im->bits == i_8_bits) {
1235 i_color *blur = mymalloc(im->xsize * sizeof(i_color) * 2);
1236 i_color *out = blur + im->xsize;
1237
1238 for (y = 0; y < im->ysize; ++y) {
1239 i_glin(&copy, 0, copy.xsize, y, blur);
1240 i_glin(im, 0, im->xsize, y, out);
1241 for (x = 0; x < im->xsize; ++x) {
1242 for (ch = 0; ch < im->channels; ++ch) {
1243 /*int temp = out[x].channel[ch] +
1244 scale * (out[x].channel[ch] - blur[x].channel[ch]);*/
1245 int temp = out[x].channel[ch] * 2 - blur[x].channel[ch];
1246 if (temp < 0)
1247 temp = 0;
1248 else if (temp > 255)
1249 temp = 255;
1250 out[x].channel[ch] = temp;
1251 }
1252 }
1253 i_plin(im, 0, im->xsize, y, out);
1254 }
1255
1256 myfree(blur);
1257 }
1258 else {
1259 i_fcolor *blur = mymalloc(im->xsize * sizeof(i_fcolor) * 2);
1260 i_fcolor *out = blur + im->xsize;
1261
1262 for (y = 0; y < im->ysize; ++y) {
1263 i_glinf(&copy, 0, copy.xsize, y, blur);
1264 i_glinf(im, 0, im->xsize, y, out);
1265 for (x = 0; x < im->xsize; ++x) {
1266 for (ch = 0; ch < im->channels; ++ch) {
1267 double temp = out[x].channel[ch] +
1268 scale * (out[x].channel[ch] - blur[x].channel[ch]);
1269 if (temp < 0)
1270 temp = 0;
1271 else if (temp > 1.0)
1272 temp = 1.0;
1273 out[x].channel[ch] = temp;
1274 }
1275 }
1276 i_plinf(im, 0, im->xsize, y, out);
1277 }
1278
1279 myfree(blur);
1280 }
1281 i_img_exorcise(&copy);
1282}
1283
dff75dee
TC
1284/*
1285=item i_diff_image(im1, im2, mindiff)
1286
1287Creates a new image that is transparent, except where the pixel in im2
1288is different from im1, where it is the pixel from im2.
1289
1290The samples must differ by at least mindiff to be considered different.
1291
1292=cut
1293*/
1294
1295i_img *
1296i_diff_image(i_img *im1, i_img *im2, int mindiff) {
1297 i_img *out;
1298 int outchans, diffchans;
1299 int xsize, ysize;
dff75dee
TC
1300
1301 i_clear_error();
1302 if (im1->channels != im2->channels) {
1303 i_push_error(0, "different number of channels");
1304 return NULL;
1305 }
1306
1307 outchans = diffchans = im1->channels;
1308 if (outchans == 1 || outchans == 3)
1309 ++outchans;
1310
b33c08f8
TC
1311 xsize = i_min(im1->xsize, im2->xsize);
1312 ysize = i_min(im1->ysize, im2->ysize);
dff75dee
TC
1313
1314 out = i_sametype_chans(im1, xsize, ysize, outchans);
1315
1316 if (im1->bits == i_8_bits && im2->bits == i_8_bits) {
1317 i_color *line1 = mymalloc(2 * xsize * sizeof(*line1));
1318 i_color *line2 = line1 + xsize;
1319 i_color empty;
1320 int x, y, ch;
1321
1322 for (ch = 0; ch < MAXCHANNELS; ++ch)
1323 empty.channel[ch] = 0;
1324
1325 for (y = 0; y < ysize; ++y) {
1326 i_glin(im1, 0, xsize, y, line1);
1327 i_glin(im2, 0, xsize, y, line2);
35342ea0
TC
1328 if (outchans != diffchans) {
1329 /* give the output an alpha channel since it doesn't have one */
1330 for (x = 0; x < xsize; ++x)
1331 line2[x].channel[diffchans] = 255;
1332 }
dff75dee
TC
1333 for (x = 0; x < xsize; ++x) {
1334 int diff = 0;
1335 for (ch = 0; ch < diffchans; ++ch) {
1336 if (line1[x].channel[ch] != line2[x].channel[ch]
1337 && abs(line1[x].channel[ch] - line2[x].channel[ch]) > mindiff) {
1338 diff = 1;
1339 break;
1340 }
1341 }
1342 if (!diff)
1343 line2[x] = empty;
1344 }
1345 i_plin(out, 0, xsize, y, line2);
1346 }
1347 myfree(line1);
1348 }
1349 else {
1350 i_fcolor *line1 = mymalloc(2 * xsize * sizeof(*line1));
1351 i_fcolor *line2 = line1 + xsize;
1352 i_fcolor empty;
1353 int x, y, ch;
1354 double dist = mindiff / 255;
1355
1356 for (ch = 0; ch < MAXCHANNELS; ++ch)
1357 empty.channel[ch] = 0;
1358
1359 for (y = 0; y < ysize; ++y) {
1360 i_glinf(im1, 0, xsize, y, line1);
1361 i_glinf(im2, 0, xsize, y, line2);
35342ea0
TC
1362 if (outchans != diffchans) {
1363 /* give the output an alpha channel since it doesn't have one */
1364 for (x = 0; x < xsize; ++x)
1365 line2[x].channel[diffchans] = 1.0;
1366 }
dff75dee
TC
1367 for (x = 0; x < xsize; ++x) {
1368 int diff = 0;
1369 for (ch = 0; ch < diffchans; ++ch) {
1370 if (line1[x].channel[ch] != line2[x].channel[ch]
1371 && abs(line1[x].channel[ch] - line2[x].channel[ch]) > dist) {
1372 diff = 1;
1373 break;
1374 }
1375 }
1376 if (!diff)
1377 line2[x] = empty;
1378 }
1379 i_plinf(out, 0, xsize, y, line2);
1380 }
1381 myfree(line1);
1382 }
1383
1384 return out;
1385}
1386
f1ac5027 1387struct fount_state;
6607600c
TC
1388static double linear_fount_f(double x, double y, struct fount_state *state);
1389static double bilinear_fount_f(double x, double y, struct fount_state *state);
1390static double radial_fount_f(double x, double y, struct fount_state *state);
1391static double square_fount_f(double x, double y, struct fount_state *state);
1392static double revolution_fount_f(double x, double y,
1393 struct fount_state *state);
1394static double conical_fount_f(double x, double y, struct fount_state *state);
1395
1396typedef double (*fount_func)(double, double, struct fount_state *);
1397static fount_func fount_funcs[] =
1398{
1399 linear_fount_f,
1400 bilinear_fount_f,
1401 radial_fount_f,
1402 square_fount_f,
1403 revolution_fount_f,
1404 conical_fount_f,
1405};
1406
1407static double linear_interp(double pos, i_fountain_seg *seg);
1408static double sine_interp(double pos, i_fountain_seg *seg);
1409static double sphereup_interp(double pos, i_fountain_seg *seg);
1410static double spheredown_interp(double pos, i_fountain_seg *seg);
1411typedef double (*fount_interp)(double pos, i_fountain_seg *seg);
1412static fount_interp fount_interps[] =
1413{
1414 linear_interp,
1415 linear_interp,
1416 sine_interp,
1417 sphereup_interp,
1418 spheredown_interp,
1419};
1420
1421static void direct_cinterp(i_fcolor *out, double pos, i_fountain_seg *seg);
1422static void hue_up_cinterp(i_fcolor *out, double pos, i_fountain_seg *seg);
1423static void hue_down_cinterp(i_fcolor *out, double pos, i_fountain_seg *seg);
1424typedef void (*fount_cinterp)(i_fcolor *out, double pos, i_fountain_seg *seg);
1425static fount_cinterp fount_cinterps[] =
1426{
1427 direct_cinterp,
1428 hue_up_cinterp,
1429 hue_down_cinterp,
1430};
1431
1432typedef double (*fount_repeat)(double v);
1433static double fount_r_none(double v);
1434static double fount_r_sawtooth(double v);
1435static double fount_r_triangle(double v);
1436static double fount_r_saw_both(double v);
1437static double fount_r_tri_both(double v);
1438static fount_repeat fount_repeats[] =
1439{
1440 fount_r_none,
1441 fount_r_sawtooth,
1442 fount_r_triangle,
1443 fount_r_saw_both,
1444 fount_r_tri_both,
1445};
1446
f1ac5027
TC
1447static int simple_ssample(i_fcolor *out, double x, double y,
1448 struct fount_state *state);
1449static int random_ssample(i_fcolor *out, double x, double y,
1450 struct fount_state *state);
1451static int circle_ssample(i_fcolor *out, double x, double y,
1452 struct fount_state *state);
1453typedef int (*fount_ssample)(i_fcolor *out, double x, double y,
1454 struct fount_state *state);
6607600c
TC
1455static fount_ssample fount_ssamples[] =
1456{
1457 NULL,
1458 simple_ssample,
1459 random_ssample,
1460 circle_ssample,
1461};
1462
1463static int
f1ac5027
TC
1464fount_getat(i_fcolor *out, double x, double y, struct fount_state *state);
1465
1466/*
1467 Keep state information used by each type of fountain fill
1468*/
1469struct fount_state {
1470 /* precalculated for the equation of the line perpendicular to the line AB */
1471 double lA, lB, lC;
1472 double AB;
1473 double sqrtA2B2;
1474 double mult;
1475 double cos;
1476 double sin;
1477 double theta;
1478 int xa, ya;
1479 void *ssample_data;
1480 fount_func ffunc;
1481 fount_repeat rpfunc;
1482 fount_ssample ssfunc;
1483 double parm;
1484 i_fountain_seg *segs;
1485 int count;
1486};
1487
1488static void
1489fount_init_state(struct fount_state *state, double xa, double ya,
1490 double xb, double yb, i_fountain_type type,
1491 i_fountain_repeat repeat, int combine, int super_sample,
1492 double ssample_param, int count, i_fountain_seg *segs);
1493
1494static void
1495fount_finish_state(struct fount_state *state);
6607600c
TC
1496
1497#define EPSILON (1e-6)
1498
1499/*
1500=item i_fountain(im, xa, ya, xb, yb, type, repeat, combine, super_sample, ssample_param, count, segs)
1501
1502Draws a fountain fill using A(xa, ya) and B(xb, yb) as reference points.
1503
1504I<type> controls how the reference points are used:
1505
1506=over
1507
1508=item i_ft_linear
1509
1510linear, where A is 0 and B is 1.
1511
1512=item i_ft_bilinear
1513
1514linear in both directions from A.
1515
1516=item i_ft_radial
1517
1518circular, where A is the centre of the fill, and B is a point
1519on the radius.
1520
1521=item i_ft_radial_square
1522
1523where A is the centre of the fill and B is the centre of
1524one side of the square.
1525
1526=item i_ft_revolution
1527
1528where A is the centre of the fill and B defines the 0/1.0
1529angle of the fill.
1530
1531=item i_ft_conical
1532
1533similar to i_ft_revolution, except that the revolution goes in both
1534directions
1535
1536=back
1537
1538I<repeat> can be one of:
1539
1540=over
1541
1542=item i_fr_none
1543
1544values < 0 are treated as zero, values > 1 are treated as 1.
1545
1546=item i_fr_sawtooth
1547
1548negative values are treated as 0, positive values are modulo 1.0
1549
1550=item i_fr_triangle
1551
1552negative values are treated as zero, if (int)value is odd then the value is treated as 1-(value
1553mod 1.0), otherwise the same as for sawtooth.
1554
1555=item i_fr_saw_both
1556
1557like i_fr_sawtooth, except that the sawtooth pattern repeats into
1558negative values.
1559
1560=item i_fr_tri_both
1561
1562Like i_fr_triangle, except that negative values are handled as their
1563absolute values.
1564
1565=back
1566
1567If combine is non-zero then non-opaque values are combined with the
1568underlying color.
1569
1570I<super_sample> controls super sampling, if any. At some point I'll
1571probably add a adaptive super-sampler. Current possible values are:
1572
1573=over
1574
1575=item i_fts_none
1576
1577No super-sampling is done.
1578
1579=item i_fts_grid
1580
1581A square grid of points withing the pixel are sampled.
1582
1583=item i_fts_random
1584
1585Random points within the pixel are sampled.
1586
1587=item i_fts_circle
1588
1589Points on the radius of a circle are sampled. This produces fairly
1590good results, but is fairly slow since sin() and cos() are evaluated
1591for each point.
1592
1593=back
1594
1595I<ssample_param> is intended to be roughly the number of points
1596sampled within the pixel.
1597
1598I<count> and I<segs> define the segments of the fill.
1599
1600=cut
1601
1602*/
1603
1604void
1605i_fountain(i_img *im, double xa, double ya, double xb, double yb,
1606 i_fountain_type type, i_fountain_repeat repeat,
1607 int combine, int super_sample, double ssample_param,
1608 int count, i_fountain_seg *segs) {
1609 struct fount_state state;
6607600c
TC
1610 int x, y;
1611 i_fcolor *line = mymalloc(sizeof(i_fcolor) * im->xsize);
efdc2568 1612 i_fcolor *work = NULL;
290bdf77 1613
f1ac5027 1614 i_fountain_seg *my_segs;
efdc2568
TC
1615 i_fill_combine_f combine_func = NULL;
1616 i_fill_combinef_f combinef_func = NULL;
1617
1618 i_get_combine(combine, &combine_func, &combinef_func);
1619 if (combinef_func)
1620 work = mymalloc(sizeof(i_fcolor) * im->xsize);
f1ac5027
TC
1621
1622 fount_init_state(&state, xa, ya, xb, yb, type, repeat, combine,
1623 super_sample, ssample_param, count, segs);
1624 my_segs = state.segs;
1625
1626 for (y = 0; y < im->ysize; ++y) {
1627 i_glinf(im, 0, im->xsize, y, line);
1628 for (x = 0; x < im->xsize; ++x) {
1629 i_fcolor c;
1630 int got_one;
f1ac5027
TC
1631 if (super_sample == i_fts_none)
1632 got_one = fount_getat(&c, x, y, &state);
1633 else
1634 got_one = state.ssfunc(&c, x, y, &state);
1635 if (got_one) {
efdc2568
TC
1636 if (combine)
1637 work[x] = c;
f1ac5027
TC
1638 else
1639 line[x] = c;
1640 }
1641 }
efdc2568
TC
1642 if (combine)
1643 combinef_func(line, work, im->channels, im->xsize);
f1ac5027
TC
1644 i_plinf(im, 0, im->xsize, y, line);
1645 }
1646 fount_finish_state(&state);
a73aeb5f 1647 if (work) myfree(work);
f1ac5027
TC
1648 myfree(line);
1649}
1650
1651typedef struct {
1652 i_fill_t base;
1653 struct fount_state state;
1654} i_fill_fountain_t;
1655
1656static void
1657fill_fountf(i_fill_t *fill, int x, int y, int width, int channels,
43c5dacb 1658 i_fcolor *data);
f1ac5027
TC
1659static void
1660fount_fill_destroy(i_fill_t *fill);
1661
1662/*
1663=item i_new_fount(xa, ya, xb, yb, type, repeat, combine, super_sample, ssample_param, count, segs)
1664
773bc121
TC
1665Creates a new general fill which fills with a fountain fill.
1666
f1ac5027
TC
1667=cut
1668*/
1669
1670i_fill_t *
1671i_new_fill_fount(double xa, double ya, double xb, double yb,
1672 i_fountain_type type, i_fountain_repeat repeat,
1673 int combine, int super_sample, double ssample_param,
1674 int count, i_fountain_seg *segs) {
1675 i_fill_fountain_t *fill = mymalloc(sizeof(i_fill_fountain_t));
1676
1677 fill->base.fill_with_color = NULL;
1678 fill->base.fill_with_fcolor = fill_fountf;
1679 fill->base.destroy = fount_fill_destroy;
efdc2568
TC
1680 if (combine)
1681 i_get_combine(combine, &fill->base.combine, &fill->base.combinef);
1682 else {
1683 fill->base.combine = NULL;
1684 fill->base.combinef = NULL;
1685 }
f1ac5027
TC
1686 fount_init_state(&fill->state, xa, ya, xb, yb, type, repeat, combine,
1687 super_sample, ssample_param, count, segs);
1688
1689 return &fill->base;
1690}
1691
1692/*
1693=back
1694
1695=head1 INTERNAL FUNCTIONS
1696
1697=over
1698
1699=item fount_init_state(...)
1700
1701Used by both the fountain fill filter and the fountain fill.
1702
1703=cut
1704*/
1705
1706static void
1707fount_init_state(struct fount_state *state, double xa, double ya,
1708 double xb, double yb, i_fountain_type type,
1709 i_fountain_repeat repeat, int combine, int super_sample,
1710 double ssample_param, int count, i_fountain_seg *segs) {
6607600c
TC
1711 int i, j;
1712 i_fountain_seg *my_segs = mymalloc(sizeof(i_fountain_seg) * count);
f1ac5027 1713 /*int have_alpha = im->channels == 2 || im->channels == 4;*/
f1ac5027
TC
1714
1715 memset(state, 0, sizeof(*state));
6607600c
TC
1716 /* we keep a local copy that we can adjust for speed */
1717 for (i = 0; i < count; ++i) {
1718 i_fountain_seg *seg = my_segs + i;
1719
1720 *seg = segs[i];
4c033fd4
TC
1721 if (seg->type < 0 || seg->type >= i_fst_end)
1722 seg->type = i_fst_linear;
6607600c
TC
1723 if (seg->color < 0 || seg->color >= i_fc_end)
1724 seg->color = i_fc_direct;
1725 if (seg->color == i_fc_hue_up || seg->color == i_fc_hue_down) {
1726 /* so we don't have to translate to HSV on each request, do it here */
1727 for (j = 0; j < 2; ++j) {
1728 i_rgb_to_hsvf(seg->c+j);
1729 }
1730 if (seg->color == i_fc_hue_up) {
1731 if (seg->c[1].channel[0] <= seg->c[0].channel[0])
1732 seg->c[1].channel[0] += 1.0;
1733 }
1734 else {
1735 if (seg->c[0].channel[0] <= seg->c[0].channel[1])
1736 seg->c[0].channel[0] += 1.0;
1737 }
1738 }
1739 /*printf("start %g mid %g end %g c0(%g,%g,%g,%g) c1(%g,%g,%g,%g) type %d color %d\n",
1740 seg->start, seg->middle, seg->end, seg->c[0].channel[0],
1741 seg->c[0].channel[1], seg->c[0].channel[2], seg->c[0].channel[3],
1742 seg->c[1].channel[0], seg->c[1].channel[1], seg->c[1].channel[2],
1743 seg->c[1].channel[3], seg->type, seg->color);*/
1744
1745 }
1746
1747 /* initialize each engine */
1748 /* these are so common ... */
f1ac5027
TC
1749 state->lA = xb - xa;
1750 state->lB = yb - ya;
1751 state->AB = sqrt(state->lA * state->lA + state->lB * state->lB);
1752 state->xa = xa;
1753 state->ya = ya;
6607600c
TC
1754 switch (type) {
1755 default:
1756 type = i_ft_linear; /* make the invalid value valid */
1757 case i_ft_linear:
1758 case i_ft_bilinear:
f1ac5027
TC
1759 state->lC = ya * ya - ya * yb + xa * xa - xa * xb;
1760 state->mult = 1;
1761 state->mult = 1/linear_fount_f(xb, yb, state);
6607600c
TC
1762 break;
1763
1764 case i_ft_radial:
f1ac5027
TC
1765 state->mult = 1.0 / sqrt((double)(xb-xa)*(xb-xa)
1766 + (double)(yb-ya)*(yb-ya));
6607600c
TC
1767 break;
1768
1769 case i_ft_radial_square:
f1ac5027
TC
1770 state->cos = state->lA / state->AB;
1771 state->sin = state->lB / state->AB;
1772 state->mult = 1.0 / state->AB;
6607600c
TC
1773 break;
1774
1775 case i_ft_revolution:
f1ac5027
TC
1776 state->theta = atan2(yb-ya, xb-xa);
1777 state->mult = 1.0 / (PI * 2);
6607600c
TC
1778 break;
1779
1780 case i_ft_conical:
f1ac5027
TC
1781 state->theta = atan2(yb-ya, xb-xa);
1782 state->mult = 1.0 / PI;
6607600c
TC
1783 break;
1784 }
f1ac5027 1785 state->ffunc = fount_funcs[type];
6607600c 1786 if (super_sample < 0
290bdf77 1787 || super_sample >= (int)(sizeof(fount_ssamples)/sizeof(*fount_ssamples))) {
6607600c
TC
1788 super_sample = 0;
1789 }
f1ac5027 1790 state->ssample_data = NULL;
6607600c
TC
1791 switch (super_sample) {
1792 case i_fts_grid:
1793 ssample_param = floor(0.5 + sqrt(ssample_param));
f1ac5027 1794 state->ssample_data = mymalloc(sizeof(i_fcolor) * ssample_param * ssample_param);
6607600c
TC
1795 break;
1796
1797 case i_fts_random:
1798 case i_fts_circle:
1799 ssample_param = floor(0.5+ssample_param);
f1ac5027 1800 state->ssample_data = mymalloc(sizeof(i_fcolor) * ssample_param);
6607600c
TC
1801 break;
1802 }
f1ac5027
TC
1803 state->parm = ssample_param;
1804 state->ssfunc = fount_ssamples[super_sample];
6607600c
TC
1805 if (repeat < 0 || repeat >= (sizeof(fount_repeats)/sizeof(*fount_repeats)))
1806 repeat = 0;
f1ac5027
TC
1807 state->rpfunc = fount_repeats[repeat];
1808 state->segs = my_segs;
1809 state->count = count;
6607600c
TC
1810}
1811
f1ac5027
TC
1812static void
1813fount_finish_state(struct fount_state *state) {
1814 if (state->ssample_data)
1815 myfree(state->ssample_data);
1816 myfree(state->segs);
1817}
6607600c 1818
6607600c 1819
f1ac5027 1820/*
6607600c
TC
1821=item fount_getat(out, x, y, ffunc, rpfunc, state, segs, count)
1822
1823Evaluates the fountain fill at the given point.
1824
1825This is called by both the non-super-sampling and super-sampling code.
1826
1827You might think that it would make sense to sample the fill parameter
1828instead, and combine those, but this breaks badly.
1829
1830=cut
1831*/
1832
1833static int
f1ac5027
TC
1834fount_getat(i_fcolor *out, double x, double y, struct fount_state *state) {
1835 double v = (state->rpfunc)((state->ffunc)(x, y, state));
6607600c
TC
1836 int i;
1837
1838 i = 0;
f1ac5027
TC
1839 while (i < state->count
1840 && (v < state->segs[i].start || v > state->segs[i].end)) {
6607600c
TC
1841 ++i;
1842 }
f1ac5027
TC
1843 if (i < state->count) {
1844 v = (fount_interps[state->segs[i].type])(v, state->segs+i);
1845 (fount_cinterps[state->segs[i].color])(out, v, state->segs+i);
6607600c
TC
1846 return 1;
1847 }
1848 else
1849 return 0;
1850}
1851
1852/*
1853=item linear_fount_f(x, y, state)
1854
1855Calculate the fill parameter for a linear fountain fill.
1856
1857Uses the point to line distance function, with some precalculation
1858done in i_fountain().
1859
1860=cut
1861*/
1862static double
1863linear_fount_f(double x, double y, struct fount_state *state) {
1864 return (state->lA * x + state->lB * y + state->lC) / state->AB * state->mult;
1865}
1866
1867/*
1868=item bilinear_fount_f(x, y, state)
1869
1870Calculate the fill parameter for a bi-linear fountain fill.
1871
1872=cut
1873*/
1874static double
1875bilinear_fount_f(double x, double y, struct fount_state *state) {
1876 return fabs((state->lA * x + state->lB * y + state->lC) / state->AB * state->mult);
1877}
1878
1879/*
1880=item radial_fount_f(x, y, state)
1881
1882Calculate the fill parameter for a radial fountain fill.
1883
1884Simply uses the distance function.
1885
1886=cut
1887 */
1888static double
1889radial_fount_f(double x, double y, struct fount_state *state) {
1890 return sqrt((double)(state->xa-x)*(state->xa-x)
1891 + (double)(state->ya-y)*(state->ya-y)) * state->mult;
1892}
1893
1894/*
1895=item square_fount_f(x, y, state)
1896
1897Calculate the fill parameter for a square fountain fill.
1898
1899Works by rotating the reference co-ordinate around the centre of the
1900square.
1901
1902=cut
1903*/
1904static double
1905square_fount_f(double x, double y, struct fount_state *state) {
1906 int xc, yc; /* centred on A */
1907 double xt, yt; /* rotated by theta */
1908 xc = x - state->xa;
1909 yc = y - state->ya;
1910 xt = fabs(xc * state->cos + yc * state->sin);
1911 yt = fabs(-xc * state->sin + yc * state->cos);
1912 return (xt > yt ? xt : yt) * state->mult;
1913}
1914
1915/*
1916=item revolution_fount_f(x, y, state)
1917
1918Calculates the fill parameter for the revolution fountain fill.
1919
1920=cut
1921*/
1922static double
1923revolution_fount_f(double x, double y, struct fount_state *state) {
1924 double angle = atan2(y - state->ya, x - state->xa);
1925
1926 angle -= state->theta;
1927 if (angle < 0) {
1928 angle = fmod(angle+ PI * 4, PI*2);
1929 }
1930
1931 return angle * state->mult;
1932}
1933
1934/*
1935=item conical_fount_f(x, y, state)
1936
1937Calculates the fill parameter for the conical fountain fill.
1938
1939=cut
1940*/
1941static double
1942conical_fount_f(double x, double y, struct fount_state *state) {
1943 double angle = atan2(y - state->ya, x - state->xa);
1944
1945 angle -= state->theta;
1946 if (angle < -PI)
1947 angle += PI * 2;
1948 else if (angle > PI)
1949 angle -= PI * 2;
1950
1951 return fabs(angle) * state->mult;
1952}
1953
1954/*
1955=item linear_interp(pos, seg)
1956
1957Calculates linear interpolation on the fill parameter. Breaks the
1958segment into 2 regions based in the I<middle> value.
1959
1960=cut
1961*/
1962static double
1963linear_interp(double pos, i_fountain_seg *seg) {
1964 if (pos < seg->middle) {
1965 double len = seg->middle - seg->start;
1966 if (len < EPSILON)
1967 return 0.0;
1968 else
1969 return (pos - seg->start) / len / 2;
1970 }
1971 else {
1972 double len = seg->end - seg->middle;
1973 if (len < EPSILON)
1974 return 1.0;
1975 else
1976 return 0.5 + (pos - seg->middle) / len / 2;
1977 }
1978}
1979
1980/*
1981=item sine_interp(pos, seg)
1982
1983Calculates sine function interpolation on the fill parameter.
1984
1985=cut
1986*/
1987static double
1988sine_interp(double pos, i_fountain_seg *seg) {
1989 /* I wonder if there's a simple way to smooth the transition for this */
1990 double work = linear_interp(pos, seg);
1991
1992 return (1-cos(work * PI))/2;
1993}
1994
1995/*
1996=item sphereup_interp(pos, seg)
1997
1998Calculates spherical interpolation on the fill parameter, with the cusp
1999at the low-end.
2000
2001=cut
2002*/
2003static double
2004sphereup_interp(double pos, i_fountain_seg *seg) {
2005 double work = linear_interp(pos, seg);
2006
2007 return sqrt(1.0 - (1-work) * (1-work));
2008}
2009
2010/*
2011=item spheredown_interp(pos, seg)
2012
2013Calculates spherical interpolation on the fill parameter, with the cusp
2014at the high-end.
2015
2016=cut
2017*/
2018static double
2019spheredown_interp(double pos, i_fountain_seg *seg) {
2020 double work = linear_interp(pos, seg);
2021
2022 return 1-sqrt(1.0 - work * work);
2023}
2024
2025/*
2026=item direct_cinterp(out, pos, seg)
2027
2028Calculates the fountain color based on direct scaling of the channels
2029of the color channels.
2030
2031=cut
2032*/
2033static void
2034direct_cinterp(i_fcolor *out, double pos, i_fountain_seg *seg) {
2035 int ch;
2036 for (ch = 0; ch < MAXCHANNELS; ++ch) {
2037 out->channel[ch] = seg->c[0].channel[ch] * (1 - pos)
2038 + seg->c[1].channel[ch] * pos;
2039 }
2040}
2041
2042/*
2043=item hue_up_cinterp(put, pos, seg)
2044
2045Calculates the fountain color based on scaling a HSV value. The hue
2046increases as the fill parameter increases.
2047
2048=cut
2049*/
2050static void
2051hue_up_cinterp(i_fcolor *out, double pos, i_fountain_seg *seg) {
2052 int ch;
2053 for (ch = 0; ch < MAXCHANNELS; ++ch) {
2054 out->channel[ch] = seg->c[0].channel[ch] * (1 - pos)
2055 + seg->c[1].channel[ch] * pos;
2056 }
2057 i_hsv_to_rgbf(out);
2058}
2059
2060/*
2061=item hue_down_cinterp(put, pos, seg)
2062
2063Calculates the fountain color based on scaling a HSV value. The hue
2064decreases as the fill parameter increases.
2065
2066=cut
2067*/
2068static void
2069hue_down_cinterp(i_fcolor *out, double pos, i_fountain_seg *seg) {
2070 int ch;
2071 for (ch = 0; ch < MAXCHANNELS; ++ch) {
2072 out->channel[ch] = seg->c[0].channel[ch] * (1 - pos)
2073 + seg->c[1].channel[ch] * pos;
2074 }
2075 i_hsv_to_rgbf(out);
2076}
2077
2078/*
2079=item simple_ssample(out, parm, x, y, state, ffunc, rpfunc, segs, count)
2080
2081Simple grid-based super-sampling.
2082
2083=cut
2084*/
2085static int
f1ac5027 2086simple_ssample(i_fcolor *out, double x, double y, struct fount_state *state) {
6607600c
TC
2087 i_fcolor *work = state->ssample_data;
2088 int dx, dy;
f1ac5027 2089 int grid = state->parm;
6607600c
TC
2090 double base = -0.5 + 0.5 / grid;
2091 double step = 1.0 / grid;
2092 int ch, i;
2093 int samp_count = 0;
2094
2095 for (dx = 0; dx < grid; ++dx) {
2096 for (dy = 0; dy < grid; ++dy) {
2097 if (fount_getat(work+samp_count, x + base + step * dx,
f1ac5027 2098 y + base + step * dy, state)) {
6607600c
TC
2099 ++samp_count;
2100 }
2101 }
2102 }
2103 for (ch = 0; ch < MAXCHANNELS; ++ch) {
2104 out->channel[ch] = 0;
2105 for (i = 0; i < samp_count; ++i) {
2106 out->channel[ch] += work[i].channel[ch];
2107 }
2108 /* we divide by 4 rather than samp_count since if there's only one valid
2109 sample it should be mostly transparent */
2110 out->channel[ch] /= grid * grid;
2111 }
2112 return samp_count;
2113}
2114
2115/*
2116=item random_ssample(out, parm, x, y, state, ffunc, rpfunc, segs, count)
2117
2118Random super-sampling.
2119
2120=cut
2121*/
2122static int
f1ac5027
TC
2123random_ssample(i_fcolor *out, double x, double y,
2124 struct fount_state *state) {
6607600c
TC
2125 i_fcolor *work = state->ssample_data;
2126 int i, ch;
f1ac5027 2127 int maxsamples = state->parm;
6607600c
TC
2128 double rand_scale = 1.0 / RAND_MAX;
2129 int samp_count = 0;
2130 for (i = 0; i < maxsamples; ++i) {
2131 if (fount_getat(work+samp_count, x - 0.5 + rand() * rand_scale,
f1ac5027 2132 y - 0.5 + rand() * rand_scale, state)) {
6607600c
TC
2133 ++samp_count;
2134 }
2135 }
2136 for (ch = 0; ch < MAXCHANNELS; ++ch) {
2137 out->channel[ch] = 0;
2138 for (i = 0; i < samp_count; ++i) {
2139 out->channel[ch] += work[i].channel[ch];
2140 }
2141 /* we divide by maxsamples rather than samp_count since if there's
2142 only one valid sample it should be mostly transparent */
2143 out->channel[ch] /= maxsamples;
2144 }
2145 return samp_count;
2146}
2147
2148/*
2149=item circle_ssample(out, parm, x, y, state, ffunc, rpfunc, segs, count)
2150
2151Super-sampling around the circumference of a circle.
2152
2153I considered saving the sin()/cos() values and transforming step-size
2154around the circle, but that's inaccurate, though it may not matter
2155much.
2156
2157=cut
2158 */
2159static int
f1ac5027
TC
2160circle_ssample(i_fcolor *out, double x, double y,
2161 struct fount_state *state) {
6607600c
TC
2162 i_fcolor *work = state->ssample_data;
2163 int i, ch;
f1ac5027 2164 int maxsamples = state->parm;
6607600c
TC
2165 double angle = 2 * PI / maxsamples;
2166 double radius = 0.3; /* semi-random */
2167 int samp_count = 0;
2168 for (i = 0; i < maxsamples; ++i) {
2169 if (fount_getat(work+samp_count, x + radius * cos(angle * i),
f1ac5027 2170 y + radius * sin(angle * i), state)) {
6607600c
TC
2171 ++samp_count;
2172 }
2173 }
2174 for (ch = 0; ch < MAXCHANNELS; ++ch) {
2175 out->channel[ch] = 0;
2176 for (i = 0; i < samp_count; ++i) {
2177 out->channel[ch] += work[i].channel[ch];
2178 }
2179 /* we divide by maxsamples rather than samp_count since if there's
2180 only one valid sample it should be mostly transparent */
2181 out->channel[ch] /= maxsamples;
2182 }
2183 return samp_count;
2184}
2185
2186/*
2187=item fount_r_none(v)
2188
2189Implements no repeats. Simply clamps the fill value.
2190
2191=cut
2192*/
2193static double
2194fount_r_none(double v) {
2195 return v < 0 ? 0 : v > 1 ? 1 : v;
2196}
2197
2198/*
2199=item fount_r_sawtooth(v)
2200
2201Implements sawtooth repeats. Clamps negative values and uses fmod()
2202on others.
2203
2204=cut
2205*/
2206static double
2207fount_r_sawtooth(double v) {
2208 return v < 0 ? 0 : fmod(v, 1.0);
2209}
2210
2211/*
2212=item fount_r_triangle(v)
2213
2214Implements triangle repeats. Clamps negative values, uses fmod to get
2215a range 0 through 2 and then adjusts values > 1.
2216
2217=cut
2218*/
2219static double
2220fount_r_triangle(double v) {
2221 if (v < 0)
2222 return 0;
2223 else {
2224 v = fmod(v, 2.0);
2225 return v > 1.0 ? 2.0 - v : v;
2226 }
2227}
2228
2229/*
2230=item fount_r_saw_both(v)
2231
2232Implements sawtooth repeats in the both postive and negative directions.
2233
2234Adjusts the value to be postive and then just uses fmod().
2235
2236=cut
2237*/
2238static double
2239fount_r_saw_both(double v) {
2240 if (v < 0)
2241 v += 1+(int)(-v);
2242 return fmod(v, 1.0);
2243}
2244
2245/*
2246=item fount_r_tri_both(v)
2247
2248Implements triangle repeats in the both postive and negative directions.
2249
2250Uses fmod on the absolute value, and then adjusts values > 1.
2251
2252=cut
2253*/
2254static double
2255fount_r_tri_both(double v) {
2256 v = fmod(fabs(v), 2.0);
2257 return v > 1.0 ? 2.0 - v : v;
2258}
2259
f1ac5027
TC
2260/*
2261=item fill_fountf(fill, x, y, width, channels, data)
2262
2263The fill function for fountain fills.
2264
2265=cut
2266*/
2267static void
2268fill_fountf(i_fill_t *fill, int x, int y, int width, int channels,
43c5dacb 2269 i_fcolor *data) {
f1ac5027 2270 i_fill_fountain_t *f = (i_fill_fountain_t *)fill;
efdc2568 2271
43c5dacb
TC
2272 while (width--) {
2273 i_fcolor c;
2274 int got_one;
2275
2276 if (f->state.ssfunc)
2277 got_one = f->state.ssfunc(&c, x, y, &f->state);
2278 else
2279 got_one = fount_getat(&c, x, y, &f->state);
2280
2281 *data++ = c;
2282
2283 ++x;
f1ac5027
TC
2284 }
2285}
2286
2287/*
2288=item fount_fill_destroy(fill)
2289
2290=cut
2291*/
2292static void
2293fount_fill_destroy(i_fill_t *fill) {
2294 i_fill_fountain_t *f = (i_fill_fountain_t *)fill;
2295 fount_finish_state(&f->state);
2296}
2297
6607600c
TC
2298/*
2299=back
2300
2301=head1 AUTHOR
2302
2303Arnar M. Hrafnkelsson <addi@umich.edu>
2304
2305Tony Cook <tony@develop-help.com> (i_fountain())
2306
2307=head1 SEE ALSO
2308
2309Imager(3)
2310
2311=cut
2312*/