]> git.imager.perl.org - imager.git/blob - maskimg.c
PNG re-work: collect more text tags
[imager.git] / maskimg.c
1 /*
2 =head1 NAME
3
4 maskimg.c - implements masked images/image subsets
5
6 =head1 SYNOPSIS
7
8 =head1 DESCRIPTION
9
10 =over
11 =cut
12 */
13
14 #include "imager.h"
15 #include "imageri.h"
16
17 #include <stdio.h>
18 /*
19 =item i_img_mask_ext
20
21 A pointer to this type of object is kept in the ext_data of a masked 
22 image.
23
24 =cut
25 */
26
27 typedef struct {
28   i_img *targ;
29   i_img *mask;
30   i_img_dim xbase, ybase;
31   i_sample_t *samps; /* temp space */
32 } i_img_mask_ext;
33
34 #define MASKEXT(im) ((i_img_mask_ext *)((im)->ext_data))
35
36 static void i_destroy_masked(i_img *im);
37 static int i_ppix_masked(i_img *im, i_img_dim x, i_img_dim y, const i_color *pix);
38 static int i_ppixf_masked(i_img *im, i_img_dim x, i_img_dim y, const i_fcolor *pix);
39 static i_img_dim i_plin_masked(i_img *im, i_img_dim l, i_img_dim r, i_img_dim y, const i_color *vals);
40 static i_img_dim i_plinf_masked(i_img *im, i_img_dim l, i_img_dim r, i_img_dim y, const i_fcolor *vals);
41 static int i_gpix_masked(i_img *im, i_img_dim x, i_img_dim y, i_color *pix);
42 static int i_gpixf_masked(i_img *im, i_img_dim x, i_img_dim y, i_fcolor *pix);
43 static i_img_dim i_glin_masked(i_img *im, i_img_dim l, i_img_dim r, i_img_dim y, i_color *vals);
44 static i_img_dim i_glinf_masked(i_img *im, i_img_dim l, i_img_dim r, i_img_dim y, i_fcolor *vals);
45 static i_img_dim i_gsamp_masked(i_img *im, i_img_dim l, i_img_dim r, i_img_dim y, i_sample_t *samp, 
46                           int const *chans, int chan_count);
47 static i_img_dim i_gsampf_masked(i_img *im, i_img_dim l, i_img_dim r, i_img_dim y, i_fsample_t *samp, 
48                            int const *chans, int chan_count);
49 static i_img_dim i_gpal_masked(i_img *im, i_img_dim l, i_img_dim r, i_img_dim y, i_palidx *vals);
50 static i_img_dim i_ppal_masked(i_img *im, i_img_dim l, i_img_dim r, i_img_dim y, const i_palidx *vals);
51 static i_img_dim
52 psamp_masked(i_img *im, i_img_dim l, i_img_dim r, i_img_dim y,
53                const i_sample_t *samples, const int *chans, int chan_count);
54 static i_img_dim
55 psampf_masked(i_img *im, i_img_dim l, i_img_dim r, i_img_dim y,
56                const i_fsample_t *samples, const int *chans, int chan_count);
57
58 /*
59 =item IIM_base_masked
60
61 The basic data we copy into a masked image.
62
63 =cut
64 */
65 static i_img IIM_base_masked =
66 {
67   0, /* channels set */
68   0, 0, 0, /* xsize, ysize, bytes */
69   ~0U, /* ch_mask */
70   i_8_bits, /* bits */
71   i_palette_type, /* type */
72   1, /* virtual */
73   NULL, /* idata */
74   { 0, 0, NULL }, /* tags */
75   NULL, /* ext_data */
76
77   i_ppix_masked, /* i_f_ppix */
78   i_ppixf_masked, /* i_f_ppixf */
79   i_plin_masked, /* i_f_plin */
80   i_plinf_masked, /* i_f_plinf */
81   i_gpix_masked, /* i_f_gpix */
82   i_gpixf_masked, /* i_f_gpixf */
83   i_glin_masked, /* i_f_glin */
84   i_glinf_masked, /* i_f_glinf */
85   i_gsamp_masked, /* i_f_gsamp */
86   i_gsampf_masked, /* i_f_gsampf */
87
88   i_gpal_masked, /* i_f_gpal */
89   i_ppal_masked, /* i_f_ppal */
90   i_addcolors_forward, /* i_f_addcolors */
91   i_getcolors_forward, /* i_f_getcolors */
92   i_colorcount_forward, /* i_f_colorcount */
93   i_maxcolors_forward, /* i_f_maxcolors */
94   i_findcolor_forward, /* i_f_findcolor */
95   i_setcolors_forward, /* i_f_setcolors */
96
97   i_destroy_masked, /* i_f_destroy */
98
99   NULL, /* i_f_gsamp_bits */
100   NULL, /* i_f_psamp_bits */
101
102   psamp_masked, /* i_f_psamp */
103   psampf_masked /* i_f_psampf */
104 };
105
106 /*
107 =item i_img_masked_new(i_img *targ, i_img *mask, i_img_dim xbase, i_img_dim ybase, i_img_dim w, i_img_dim h)
108
109 Create a new masked image.
110
111 The image mask is optional, in which case the image is just a view of
112 a rectangular portion of the image.
113
114 The mask only has an effect of writing to the image, the entire view
115 of the underlying image is readable.
116
117 pixel access to mimg(x,y) is translated to targ(x+xbase, y+ybase), as long 
118 as (0 <= x < w) and (0 <= y < h).
119
120 For a pixel to be writable, the pixel mask(x,y) must have non-zero in
121 it's first channel.  No scaling of the pixel is done, the channel 
122 sample is treated as boolean.
123
124 =cut
125 */
126
127 i_img *i_img_masked_new(i_img *targ, i_img *mask, i_img_dim x, i_img_dim y, i_img_dim w, i_img_dim h) {
128   i_img *im;
129   i_img_mask_ext *ext;
130
131   i_clear_error();
132   if (x >= targ->xsize || y >= targ->ysize) {
133     i_push_error(0, "subset outside of target image");
134     return NULL;
135   }
136   if (mask) {
137     if (w > mask->xsize)
138       w = mask->xsize;
139     if (h > mask->ysize)
140       h = mask->ysize;
141   }
142   if (x+w > targ->xsize)
143     w = targ->xsize - x;
144   if (y+h > targ->ysize)
145     h = targ->ysize - y;
146
147   im = mymalloc(sizeof(i_img));
148   memcpy(im, &IIM_base_masked, sizeof(i_img));
149   im->xsize = w;
150   im->ysize = h;
151   im->channels = targ->channels;
152   im->bits = targ->bits;
153   im->type = targ->type;
154   ext = mymalloc(sizeof(*ext));
155   ext->targ = targ;
156   ext->mask = mask;
157   ext->xbase = x;
158   ext->ybase = y;
159   ext->samps = mymalloc(sizeof(i_sample_t) * im->xsize);
160   im->ext_data = ext;
161
162   return im;
163 }
164
165 /*
166 =item i_destroy_masked(i_img *im)
167
168 The destruction handler for masked images.
169
170 Releases the ext_data.
171
172 Internal function.
173
174 =cut
175 */
176
177 static void i_destroy_masked(i_img *im) {
178   myfree(MASKEXT(im)->samps);
179   myfree(im->ext_data);
180 }
181
182 /*
183 =item i_ppix_masked(i_img *im, i_img_dim x, i_img_dim y, const i_color *pix)
184
185 Write a pixel to a masked image.
186
187 Internal function.
188
189 =cut
190 */
191 static int i_ppix_masked(i_img *im, i_img_dim x, i_img_dim y, const i_color *pix) {
192   i_img_mask_ext *ext = MASKEXT(im);
193   int result;
194
195   if (x < 0 || x >= im->xsize || y < 0 || y >= im->ysize)
196     return -1;
197   if (ext->mask) {
198     i_sample_t samp;
199     
200     if (i_gsamp(ext->mask, x, x+1, y, &samp, NULL, 1) && !samp)
201       return 0; /* pretend it was good */
202   }
203   result = i_ppix(ext->targ, x + ext->xbase, y + ext->ybase, pix);
204   im->type = ext->targ->type;
205   return result;
206 }
207
208 /*
209 =item i_ppixf_masked(i_img *im, i_img_dim x, i_img_dim y, const i_fcolor *pix)
210
211 Write a pixel to a masked image.
212
213 Internal function.
214
215 =cut
216 */
217 static int i_ppixf_masked(i_img *im, i_img_dim x, i_img_dim y, const i_fcolor *pix) {
218   i_img_mask_ext *ext = MASKEXT(im);
219   int result;
220
221   if (x < 0 || x >= im->xsize || y < 0 || y >= im->ysize)
222     return -1;
223   if (ext->mask) {
224     i_sample_t samp;
225     
226     if (i_gsamp(ext->mask, x, x+1, y, &samp, NULL, 1) && !samp)
227       return 0; /* pretend it was good */
228   }
229   result = i_ppixf(ext->targ, x + ext->xbase, y + ext->ybase, pix);
230   im->type = ext->targ->type;
231   return result;
232 }
233
234 /*
235 =item i_plin_masked(i_img *im, i_img_dim l, i_img_dim r, i_img_dim y, const i_color *vals)
236
237 Write a row of data to a masked image.
238
239 Internal function.
240
241 =cut
242 */
243 static i_img_dim i_plin_masked(i_img *im, i_img_dim l, i_img_dim r, i_img_dim y, const i_color *vals) {
244   i_img_mask_ext *ext = MASKEXT(im);
245
246   if (y >= 0 && y < im->ysize && l < im->xsize && l >= 0) {
247     if (r > im->xsize)
248       r = im->xsize;
249     if (ext->mask) {
250       i_img_dim i;
251       int simple = 0;
252       i_sample_t *samps = ext->samps;
253       i_img_dim w = r - l;
254
255       i_gsamp(ext->mask, l, r, y, samps, NULL, 1);
256       if (w < 10)
257         simple = 1;
258       else {
259         /* the idea is to make a fast scan to see how often the state
260            changes */
261         i_img_dim changes = 0;
262         for (i = 0; i < w-1; ++i)
263           if (!samps[i] != !samps[i+1])
264             ++changes;
265         if (changes > w/3) /* just rough */
266           simple = 1;
267       }
268       if (simple) {
269         /* we'd be calling a usually more complicated i_plin function
270            almost as often as the usually simple i_ppix(), so just
271            do a simple scan
272         */
273         for (i = 0; i < w; ++i) {
274           if (samps[i])
275             i_ppix(ext->targ, l + i + ext->xbase, y + ext->ybase, vals + i);
276         }
277         im->type = ext->targ->type;
278         return r-l;
279       }
280       else {
281         /* the scan above indicates there should be some contiguous 
282            regions, look for them and render
283         */
284         i_img_dim start;
285         i = 0;
286         while (i < w) {
287           while (i < w && !samps[i])
288             ++i;
289           start = i;
290           while (i < w && samps[i])
291             ++i;
292           if (i != start)
293             i_plin(ext->targ, l + start + ext->xbase, l + i + ext->xbase, 
294                    y + ext->ybase, vals + start);
295         }
296         im->type = ext->targ->type;
297         return w;
298       }
299     }
300     else {
301       i_img_dim result = i_plin(ext->targ, l + ext->xbase, r + ext->xbase, 
302                           y + ext->ybase, vals);
303       im->type = ext->targ->type;
304       return result;
305     }
306   }
307   else {
308     return 0;
309   }
310 }
311
312 /*
313 =item i_plinf_masked(i_img *im, i_img_dim l, i_img_dim r, i_img_dim y, const i_fcolor *vals)
314
315 Write a row of data to a masked image.
316
317 Internal function.
318
319 =cut
320 */
321 static i_img_dim i_plinf_masked(i_img *im, i_img_dim l, i_img_dim r, i_img_dim y, const i_fcolor *vals) {
322   i_img_mask_ext *ext = MASKEXT(im);
323   if (y >= 0 && y < im->ysize && l < im->xsize && l >= 0) {
324     if (r > im->xsize)
325       r = im->xsize;
326     if (ext->mask) {
327       i_img_dim i;
328       int simple = 0;
329       i_sample_t *samps = ext->samps;
330       i_img_dim w = r - l;
331
332       i_gsamp(ext->mask, l, r, y, samps, NULL, 1);
333       if (w < 10)
334         simple = 1;
335       else {
336         /* the idea is to make a fast scan to see how often the state
337            changes */
338         i_img_dim changes = 0;
339         for (i = 0; i < w-1; ++i)
340           if (!samps[i] != !samps[i+1])
341             ++changes;
342         if (changes > w/3) /* just rough */
343           simple = 1;
344       }
345       if (simple) {
346         /* we'd be calling a usually more complicated i_plin function
347            almost as often as the usually simple i_ppix(), so just
348            do a simple scan
349         */
350         for (i = 0; i < w; ++i) {
351           if (samps[i])
352             i_ppixf(ext->targ, l + i + ext->xbase, y + ext->ybase, vals+i);
353         }
354         im->type = ext->targ->type;
355         return r-l;
356       }
357       else {
358         /* the scan above indicates there should be some contiguous 
359            regions, look for them and render
360         */
361         i_img_dim start;
362         i = 0;
363         while (i < w) {
364           while (i < w && !samps[i])
365             ++i;
366           start = i;
367           while (i < w && samps[i])
368             ++i;
369           if (i != start)
370             i_plinf(ext->targ, l + start + ext->xbase, l + i + ext->xbase, 
371                     y + ext->ybase, vals + start);
372         }
373         im->type = ext->targ->type;
374         return w;
375       }
376     }
377     else {
378       i_img_dim result = i_plinf(ext->targ, l + ext->xbase, r + ext->xbase, 
379                            y + ext->ybase, vals);
380       im->type = ext->targ->type;
381       return result;
382     }
383   }
384   else {
385     return 0;
386   }
387 }
388
389 /*
390 =item i_gpix_masked(i_img *im, i_img_dim x, i_img_dim y, i_color *pix)
391
392 Read a pixel from a masked image.
393
394 Internal.
395
396 =cut
397 */
398 static int i_gpix_masked(i_img *im, i_img_dim x, i_img_dim y, i_color *pix) {
399   i_img_mask_ext *ext = MASKEXT(im);
400
401   if (x < 0 || x >= im->xsize || y < 0 || y >= im->ysize)
402     return -1;
403
404   return i_gpix(ext->targ, x + ext->xbase, y + ext->ybase, pix);
405 }
406
407 /*
408 =item i_gpixf_masked(i_img *im, i_img_dim x, i_img_dim y, i_fcolor *pix)
409
410 Read a pixel from a masked image.
411
412 Internal.
413
414 =cut
415 */
416 static int i_gpixf_masked(i_img *im, i_img_dim x, i_img_dim y, i_fcolor *pix) {
417   i_img_mask_ext *ext = MASKEXT(im);
418
419   if (x < 0 || x >= im->xsize || y < 0 || y >= im->ysize)
420     return -1;
421
422   return i_gpixf(ext->targ, x + ext->xbase, y + ext->ybase, pix);
423 }
424
425 static i_img_dim i_glin_masked(i_img *im, i_img_dim l, i_img_dim r, i_img_dim y, i_color *vals) {
426   i_img_mask_ext *ext = MASKEXT(im);
427   if (y >= 0 && y < im->ysize && l < im->xsize && l >= 0) {
428     if (r > im->xsize)
429       r = im->xsize;
430     return i_glin(ext->targ, l + ext->xbase, r + ext->xbase, 
431                   y + ext->ybase, vals);
432   }
433   else {
434     return 0;
435   }
436 }
437
438 static i_img_dim i_glinf_masked(i_img *im, i_img_dim l, i_img_dim r, i_img_dim y, i_fcolor *vals) {
439   i_img_mask_ext *ext = MASKEXT(im);
440   if (y >= 0 && y < im->ysize && l < im->xsize && l >= 0) {
441     if (r > im->xsize)
442       r = im->xsize;
443     return i_glinf(ext->targ, l + ext->xbase, r + ext->xbase, 
444                   y + ext->ybase, vals);
445   }
446   else {
447     return 0;
448   }
449 }
450
451 static i_img_dim i_gsamp_masked(i_img *im, i_img_dim l, i_img_dim r, i_img_dim y, i_sample_t *samp, 
452                           int const *chans, int chan_count) {
453   i_img_mask_ext *ext = MASKEXT(im);
454   if (y >= 0 && y < im->ysize && l < im->xsize && l >= 0) {
455     if (r > im->xsize)
456       r = im->xsize;
457     return i_gsamp(ext->targ, l + ext->xbase, r + ext->xbase, 
458                   y + ext->ybase, samp, chans, chan_count);
459   }
460   else {
461     return 0;
462   }
463 }
464
465 static i_img_dim i_gsampf_masked(i_img *im, i_img_dim l, i_img_dim r, i_img_dim y, i_fsample_t *samp, 
466                           int const *chans, int chan_count) {
467   i_img_mask_ext *ext = MASKEXT(im);
468   if (y >= 0 && y < im->ysize && l < im->xsize && l >= 0) {
469     if (r > im->xsize)
470       r = im->xsize;
471     return i_gsampf(ext->targ, l + ext->xbase, r + ext->xbase, 
472                     y + ext->ybase, samp, chans, chan_count);
473   }
474   else {
475     return 0;
476   }
477 }
478
479 static i_img_dim i_gpal_masked(i_img *im, i_img_dim l, i_img_dim r, i_img_dim y, i_palidx *vals) {
480   i_img_mask_ext *ext = MASKEXT(im);
481   if (y >= 0 && y < im->ysize && l < im->xsize && l >= 0) {
482     if (r > im->xsize)
483       r = im->xsize;
484     return i_gpal(ext->targ, l + ext->xbase, r + ext->xbase, 
485                   y + ext->ybase, vals);
486   }
487   else {
488     return 0;
489   }
490 }
491
492 static i_img_dim i_ppal_masked(i_img *im, i_img_dim l, i_img_dim r, i_img_dim y, const i_palidx *vals) {
493   i_img_mask_ext *ext = MASKEXT(im);
494   if (y >= 0 && y < im->ysize && l < im->xsize && l >= 0) {
495     if (r > im->xsize)
496       r = im->xsize;
497     if (ext->mask) {
498       i_img_dim i;
499       i_sample_t *samps = ext->samps;
500       i_img_dim w = r - l;
501       i_img_dim start;
502       
503       i_gsamp(ext->mask, l, r, y, samps, NULL, 1);
504       i = 0;
505       while (i < w) {
506         while (i < w && !samps[i])
507           ++i;
508         start = i;
509         while (i < w && samps[i])
510           ++i;
511         if (i != start)
512           i_ppal(ext->targ, l+start+ext->xbase, l+i+ext->xbase, 
513                  y+ext->ybase, vals+start);
514       }
515       return w;
516     }
517     else {
518       return i_ppal(ext->targ, l + ext->xbase, r + ext->xbase, 
519                     y + ext->ybase, vals);
520     }
521   }
522   else {
523     return 0;
524   }
525 }
526
527 /*
528 =item psamp_masked()
529
530 i_psamp() implementation for masked images.
531
532 =cut
533 */
534
535 static i_img_dim
536 psamp_masked(i_img *im, i_img_dim l, i_img_dim r, i_img_dim y,
537              const i_sample_t *samples, const int *chans, int chan_count) {
538   i_img_mask_ext *ext = MASKEXT(im);
539
540   if (y >= 0 && y < im->ysize && l < im->xsize && l >= 0) {
541     unsigned old_ch_mask = ext->targ->ch_mask;
542     i_img_dim result = 0;
543     ext->targ->ch_mask = im->ch_mask;
544     if (r > im->xsize)
545       r = im->xsize;
546     if (ext->mask) {
547       i_img_dim w = r - l;
548       i_img_dim i = 0;
549       i_img_dim x = ext->xbase + l;
550       i_img_dim work_y = y + ext->ybase;
551       i_sample_t *mask_samps = ext->samps;
552         
553       i_gsamp(ext->mask, l, r, y, mask_samps, NULL, 1);
554       /* not optimizing this yet */
555       while (i < w) {
556         if (mask_samps[i]) {
557           /* found a set mask value, try to do a run */
558           i_img_dim run_left = x;
559           const i_sample_t *run_samps = samples;
560           ++i;
561           ++x;
562           samples += chan_count;
563           
564           while (i < w && mask_samps[i]) {
565             ++i;
566             ++x;
567             samples += chan_count;
568           }
569           result += i_psamp(ext->targ, run_left, x, work_y, run_samps, chans, chan_count);
570         }
571         else {
572           ++i;
573           ++x;
574           samples += chan_count;
575           result += chan_count; /* pretend we wrote masked off pixels */
576         }
577       }
578     }
579     else {
580       result = i_psamp(ext->targ, l + ext->xbase, r + ext->xbase, 
581                        y + ext->ybase, samples, chans, chan_count);
582       im->type = ext->targ->type;
583     }
584     ext->targ->ch_mask = old_ch_mask;
585     return result;
586   }
587   else {
588     i_push_error(0, "Image position outside of image");
589     return -1;
590   }
591 }
592
593 /*
594 =item psampf_masked()
595
596 i_psampf() implementation for masked images.
597
598 =cut
599 */
600
601 static i_img_dim
602 psampf_masked(i_img *im, i_img_dim l, i_img_dim r, i_img_dim y,
603              const i_fsample_t *samples, const int *chans, int chan_count) {
604   i_img_mask_ext *ext = MASKEXT(im);
605
606   if (y >= 0 && y < im->ysize && l < im->xsize && l >= 0) {
607     i_img_dim result = 0;
608     unsigned old_ch_mask = ext->targ->ch_mask;
609     ext->targ->ch_mask = im->ch_mask;
610     if (r > im->xsize)
611       r = im->xsize;
612     if (ext->mask) {
613       i_img_dim w = r - l;
614       i_img_dim i = 0;
615       i_img_dim x = ext->xbase + l;
616       i_img_dim work_y = y + ext->ybase;
617       i_sample_t *mask_samps = ext->samps;
618         
619       i_gsamp(ext->mask, l, r, y, mask_samps, NULL, 1);
620       /* not optimizing this yet */
621       while (i < w) {
622         if (mask_samps[i]) {
623           /* found a set mask value, try to do a run */
624           i_img_dim run_left = x;
625           const i_fsample_t *run_samps = samples;
626           ++i;
627           ++x;
628           samples += chan_count;
629           
630           while (i < w && mask_samps[i]) {
631             ++i;
632             ++x;
633             samples += chan_count;
634           }
635           result += i_psampf(ext->targ, run_left, x, work_y, run_samps, chans, chan_count);
636         }
637         else {
638           ++i;
639           ++x;
640           samples += chan_count;
641           result += chan_count; /* pretend we wrote masked off pixels */
642         }
643       }
644     }
645     else {
646       result = i_psampf(ext->targ, l + ext->xbase, r + ext->xbase, 
647                         y + ext->ybase, samples,
648                                  chans, chan_count);
649       im->type = ext->targ->type;
650     }
651     ext->targ->ch_mask = old_ch_mask;
652     return result;
653   }
654   else {
655     i_push_error(0, "Image position outside of image");
656     return -1;
657   }
658 }
659
660
661 /*
662 =back
663
664 =head1 AUTHOR
665
666 Tony Cook <tony@develop-help.com>
667
668 =head1 SEE ALSO
669
670 Imager(3)
671
672 =cut
673 */