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