1 /*
2 * * Copyright (C) 2006-2011 Anders Brander <anders@brander.dk>,
3 * * Anders Kvist <akv@lnxbx.dk> and Klaus Post <klauspost@gmail.com>
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version 2
8 * of the License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 */
19
20 #include <rawstudio.h>
21 #include <glib.h>
22 #include <libxml/encoding.h>
23 #include <libxml/xmlwriter.h>
24 #include "application.h"
25 #include "rs-cache.h"
26 #include "rs-photo.h"
27
28 /* This will be written to XML files for making backward compatibility easier to implement */
29 #define CACHEVERSION 5
30
31 gchar *
rs_cache_get_name(const gchar * src)32 rs_cache_get_name(const gchar *src)
33 {
34 gchar *ret=NULL;
35 gchar *dotdir, *filename;
36 GString *out;
37 dotdir = rs_dotdir_get(src);
38 filename = g_path_get_basename(src);
39 if (dotdir)
40 {
41 out = g_string_new(dotdir);
42 out = g_string_append(out, G_DIR_SEPARATOR_S);
43 out = g_string_append(out, filename);
44 out = g_string_append(out, ".cache.xml");
45 ret = out->str;
46 g_string_free(out, FALSE);
47 g_free(dotdir);
48 }
49 g_free(filename);
50 return(ret);
51 }
52
53 void
rs_cache_save(RS_PHOTO * photo,const RSSettingsMask mask)54 rs_cache_save(RS_PHOTO *photo, const RSSettingsMask mask)
55 {
56 gint id;
57 xmlTextWriterPtr writer;
58 gchar *cachename;
59
60 if (!photo->filename) return;
61
62 cachename = rs_cache_get_name(photo->filename);
63 if (!cachename) return;
64 writer = xmlNewTextWriterFilename(cachename, 0); /* fixme, check for errors */
65 xmlTextWriterSetIndent(writer, 1);
66 xmlTextWriterStartDocument(writer, NULL, "ISO-8859-1", NULL);
67 xmlTextWriterStartElement(writer, BAD_CAST "rawstudio-cache");
68 xmlTextWriterWriteFormatAttribute(writer, BAD_CAST "version", "%d", CACHEVERSION);
69 xmlTextWriterWriteFormatElement(writer, BAD_CAST "priority", "%d",
70 photo->priority);
71 if (photo->exported)
72 xmlTextWriterWriteFormatElement(writer, BAD_CAST "exported", "yes");
73 xmlTextWriterWriteFormatElement(writer, BAD_CAST "orientation", "%d",
74 photo->orientation);
75 xmlTextWriterWriteFormatElement(writer, BAD_CAST "angle", "%f",
76 photo->angle);
77
78 RSDcpFile *dcp = rs_photo_get_dcp_profile(photo);
79 if (RS_IS_DCP_FILE(dcp))
80 {
81 const gchar *dcp_id = rs_dcp_get_id(RS_DCP_FILE(dcp));
82 xmlTextWriterWriteFormatElement(writer, BAD_CAST "dcp-profile", "%s",
83 dcp_id);
84 }
85
86 RSIccProfile *icc = rs_photo_get_icc_profile(photo);
87 if (RS_IS_ICC_PROFILE(icc))
88 {
89 const gchar *icc_filename;
90 g_object_get(icc, "filename", &icc_filename, NULL);
91 if (icc_filename)
92 {
93 gchar *basename = g_path_get_basename(icc_filename);
94 xmlTextWriterWriteFormatElement(writer, BAD_CAST "icc-profile", "%s",
95 basename);
96 g_free(basename);
97 }
98 }
99
100 if (photo->crop)
101 {
102 xmlTextWriterWriteFormatElement(writer, BAD_CAST "crop", "%d %d %d %d",
103 photo->crop->x1, photo->crop->y1,
104 photo->crop->x2, photo->crop->y2);
105 }
106 for(id=0;id<3&&mask>0;id++)
107 {
108 xmlTextWriterStartElement(writer, BAD_CAST "settings");
109 xmlTextWriterWriteFormatAttribute(writer, BAD_CAST "id", "%d", id);
110 rs_cache_save_settings(photo->settings[id], mask, writer);
111 xmlTextWriterEndElement(writer);
112 }
113 xmlTextWriterEndDocument(writer);
114 xmlFreeTextWriter(writer);
115 g_free(cachename);
116 return;
117 }
118
119 void
rs_cache_save_settings(RSSettings * rss,const RSSettingsMask mask,xmlTextWriterPtr writer)120 rs_cache_save_settings(RSSettings *rss, const RSSettingsMask mask, xmlTextWriterPtr writer)
121 {
122 if (mask & MASK_EXPOSURE)
123 xmlTextWriterWriteFormatElement(writer, BAD_CAST "exposure", "%f", rss->exposure);
124 if (mask & MASK_SATURATION)
125 xmlTextWriterWriteFormatElement(writer, BAD_CAST "saturation", "%f", rss->saturation);
126 if (mask & MASK_HUE)
127 xmlTextWriterWriteFormatElement(writer, BAD_CAST "hue", "%f", rss->hue);
128 if (mask & MASK_CONTRAST)
129 xmlTextWriterWriteFormatElement(writer, BAD_CAST "contrast", "%f", rss->contrast);
130 if (mask & MASK_WARMTH)
131 xmlTextWriterWriteFormatElement(writer, BAD_CAST "warmth", "%f", rss->dcp_temp);
132 if (mask & MASK_TINT)
133 xmlTextWriterWriteFormatElement(writer, BAD_CAST "tint", "%f", rss->dcp_tint);
134 if (mask & MASK_WB && rss->wb_ascii)
135 xmlTextWriterWriteFormatElement(writer, BAD_CAST "wb_ascii", "%s", rss->wb_ascii);
136 if (mask & MASK_SHARPEN)
137 xmlTextWriterWriteFormatElement(writer, BAD_CAST "sharpen", "%f", rss->sharpen);
138 if (mask & MASK_DENOISE_LUMA)
139 xmlTextWriterWriteFormatElement(writer, BAD_CAST "denoise_luma", "%f", rss->denoise_luma);
140 if (mask & MASK_DENOISE_CHROMA)
141 xmlTextWriterWriteFormatElement(writer, BAD_CAST "denoise_chroma", "%f", rss->denoise_chroma);
142 if (mask & MASK_CHANNELMIXER)
143 {
144 xmlTextWriterWriteFormatElement(writer, BAD_CAST "channelmixer_red", "%f", rss->channelmixer_red);
145 xmlTextWriterWriteFormatElement(writer, BAD_CAST "channelmixer_green", "%f", rss->channelmixer_green);
146 xmlTextWriterWriteFormatElement(writer, BAD_CAST "channelmixer_blue", "%f", rss->channelmixer_blue);
147 }
148 if (mask & MASK_TCA_KR)
149 xmlTextWriterWriteFormatElement(writer, BAD_CAST "tca_kr", "%f", rss->tca_kr);
150 if (mask & MASK_TCA_KB)
151 xmlTextWriterWriteFormatElement(writer, BAD_CAST "tca_kb", "%f", rss->tca_kb);
152 if (mask & MASK_VIGNETTING)
153 xmlTextWriterWriteFormatElement(writer, BAD_CAST "vignetting", "%f", rss->vignetting);
154 if (mask & MASK_CURVE && rss->curve_nknots > 0)
155 {
156 gint i;
157 xmlTextWriterStartElement(writer, BAD_CAST "curve");
158 xmlTextWriterWriteFormatAttribute(writer, BAD_CAST "num", "%d", rss->curve_nknots);
159 for(i=0;i<rss->curve_nknots;i++)
160 xmlTextWriterWriteFormatElement(writer, BAD_CAST "knot", "%f %f",
161 rss->curve_knots[i*2+0],
162 rss->curve_knots[i*2+1]);
163 xmlTextWriterEndElement(writer);
164 }
165 }
166
167 guint
rs_cache_load_setting(RSSettings * rss,xmlDocPtr doc,xmlNodePtr cur,gint version)168 rs_cache_load_setting(RSSettings *rss, xmlDocPtr doc, xmlNodePtr cur, gint version)
169 {
170 RSSettingsMask mask = 0;
171 xmlChar *val;
172 gfloat *target=NULL;
173 xmlNodePtr curve = NULL;
174 while(cur)
175 {
176 target = NULL;
177 if ((!xmlStrcmp(cur->name, BAD_CAST "exposure")))
178 {
179 mask |= MASK_EXPOSURE;
180 target = &rss->exposure;
181 }
182 else if ((!xmlStrcmp(cur->name, BAD_CAST "saturation")))
183 {
184 mask |= MASK_SATURATION;
185 target = &rss->saturation;
186 }
187 else if ((!xmlStrcmp(cur->name, BAD_CAST "hue")))
188 {
189 mask |= MASK_HUE;
190 target = &rss->hue;
191 }
192 else if ((!xmlStrcmp(cur->name, BAD_CAST "contrast")))
193 {
194 mask |= MASK_CONTRAST;
195 target = &rss->contrast;
196 }
197 else if ((!xmlStrcmp(cur->name, BAD_CAST "warmth")))
198 {
199 if ( version <= 4)
200 {
201 mask |= MASK_WARMTH;
202 target = &rss->warmth;
203 rss->recalc_temp = TRUE;
204 }
205 else
206 {
207 mask |= MASK_WARMTH;
208 target = &rss->dcp_temp;
209 }
210 }
211 else if ((!xmlStrcmp(cur->name, BAD_CAST "tint")))
212 {
213 if ( version <= 4)
214 {
215 mask |= MASK_TINT;
216 target = &rss->tint;
217 rss->recalc_temp = TRUE;
218 }
219 else
220 {
221 mask |= MASK_TINT;
222 target = &rss->dcp_tint;
223 }
224 }
225 else if ((!xmlStrcmp(cur->name, BAD_CAST "wb_ascii")))
226 {
227 mask |= MASK_WB;
228 val = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
229 rss->wb_ascii = g_strdup((gchar *) val);
230 xmlFree(val);
231 }
232 else if ((!xmlStrcmp(cur->name, BAD_CAST "sharpen")))
233 {
234 mask |= MASK_SHARPEN;
235 target = &rss->sharpen;
236 }
237 else if ((!xmlStrcmp(cur->name, BAD_CAST "denoise_luma")))
238 {
239 mask |= MASK_DENOISE_LUMA;
240 target = &rss->denoise_luma;
241 }
242 else if ((!xmlStrcmp(cur->name, BAD_CAST "denoise_chroma")))
243 {
244 mask |= MASK_DENOISE_CHROMA;
245 target = &rss->denoise_chroma;
246 }
247 else if ((!xmlStrcmp(cur->name, BAD_CAST "channelmixer_red")))
248 {
249 mask |= MASK_CHANNELMIXER_RED;
250 val = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
251 rss->channelmixer_red = rs_atof((gchar *) val);
252 xmlFree(val);
253
254 if (version < 4)
255 rss->channelmixer_red *= 3.0;
256 }
257 else if ((!xmlStrcmp(cur->name, BAD_CAST "channelmixer_green")))
258 {
259 mask |= MASK_CHANNELMIXER_GREEN;
260 val = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
261 rss->channelmixer_green = rs_atof((gchar *) val);
262 xmlFree(val);
263
264 if (version < 4)
265 rss->channelmixer_green *= 3.0;
266 }
267 else if ((!xmlStrcmp(cur->name, BAD_CAST "channelmixer_blue")))
268 {
269 mask |= MASK_CHANNELMIXER_BLUE;
270 val = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
271 rss->channelmixer_blue = rs_atof((gchar *) val);
272 xmlFree(val);
273
274 if (version < 4)
275 rss->channelmixer_blue *= 3.0;
276 }
277 else if ((!xmlStrcmp(cur->name, BAD_CAST "tca_kr")))
278 {
279 mask |= MASK_TCA_KR;
280 val = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
281 rss->tca_kr = rs_atof((gchar *) val);
282 xmlFree(val);
283 }
284 else if ((!xmlStrcmp(cur->name, BAD_CAST "tca_kb")))
285 {
286 mask |= MASK_TCA_KB;
287 val = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
288 rss->tca_kb = rs_atof((gchar *) val);
289 xmlFree(val);
290 }
291 else if ((!xmlStrcmp(cur->name, BAD_CAST "vignetting")))
292 {
293 mask |= MASK_VIGNETTING;
294 val = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
295 rss->vignetting = rs_atof((gchar *) val);
296 xmlFree(val);
297 }
298 else if ((!xmlStrcmp(cur->name, BAD_CAST "curve")))
299 {
300 gchar **vals;
301 gint num;
302 gfloat x,y;
303
304 val = xmlGetProp(cur, BAD_CAST "num");
305 if (val)
306 num = atoi((gchar *) val);
307 else
308 num = 0;
309
310 rss->curve_knots = g_new(gfloat, 2*num);
311 rss->curve_nknots = 0;
312 curve = cur->xmlChildrenNode;
313 while (curve && num)
314 {
315 if ((!xmlStrcmp(curve->name, BAD_CAST "knot")))
316 {
317 mask |= MASK_CURVE;
318 val = xmlNodeListGetString(doc, curve->xmlChildrenNode, 1);
319 vals = g_strsplit((gchar *)val, " ", 4);
320 if (vals[0] && vals[1])
321 {
322 x = rs_atof(vals[0]);
323 y = rs_atof(vals[1]);
324 rss->curve_knots[rss->curve_nknots*2+0] = x;
325 rss->curve_knots[rss->curve_nknots*2+1] = y;
326 rss->curve_nknots++;
327 num--;
328 }
329 g_strfreev(vals);
330 xmlFree(val);
331 }
332 curve = curve->next;
333 }
334 }
335
336 if (target)
337 {
338 val = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
339 *target = rs_atof((gchar *) val);
340 xmlFree(val);
341 }
342 cur = cur->next;
343 }
344
345 return mask;
346 }
347
348 guint
rs_cache_load(RS_PHOTO * photo)349 rs_cache_load(RS_PHOTO *photo)
350 {
351 RSSettingsMask mask = 0;
352 xmlDocPtr doc;
353 xmlNodePtr cur;
354 xmlChar *val;
355 gchar *cachename;
356 gint id;
357 gint version = 0;
358 RSSettings *settings;
359
360 cachename = rs_cache_get_name(photo->filename);
361 if (!cachename) return mask;
362 if (!g_file_test(cachename, G_FILE_TEST_IS_REGULAR)) return FALSE;
363 photo->exported = FALSE;
364 doc = xmlParseFile(cachename);
365 if(doc==NULL) return mask;
366
367 /* Return something if the file exists */
368 mask = 0x80000000;
369
370 cur = xmlDocGetRootElement(doc);
371
372 if ((!xmlStrcmp(cur->name, BAD_CAST "rawstudio-cache")))
373 {
374 val = xmlGetProp(cur, BAD_CAST "version");
375 if (val)
376 version = atoi((gchar *) val);
377 }
378
379 cur = cur->xmlChildrenNode;
380 while(cur)
381 {
382 if ((!xmlStrcmp(cur->name, BAD_CAST "settings")))
383 {
384 val = xmlGetProp(cur, BAD_CAST "id");
385 id = (val) ? atoi((gchar *) val) : 0;
386 xmlFree(val);
387 if (id>2) id=0;
388 if (id<0) id=0;
389 settings = rs_settings_new();
390 mask |= rs_cache_load_setting(settings, doc, cur->xmlChildrenNode, version);
391 rs_photo_apply_settings(photo, id, settings, MASK_ALL);
392 g_object_unref(settings);
393 }
394 else if ((!xmlStrcmp(cur->name, BAD_CAST "priority")))
395 {
396 val = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
397 if (val)
398 {
399 photo->priority = atoi((gchar *) val);
400 xmlFree(val);
401 }
402 }
403 else if ((!xmlStrcmp(cur->name, BAD_CAST "orientation")))
404 {
405 val = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
406 if (val)
407 {
408 photo->orientation = atoi((gchar *) val);
409 xmlFree(val);
410 }
411 }
412 else if ((!xmlStrcmp(cur->name, BAD_CAST "angle")))
413 {
414 val = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
415 if (val)
416 {
417 photo->angle = rs_atof((gchar *) val);
418 xmlFree(val);
419 }
420 }
421 else if ((!xmlStrcmp(cur->name, BAD_CAST "exported")))
422 {
423 val = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
424 if (val)
425 {
426 if (g_ascii_strcasecmp((gchar *) val, "yes")==0)
427 photo->exported = TRUE;
428 xmlFree(val);
429 }
430 }
431 else if ((!xmlStrcmp(cur->name, BAD_CAST "dcp-profile")))
432 {
433 val = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
434 if (val)
435 {
436 RSProfileFactory *factory = rs_profile_factory_new_default();
437 RSDcpFile *dcp = rs_profile_factory_find_from_id(factory, (gchar *) val);
438 if (dcp)
439 rs_photo_set_dcp_profile(photo, dcp);
440 xmlFree(val);
441 }
442 }
443 else if ((!xmlStrcmp(cur->name, BAD_CAST "icc-profile")))
444 {
445 val = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
446 if (val)
447 {
448 RSProfileFactory *factory = rs_profile_factory_new_default();
449 RSIccProfile *icc = rs_profile_factory_find_icc_from_filename(factory, (gchar *) val);
450 if (icc)
451 rs_photo_set_icc_profile(photo, icc);
452 xmlFree(val);
453 }
454 }
455 else if ((!xmlStrcmp(cur->name, BAD_CAST "crop")))
456 {
457 RS_RECT *crop = g_new0(RS_RECT, 1);
458 gchar **vals = NULL;
459
460 val = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
461 if (val)
462 vals = g_strsplit((gchar *)val, " ", 4);
463 if (val && vals[0])
464 {
465 crop->x1 = atoi((gchar *) vals[0]);
466 if (vals[1])
467 {
468 crop->y1 = atoi((gchar *) vals[1]);
469 if (vals[2])
470 {
471 crop->x2 = atoi((gchar *) vals[2]);
472 if (vals[3])
473 crop->y2 = atoi((gchar *) vals[3]);
474 }
475 }
476 }
477
478 /* If crop was done before demosaic was implemented, we should
479 double the dimensions */
480 if (version < 2)
481 {
482 crop->x1 *= 2;
483 crop->y1 *= 2;
484 crop->x2 *= 2;
485 crop->y2 *= 2;
486 }
487
488 rs_photo_set_crop(photo, crop);
489 g_free(crop);
490 g_strfreev(vals);
491 xmlFree(val);
492 }
493 cur = cur->next;
494 }
495
496 xmlFreeDoc(doc);
497 g_free(cachename);
498 return mask;
499 }
500
501 void
rs_cache_load_quick(const gchar * filename,gint * priority,gboolean * exported)502 rs_cache_load_quick(const gchar *filename, gint *priority, gboolean *exported)
503 {
504 xmlDocPtr doc;
505 xmlNodePtr cur;
506 xmlChar *val;
507 gchar *cachename;
508
509 if (priority) *priority = PRIO_U;
510 if (exported) *exported = FALSE;
511
512 if (!filename)
513 return;
514
515 cachename = rs_cache_get_name(filename);
516
517 if (!cachename)
518 return;
519
520 if (!g_file_test(cachename, G_FILE_TEST_IS_REGULAR))
521 {
522 g_free(cachename);
523 return;
524 }
525
526 doc = xmlParseFile(cachename);
527 g_free(cachename);
528
529 if(doc==NULL)
530 return;
531
532 cur = xmlDocGetRootElement(doc);
533
534 cur = cur->xmlChildrenNode;
535 while(cur)
536 {
537 if (priority && (!xmlStrcmp(cur->name, BAD_CAST "priority")))
538 {
539 val = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
540 *priority = atoi((gchar *) val);
541 xmlFree(val);
542 }
543 if (exported && (!xmlStrcmp(cur->name, BAD_CAST "exported")))
544 {
545 val = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
546 if (g_ascii_strcasecmp((gchar *) val, "yes")==0)
547 *exported = TRUE;
548 xmlFree(val);
549 }
550 cur = cur->next;
551 }
552
553 xmlFreeDoc(doc);
554 return;
555 }
556
557 void
rs_cache_save_flags(const gchar * filename,const guint * priority,const gboolean * exported)558 rs_cache_save_flags(const gchar *filename, const guint *priority, const gboolean *exported)
559 {
560 RS_PHOTO *photo;
561 RSSettingsMask mask;
562
563 g_assert(filename != NULL);
564
565 if (!(priority || exported)) return;
566
567 /* Aquire a "fake" RS_PHOTO */
568 photo = rs_photo_new();
569 photo->filename = (gchar *) filename;
570
571 if ((mask = rs_cache_load(photo)))
572 {
573 /* If we got a cache file, save as normal */
574 if (priority)
575 photo->priority = *priority;
576 if (exported)
577 photo->exported = *exported;
578 rs_cache_save(photo, mask);
579 }
580 else
581 {
582 /* If we're creating a new file, only save what we know */
583 xmlTextWriterPtr writer;
584 gchar *cachename = rs_cache_get_name(photo->filename);
585
586 if (cachename)
587 {
588 writer = xmlNewTextWriterFilename(cachename, 0); /* fixme, check for errors */
589 g_free(cachename);
590
591 xmlTextWriterStartDocument(writer, NULL, "ISO-8859-1", NULL);
592 xmlTextWriterStartElement(writer, BAD_CAST "rawstudio-cache");
593
594 if (priority)
595 xmlTextWriterWriteFormatElement(writer, BAD_CAST "priority", "%d",
596 *priority);
597
598 if (exported && *exported)
599 xmlTextWriterWriteFormatElement(writer, BAD_CAST "exported", "yes");
600
601 xmlTextWriterEndDocument(writer);
602 xmlFreeTextWriter(writer);
603 }
604 }
605
606 /* Free the photo */
607 photo->filename = NULL;
608 g_object_unref(photo);
609
610 return;
611 }
612