- the BMP reader now validates the bfOffBits value from the BMP header
[imager.git] / tags.c
1 /*
2 =head1 NAME
3
4 tags.c - functions for manipulating an images tags list
5
6 =head1 SYNOPSIS
7
8   i_img_tags tags;
9   i_tags_new(&tags);
10   i_tags_destroy(&tags);
11   i_tags_addn(&tags, "name", code, idata);
12   i_tags_add(&tags, "name", code, data, data_size, idata);
13   if (i_tags_find(&tags, name, start, &entry)) { found }
14   if (i_tags_findn(&tags, code, start, &entry)) { found }
15   i_tags_delete(&tags, index);
16   count = i_tags_delbyname(tags, name);
17   count = i_tags_delbycode(tags, code);
18
19 =head1 DESCRIPTION
20
21 Provides functions which give write access to the tags list of an image.
22
23 For read access directly access the fields (do not write any fields
24 directly).
25
26 A tag is represented by an i_img_tag structure:
27
28   typedef enum {
29     itt_double,
30     iit_text
31   } i_tag_type;
32
33   typedef struct {
34     char *name; // name of a given tag, might be NULL 
35     int code; // number of a given tag, -1 if it has no meaning 
36     char *data; // value of a given tag if it's not an int, may be NULL 
37     int size; // size of the data 
38     int idata; // value of a given tag if data is NULL 
39   } i_img_tag;
40
41
42 =over
43
44 =cut
45 */
46
47 #include "image.h"
48 #include <string.h>
49 #include <stdlib.h>
50 #include <errno.h>
51 #include <limits.h>
52
53 /* useful for debugging */
54 void i_tags_print(i_img_tags *tags);
55
56 /*
57 =item i_tags_new(i_img_tags *tags)
58
59 Initialize a tags structure.  Should not be used if the tags structure
60 has been previously used.
61
62 To destroy the contents use i_tags_destroy()
63
64 =cut
65 */
66
67 void i_tags_new(i_img_tags *tags) {
68   tags->count = tags->alloc = 0;
69   tags->tags = NULL;
70 }
71
72 /*
73 =item i_tags_addn(i_img_tags *tags, char *name, int code, int idata)
74
75 Adds a tag that has an integer value.  A simple wrapper around i_tags_add().
76
77 Duplicate tags can be added.
78
79 Returns non-zero on success.
80
81 =cut
82 */
83
84 int i_tags_addn(i_img_tags *tags, char const *name, int code, int idata) {
85   return i_tags_add(tags, name, code, NULL, 0, idata);
86 }
87
88 /*
89 =item i_tags_add(i_img_tags *tags, char *name, int code, char *data, int size, i_tag_type type, int idata)
90
91 Adds a tag to the tags list.
92
93 Duplicate tags can be added.
94
95 Returns non-zero on success.
96
97 =cut
98 */
99
100 int i_tags_add(i_img_tags *tags, char const *name, int code, char const *data, 
101                int size, int idata) {
102   i_img_tag work = {0};
103   /*printf("i_tags_add(tags %p [count %d], name %s, code %d, data %p, size %d, idata %d)\n",
104     tags, tags->count, name, code, data, size, idata);*/
105   if (tags->tags == NULL) {
106     int alloc = 10;
107     tags->tags = mymalloc(sizeof(i_img_tag) * alloc);
108     if (!tags->tags)
109       return 0;
110     tags->alloc = alloc;
111   }
112   else if (tags->count == tags->alloc) {
113     int newalloc = tags->alloc + 10;
114     void *newtags = myrealloc(tags->tags, sizeof(i_img_tag) * newalloc);
115     if (!newtags) {
116       return 0;
117     }
118     tags->tags = newtags;
119     tags->alloc = newalloc;
120   }
121   if (name) {
122     work.name = mymalloc(strlen(name)+1);
123     if (!work.name)
124       return 0;
125     strcpy(work.name, name);
126   }
127   if (data) {
128     if (size == -1)
129       size = strlen(data);
130     work.data = mymalloc(size+1);
131     if (!work.data) {
132       if (work.name) myfree(work.name);
133       return 0;
134     }
135     memcpy(work.data, data, size);
136     work.data[size] = '\0'; /* convenience */
137     work.size = size;
138   }
139   work.code = code;
140   work.idata = idata;
141   tags->tags[tags->count++] = work;
142
143   /*i_tags_print(tags);*/
144
145   return 1;
146 }
147
148 void i_tags_destroy(i_img_tags *tags) {
149   if (tags->tags) {
150     int i;
151     for (i = 0; i < tags->count; ++i) {
152       if (tags->tags[i].name)
153         myfree(tags->tags[i].name);
154       if (tags->tags[i].data)
155         myfree(tags->tags[i].data);
156     }
157     myfree(tags->tags);
158   }
159 }
160
161 int i_tags_find(i_img_tags *tags, char const *name, int start, int *entry) {
162   if (tags->tags) {
163     while (start < tags->count) {
164       if (tags->tags[start].name && strcmp(name, tags->tags[start].name) == 0) {
165         *entry = start;
166         return 1;
167       }
168       ++start;
169     }
170   }
171   return 0;
172 }
173
174 int i_tags_findn(i_img_tags *tags, int code, int start, int *entry) {
175   if (tags->tags) {
176     while (start < tags->count) {
177       if (tags->tags[start].code == code) {
178         *entry = start;
179         return 1;
180       }
181       ++start;
182     }
183   }
184   return 0;
185 }
186
187 int i_tags_delete(i_img_tags *tags, int entry) {
188   /*printf("i_tags_delete(tags %p [count %d], entry %d)\n",
189     tags, tags->count, entry);*/
190   if (tags->tags && entry >= 0 && entry < tags->count) {
191     i_img_tag old = tags->tags[entry];
192     memmove(tags->tags+entry, tags->tags+entry+1,
193             (tags->count-entry-1) * sizeof(i_img_tag));
194     if (old.name)
195       myfree(old.name);
196     if (old.data)
197       myfree(old.data);
198     --tags->count;
199
200     return 1;
201   }
202   return 0;
203 }
204
205 int i_tags_delbyname(i_img_tags *tags, char const *name) {
206   int count = 0;
207   int i;
208   /*printf("i_tags_delbyname(tags %p [count %d], name %s)\n",
209     tags, tags->count, name);*/
210   if (tags->tags) {
211     for (i = tags->count-1; i >= 0; --i) {
212       if (tags->tags[i].name && strcmp(name, tags->tags[i].name) == 0) {
213         ++count;
214         i_tags_delete(tags, i);
215       }
216     }
217   }
218   /*i_tags_print(tags);*/
219
220   return count;
221 }
222
223 int i_tags_delbycode(i_img_tags *tags, int code) {
224   int count = 0;
225   int i;
226   if (tags->tags) {
227     for (i = tags->count-1; i >= 0; --i) {
228       if (tags->tags[i].code == code) {
229         ++count;
230         i_tags_delete(tags, i);
231       }
232     }
233   }
234   return count;
235 }
236
237 int i_tags_get_float(i_img_tags *tags, char const *name, int code, 
238                      double *value) {
239   int index;
240   i_img_tag *entry;
241
242   if (name) {
243     if (!i_tags_find(tags, name, 0, &index))
244       return 0;
245   }
246   else {
247     if (!i_tags_findn(tags, code, 0, &index))
248       return 0;
249   }
250   entry = tags->tags+index;
251   if (entry->data)
252     *value = atof(entry->data);
253   else
254     *value = entry->idata;
255
256   return 1;
257 }
258
259 int i_tags_set_float(i_img_tags *tags, char const *name, int code, 
260                      double value) {
261   char temp[40];
262
263   sprintf(temp, "%.30g", value);
264   if (name)
265     i_tags_delbyname(tags, name);
266   else
267     i_tags_delbycode(tags, code);
268
269   return i_tags_add(tags, name, code, temp, strlen(temp), 0);
270 }
271
272 int i_tags_get_int(i_img_tags *tags, char const *name, int code, int *value) {
273   int index;
274   i_img_tag *entry;
275
276   if (name) {
277     if (!i_tags_find(tags, name, 0, &index))
278       return 0;
279   }
280   else {
281     if (!i_tags_findn(tags, code, 0, &index))
282       return 0;
283   }
284   entry = tags->tags+index;
285   if (entry->data)
286     *value = atoi(entry->data);
287   else
288     *value = entry->idata;
289
290   return 1;
291 }
292
293 static int parse_long(char *data, char **end, long *out) {
294 #if 0
295   /* I wrote this without thinking about strtol */
296   long x = 0;
297   int neg = *data == '-';
298
299   if (neg)
300     ++data;
301   if (!isdigit(*data))
302     return 0;
303   while (isdigit(*data)) {
304     /* this check doesn't guarantee we don't overflow, but it helps */
305     if (x > LONG_MAX / 10)
306       return 0; 
307     x = x * 10 + *data - '0';
308     ++data;
309   }
310   if (neg)
311     x = -x;
312
313   *end = data;
314
315   return 1;
316 #else
317   long result;
318   int savederr = errno;
319   char *myend;
320
321   errno = 0;
322   result = strtol(data, &myend, 10);
323   if ((result == LONG_MIN || result == LONG_MAX) && errno == ERANGE
324       || myend == data) {
325     return 0;
326   }
327
328   *out = result;
329   *end = myend;
330
331   return 1;
332 #endif
333 }
334
335 /* parse a comma-separated list of integers
336    returns when it has maxcount numbers, finds a non-comma after a number
337    or can't parse a number
338    if it can't parse a number after a comma, that's considered an error
339 */
340 static int parse_long_list(char *data, char **end, int maxcount, long *out) {
341   int i;
342
343   i = 0;
344   while (i < maxcount-1) {
345     if (!parse_long(data, &data, out))
346       return 0;
347     out++;
348     i++;
349     if (*data != ',')
350       return i;
351     ++data;
352   }
353   if (!parse_long(data, &data, out))
354     return 0;
355   ++i;
356   *end = data;
357   return i;
358 }
359
360 /* parse "color(red,green,blue,alpha)" */
361 static int parse_color(char *data, char **end, i_color *value) {
362   long n[4];
363   int count, i;
364   
365   if (memcmp(data, "color(", 6))
366     return 0; /* not a color */
367   data += 6;
368   count = parse_long_list(data, &data, 4, n);
369   if (count < 3)
370     return 0;
371   for (i = 0; i < count; ++i)
372     value->channel[i] = n[i];
373   if (count < 4)
374     value->channel[3] = 255;
375
376   return 1;
377 }
378
379 int i_tags_get_color(i_img_tags *tags, char const *name, int code, 
380                      i_color *value) {
381   int index;
382   i_img_tag *entry;
383   char *end;
384
385   if (name) {
386     if (!i_tags_find(tags, name, 0, &index))
387       return 0;
388   }
389   else {
390     if (!i_tags_findn(tags, code, 0, &index))
391       return 0;
392   }
393   entry = tags->tags+index;
394   if (!entry->data) 
395     return 0;
396
397   if (!parse_color(entry->data, &end, value))
398     return 0;
399   
400   /* for now we're sloppy about the end */
401
402   return 1;
403 }
404
405 int i_tags_set_color(i_img_tags *tags, char const *name, int code, 
406                      i_color const *value) {
407   char temp[80];
408
409   sprintf(temp, "color(%d,%d,%d,%d)", value->channel[0], value->channel[1],
410           value->channel[2], value->channel[3]);
411   if (name)
412     i_tags_delbyname(tags, name);
413   else
414     i_tags_delbycode(tags, code);
415
416   return i_tags_add(tags, name, code, temp, strlen(temp), 0);
417 }
418
419 int i_tags_get_string(i_img_tags *tags, char const *name, int code, 
420                       char *value, size_t value_size) {
421   int index;
422   i_img_tag *entry;
423
424   if (name) {
425     if (!i_tags_find(tags, name, 0, &index))
426       return 0;
427   }
428   else {
429     if (!i_tags_findn(tags, code, 0, &index))
430       return 0;
431   }
432   entry = tags->tags+index;
433   if (entry->data) {
434     size_t cpsize = value_size < entry->size ? value_size : entry->size;
435     memcpy(value, entry->data, cpsize);
436     if (cpsize == value_size)
437       --cpsize;
438     value[cpsize] = '\0';
439   }
440   else {
441     sprintf(value, "%d", entry->data);
442   }
443
444   return 1;
445 }
446
447 void i_tags_print(i_img_tags *tags) {
448   int i;
449   printf("Alloc %d\n", tags->alloc);
450   printf("Count %d\n", tags->count);
451   for (i = 0; i < tags->count; ++i) {
452     i_img_tag *tag = tags->tags + i;
453     printf("Tag %d\n", i);
454     if (tag->name)
455       printf(" Name : %s (%p)\n", tag->name, tag->name);
456     printf(" Code : %d\n", tag->code);
457     if (tag->data) {
458       int pos;
459       printf(" Data : %d (%p) => '", tag->size, tag->data);
460       for (pos = 0; pos < tag->size; ++pos) {
461         if (tag->data[pos] == '\\' || tag->data[pos] == '\'') {
462           putchar('\\');
463           putchar(tag->data[pos]);
464         }
465         else if (tag->data[pos] < ' ' || tag->data[pos] >= '\x7E')
466           printf("\\x%02X", tag->data[pos]);
467         else
468           putchar(tag->data[pos]);
469       }
470       printf("'\n");
471       printf(" Idata: %d\n", tag->idata);
472     }
473   }
474 }
475
476 /*
477 =back
478
479 =head1 AUTHOR
480
481 Tony Cook <tony@develop-help.com>
482
483 =head1 SEE ALSO
484
485 Imager(3)
486
487 =cut
488 */