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