merge write to gif tags updates
[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     work.data = mymalloc(size+1);
129     if (!work.data) {
130       if (work.name) myfree(work.name);
131       return 0;
132     }
133     memcpy(work.data, data, size);
134     work.data[size] = '\0'; /* convenience */
135     work.size = size;
136   }
137   work.code = code;
138   work.idata = idata;
139   tags->tags[tags->count++] = work;
140
141   /*i_tags_print(tags);*/
142
143   return 1;
144 }
145
146 void i_tags_destroy(i_img_tags *tags) {
147   if (tags->tags) {
148     int i;
149     for (i = 0; i < tags->count; ++i) {
150       if (tags->tags[i].name)
151         myfree(tags->tags[i].name);
152       if (tags->tags[i].data)
153         myfree(tags->tags[i].data);
154     }
155     myfree(tags->tags);
156   }
157 }
158
159 int i_tags_find(i_img_tags *tags, char const *name, int start, int *entry) {
160   if (tags->tags) {
161     while (start < tags->count) {
162       if (tags->tags[start].name && strcmp(name, tags->tags[start].name) == 0) {
163         *entry = start;
164         return 1;
165       }
166       ++start;
167     }
168   }
169   return 0;
170 }
171
172 int i_tags_findn(i_img_tags *tags, int code, int start, int *entry) {
173   if (tags->tags) {
174     while (start < tags->count) {
175       if (tags->tags[start].code == code) {
176         *entry = start;
177         return 1;
178       }
179       ++start;
180     }
181   }
182   return 0;
183 }
184
185 int i_tags_delete(i_img_tags *tags, int entry) {
186   /*printf("i_tags_delete(tags %p [count %d], entry %d)\n",
187     tags, tags->count, entry);*/
188   if (tags->tags && entry >= 0 && entry < tags->count) {
189     i_img_tag old = tags->tags[entry];
190     memmove(tags->tags+entry, tags->tags+entry+1,
191             (tags->count-entry-1) * sizeof(i_img_tag));
192     if (old.name)
193       myfree(old.name);
194     if (old.data)
195       myfree(old.data);
196     --tags->count;
197
198     return 1;
199   }
200   return 0;
201 }
202
203 int i_tags_delbyname(i_img_tags *tags, char const *name) {
204   int count = 0;
205   int i;
206   /*printf("i_tags_delbyname(tags %p [count %d], name %s)\n",
207     tags, tags->count, name);*/
208   if (tags->tags) {
209     for (i = tags->count-1; i >= 0; --i) {
210       if (tags->tags[i].name && strcmp(name, tags->tags[i].name) == 0) {
211         ++count;
212         i_tags_delete(tags, i);
213       }
214     }
215   }
216   /*i_tags_print(tags);*/
217
218   return count;
219 }
220
221 int i_tags_delbycode(i_img_tags *tags, int code) {
222   int count = 0;
223   int i;
224   if (tags->tags) {
225     for (i = tags->count-1; i >= 0; --i) {
226       if (tags->tags[i].code == code) {
227         ++count;
228         i_tags_delete(tags, i);
229       }
230     }
231   }
232   return count;
233 }
234
235 int i_tags_get_float(i_img_tags *tags, char const *name, int code, 
236                      double *value) {
237   int index;
238   i_img_tag *entry;
239
240   if (name) {
241     if (!i_tags_find(tags, name, 0, &index))
242       return 0;
243   }
244   else {
245     if (!i_tags_findn(tags, code, 0, &index))
246       return 0;
247   }
248   entry = tags->tags+index;
249   if (entry->data)
250     *value = atof(entry->data);
251   else
252     *value = entry->idata;
253
254   return 1;
255 }
256
257 int i_tags_set_float(i_img_tags *tags, char const *name, int code, 
258                      double value) {
259   char temp[40];
260
261   sprintf(temp, "%.30g", value);
262   if (name)
263     i_tags_delbyname(tags, name);
264   else
265     i_tags_delbycode(tags, code);
266
267   return i_tags_add(tags, name, code, temp, strlen(temp), 0);
268 }
269
270 int i_tags_get_int(i_img_tags *tags, char const *name, int code, int *value) {
271   int index;
272   i_img_tag *entry;
273
274   if (name) {
275     if (!i_tags_find(tags, name, 0, &index))
276       return 0;
277   }
278   else {
279     if (!i_tags_findn(tags, code, 0, &index))
280       return 0;
281   }
282   entry = tags->tags+index;
283   if (entry->data)
284     *value = atoi(entry->data);
285   else
286     *value = entry->idata;
287
288   return 1;
289 }
290
291 static int parse_long(char *data, char **end, long *out) {
292 #if 0
293   /* I wrote this without thinking about strtol */
294   long x = 0;
295   int neg = *data == '-';
296
297   if (neg)
298     ++data;
299   if (!isdigit(*data))
300     return 0;
301   while (isdigit(*data)) {
302     /* this check doesn't guarantee we don't overflow, but it helps */
303     if (x > LONG_MAX / 10)
304       return 0; 
305     x = x * 10 + *data - '0';
306     ++data;
307   }
308   if (neg)
309     x = -x;
310
311   *end = data;
312
313   return 1;
314 #else
315   long result;
316   int savederr = errno;
317   char *myend;
318
319   errno = 0;
320   result = strtol(data, &myend, 10);
321   if ((result == LONG_MIN || result == LONG_MAX) && errno == ERANGE
322       || myend == data) {
323     return 0;
324   }
325
326   *out = result;
327   *end = myend;
328
329   return 1;
330 #endif
331 }
332
333 /* parse a comma-separated list of integers
334    returns when it has maxcount numbers, finds a non-comma after a number
335    or can't parse a number
336    if it can't parse a number after a comma, that's considered an error
337 */
338 static int parse_long_list(char *data, char **end, int maxcount, long *out) {
339   int i;
340
341   while (i < maxcount-1) {
342     if (!parse_long(data, &data, out))
343       return 0;
344     out++;
345     i++;
346     if (*data != ',')
347       return i;
348     ++data;
349   }
350   if (!parse_long(data, &data, out))
351     return 0;
352   ++i;
353   *end = data;
354   return i;
355 }
356
357 /* parse "color(red,green,blue,alpha)" */
358 static int parse_color(char *data, char **end, i_color *value) {
359   long n[4];
360   int count, i;
361   
362   if (memcmp(data, "color(", 6))
363     return 0; /* not a color */
364   data += 6;
365   count = parse_long_list(data, &data, 4, n);
366   if (count < 3)
367     return 0;
368   for (i = 0; i < count; ++i)
369     value->channel[i] = n[i];
370   if (count < 4)
371     value->channel[3] = 255;
372
373   return 1;
374 }
375
376 int i_tags_get_color(i_img_tags *tags, char const *name, int code, 
377                      i_color *value) {
378   int index;
379   i_img_tag *entry;
380   char *end;
381
382   if (name) {
383     if (!i_tags_find(tags, name, 0, &index))
384       return 0;
385   }
386   else {
387     if (!i_tags_findn(tags, code, 0, &index))
388       return 0;
389   }
390   entry = tags->tags+index;
391   if (!entry->data) 
392     return 0;
393
394   if (!parse_color(entry->data, &end, value))
395     return 0;
396   
397   /* for now we're sloppy about the end */
398
399   return 1;
400 }
401
402 int i_tags_set_color(i_img_tags *tags, char const *name, int code, 
403                      i_color const *value) {
404   char temp[80];
405
406   sprintf(temp, "color(%d,%d,%d,%d)", value->channel[0], value->channel[1],
407           value->channel[2], value->channel[3]);
408   if (name)
409     i_tags_delbyname(tags, name);
410   else
411     i_tags_delbycode(tags, code);
412
413   return i_tags_add(tags, name, code, temp, strlen(temp), 0);
414 }
415
416 int i_tags_get_string(i_img_tags *tags, char const *name, int code, 
417                       char *value, size_t value_size) {
418   int index;
419   i_img_tag *entry;
420
421   if (name) {
422     if (!i_tags_find(tags, name, 0, &index))
423       return 0;
424   }
425   else {
426     if (!i_tags_findn(tags, code, 0, &index))
427       return 0;
428   }
429   entry = tags->tags+index;
430   if (entry->data) {
431     size_t cpsize = value_size < entry->size ? value_size : entry->size;
432     memcpy(value, entry->data, cpsize);
433     if (cpsize == value_size)
434       --cpsize;
435     value[cpsize] = '\0';
436   }
437   else {
438     sprintf(value, "%d", entry->data);
439   }
440
441   return 1;
442 }
443
444 void i_tags_print(i_img_tags *tags) {
445   int i;
446   printf("Alloc %d\n", tags->alloc);
447   printf("Count %d\n", tags->count);
448   for (i = 0; i < tags->count; ++i) {
449     i_img_tag *tag = tags->tags + i;
450     printf("Tag %d\n", i);
451     if (tag->name)
452       printf(" Name : %s (%p)\n", tag->name, tag->name);
453     printf(" Code : %d\n", tag->code);
454     if (tag->data) {
455       int pos;
456       printf(" Data : %d (%p) => '", tag->size, tag->data);
457       for (pos = 0; pos < tag->size; ++pos) {
458         if (tag->data[pos] == '\\' || tag->data[pos] == '\'') {
459           putchar('\\');
460           putchar(tag->data[pos]);
461         }
462         else if (tag->data[pos] < ' ' || tag->data[pos] >= '\x7E')
463           printf("\\x%02X", tag->data[pos]);
464         else
465           putchar(tag->data[pos]);
466       }
467       printf("'\n");
468       printf(" Idata: %d\n", tag->idata);
469     }
470   }
471 }
472
473 /*
474 =back
475
476 =head1 AUTHOR
477
478 Tony Cook <tony@develop-help.com>
479
480 =head1 SEE ALSO
481
482 Imager(3)
483
484 =cut
485 */