1 /* GIMP - The GNU Image Manipulation Program
2 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
3 * PNM reading and writing code Copyright (C) 1996 Erik Nygren
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (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, see <https://www.gnu.org/licenses/>.
17 */
18
19 /*
20 * The dicom reading and writing code was written from scratch
21 * by Dov Grobgeld. (dov.grobgeld@gmail.com).
22 */
23
24 #include "config.h"
25
26 #include <errno.h>
27 #include <string.h>
28 #include <time.h>
29
30 #include <glib/gstdio.h>
31
32 #include <libgimp/gimp.h>
33 #include <libgimp/gimpui.h>
34
35 #include "libgimp/stdplugins-intl.h"
36
37
38 #define LOAD_PROC "file-dicom-load"
39 #define SAVE_PROC "file-dicom-save"
40 #define PLUG_IN_BINARY "file-dicom"
41 #define PLUG_IN_ROLE "gimp-file-dicom"
42
43
44 /* A lot of Dicom images are wrongly encoded. By guessing the endian
45 * we can get around this problem.
46 */
47 #define GUESS_ENDIAN 1
48
49 /* Declare local data types */
50 typedef struct _DicomInfo
51 {
52 guint width, height; /* The size of the image */
53 gint maxval; /* For 16 and 24 bit image files, the max
54 value which we need to normalize to */
55 gint samples_per_pixel; /* Number of image planes (0 for pbm) */
56 gint bpp;
57 gint bits_stored;
58 gint high_bit;
59 gboolean is_signed;
60 gboolean planar;
61 } DicomInfo;
62
63 /* Local function prototypes */
64 static void query (void);
65 static void run (const gchar *name,
66 gint nparams,
67 const GimpParam *param,
68 gint *nreturn_vals,
69 GimpParam **return_vals);
70 static gint32 load_image (const gchar *filename,
71 GError **error);
72 static gboolean save_image (const gchar *filename,
73 gint32 image_ID,
74 gint32 drawable_ID,
75 GError **error);
76 static void dicom_loader (guint8 *pix_buf,
77 DicomInfo *info,
78 GeglBuffer *buffer);
79 static void guess_and_set_endian2 (guint16 *buf16,
80 gint length);
81 static void toggle_endian2 (guint16 *buf16,
82 gint length);
83 static void add_tag_pointer (GByteArray *group_stream,
84 gint group,
85 gint element,
86 const gchar *value_rep,
87 const guint8 *data,
88 gint length);
89 static GSList * dicom_add_tags (FILE *DICOM,
90 GByteArray *group_stream,
91 GSList *elements);
92 static gboolean write_group_to_file (FILE *DICOM,
93 gint group,
94 GByteArray *group_stream);
95
96
97 const GimpPlugInInfo PLUG_IN_INFO =
98 {
99 NULL, /* init_proc */
100 NULL, /* quit_proc */
101 query, /* query_proc */
102 run, /* run_proc */
103 };
104
MAIN()105 MAIN ()
106
107 static void
108 query (void)
109 {
110 static const GimpParamDef load_args[] =
111 {
112 { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" },
113 { GIMP_PDB_STRING, "filename", "The name of the file to load" },
114 { GIMP_PDB_STRING, "raw-filename", "The name of the file to load" }
115 };
116 static const GimpParamDef load_return_vals[] =
117 {
118 { GIMP_PDB_IMAGE, "image", "Output image" }
119 };
120
121 static const GimpParamDef save_args[] =
122 {
123 { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" },
124 { GIMP_PDB_IMAGE, "image", "Input image" },
125 { GIMP_PDB_DRAWABLE, "drawable", "Drawable to save" },
126 { GIMP_PDB_STRING, "filename", "The name of the file to save" },
127 { GIMP_PDB_STRING, "raw-filename", "The name of the file to save" },
128 };
129
130 gimp_install_procedure (LOAD_PROC,
131 "loads files of the dicom file format",
132 "Load a file in the DICOM standard format."
133 "The standard is defined at "
134 "http://medical.nema.org/. The plug-in currently "
135 "only supports reading images with uncompressed "
136 "pixel sections.",
137 "Dov Grobgeld",
138 "Dov Grobgeld <dov@imagic.weizmann.ac.il>",
139 "2003",
140 N_("DICOM image"),
141 NULL,
142 GIMP_PLUGIN,
143 G_N_ELEMENTS (load_args),
144 G_N_ELEMENTS (load_return_vals),
145 load_args, load_return_vals);
146
147 gimp_register_file_handler_mime (LOAD_PROC, "image/x-dcm");
148 gimp_register_magic_load_handler (LOAD_PROC,
149 "dcm,dicom",
150 "",
151 "128,string,DICM"
152 );
153
154 gimp_install_procedure (SAVE_PROC,
155 "Save file in the DICOM file format",
156 "Save an image in the medical standard DICOM image "
157 "formats. The standard is defined at "
158 "http://medical.nema.org/. The file format is "
159 "defined in section 10 of the standard. The files "
160 "are saved uncompressed and the compulsory DICOM "
161 "tags are filled with default dummy values.",
162 "Dov Grobgeld",
163 "Dov Grobgeld <dov@imagic.weizmann.ac.il>",
164 "2003",
165 N_("Digital Imaging and Communications in "
166 "Medicine image"),
167 "RGB, GRAY",
168 GIMP_PLUGIN,
169 G_N_ELEMENTS (save_args), 0,
170 save_args, NULL);
171
172 gimp_register_file_handler_mime (SAVE_PROC, "image/x-dcm");
173 gimp_register_save_handler (SAVE_PROC, "dcm,dicom", "");
174 }
175
176 static void
run(const gchar * name,gint nparams,const GimpParam * param,gint * nreturn_vals,GimpParam ** return_vals)177 run (const gchar *name,
178 gint nparams,
179 const GimpParam *param,
180 gint *nreturn_vals,
181 GimpParam **return_vals)
182 {
183 static GimpParam values[2];
184 GimpRunMode run_mode;
185 GimpPDBStatusType status = GIMP_PDB_SUCCESS;
186 gint32 image_ID;
187 gint32 drawable_ID;
188 GimpExportReturn export = GIMP_EXPORT_CANCEL;
189 GError *error = NULL;
190
191 INIT_I18N ();
192 gegl_init (NULL, NULL);
193
194 run_mode = param[0].data.d_int32;
195
196 *nreturn_vals = 1;
197 *return_vals = values;
198 values[0].type = GIMP_PDB_STATUS;
199 values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR;
200
201 if (strcmp (name, LOAD_PROC) == 0)
202 {
203 image_ID = load_image (param[1].data.d_string, &error);
204
205 if (image_ID != -1)
206 {
207 *nreturn_vals = 2;
208
209 values[1].type = GIMP_PDB_IMAGE;
210 values[1].data.d_image = image_ID;
211 }
212 else
213 {
214 status = GIMP_PDB_EXECUTION_ERROR;
215
216 if (error)
217 {
218 *nreturn_vals = 2;
219 values[1].type = GIMP_PDB_STRING;
220 values[1].data.d_string = error->message;
221 }
222 }
223 }
224 else if (strcmp (name, SAVE_PROC) == 0)
225 {
226 image_ID = param[1].data.d_int32;
227 drawable_ID = param[2].data.d_int32;
228
229 switch (run_mode)
230 {
231 case GIMP_RUN_INTERACTIVE:
232 case GIMP_RUN_WITH_LAST_VALS:
233 gimp_ui_init (PLUG_IN_BINARY, FALSE);
234 export = gimp_export_image (&image_ID, &drawable_ID, "DICOM",
235 GIMP_EXPORT_CAN_HANDLE_RGB |
236 GIMP_EXPORT_CAN_HANDLE_GRAY);
237
238 if (export == GIMP_EXPORT_CANCEL)
239 {
240 values[0].data.d_status = GIMP_PDB_CANCEL;
241 return;
242 }
243 break;
244
245 default:
246 break;
247 }
248
249 switch (run_mode)
250 {
251 case GIMP_RUN_INTERACTIVE:
252 break;
253
254 case GIMP_RUN_NONINTERACTIVE:
255 /* Make sure all the arguments are there! */
256 if (nparams != 5)
257 status = GIMP_PDB_CALLING_ERROR;
258 break;
259
260 case GIMP_RUN_WITH_LAST_VALS:
261 break;
262
263 default:
264 break;
265 }
266
267 if (status == GIMP_PDB_SUCCESS)
268 {
269 if (! save_image (param[3].data.d_string, image_ID, drawable_ID,
270 &error))
271 {
272 status = GIMP_PDB_EXECUTION_ERROR;
273
274 if (error)
275 {
276 *nreturn_vals = 2;
277 values[1].type = GIMP_PDB_STRING;
278 values[1].data.d_string = error->message;
279 }
280 }
281 }
282
283 if (export == GIMP_EXPORT_EXPORT)
284 gimp_image_delete (image_ID);
285 }
286 else
287 {
288 status = GIMP_PDB_CALLING_ERROR;
289 }
290
291 values[0].data.d_status = status;
292 }
293
294 /**
295 * add_parasites_to_image:
296 * @data: pointer to a GimpParasite to be attached to the image
297 * specified by @user_data.
298 * @user_data: pointer to the image_ID to which parasite @data should
299 * be added.
300 *
301 * Attaches parasite to image and also frees that parasite
302 **/
303 static void
add_parasites_to_image(gpointer data,gpointer user_data)304 add_parasites_to_image (gpointer data,
305 gpointer user_data)
306 {
307 GimpParasite *parasite = (GimpParasite *) data;
308 gint32 *image_ID = (gint32 *) user_data;
309
310 gimp_image_attach_parasite (*image_ID, parasite);
311 gimp_parasite_free (parasite);
312 }
313
314 static gint32
load_image(const gchar * filename,GError ** error)315 load_image (const gchar *filename,
316 GError **error)
317 {
318 gint32 volatile image_ID = -1;
319 gint32 layer_ID;
320 GeglBuffer *buffer;
321 GSList *elements = NULL;
322 FILE *DICOM;
323 gchar buf[500]; /* buffer for random things like scanning */
324 DicomInfo *dicominfo;
325 guint width = 0;
326 guint height = 0;
327 gint samples_per_pixel = 0;
328 gint bpp = 0;
329 gint bits_stored = 0;
330 gint high_bit = 0;
331 guint8 *pix_buf = NULL;
332 gboolean is_signed = FALSE;
333 guint8 in_sequence = 0;
334 gboolean implicit_encoding = FALSE;
335 gboolean big_endian = FALSE;
336
337 gimp_progress_init_printf (_("Opening '%s'"),
338 gimp_filename_to_utf8 (filename));
339
340 DICOM = g_fopen (filename, "rb");
341
342 if (! DICOM)
343 {
344 g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
345 _("Could not open '%s' for reading: %s"),
346 gimp_filename_to_utf8 (filename), g_strerror (errno));
347 return -1;
348 }
349
350 /* allocate the necessary structures */
351 dicominfo = g_new0 (DicomInfo, 1);
352
353 /* Parse the file */
354 fread (buf, 1, 128, DICOM); /* skip past buffer */
355
356 /* Check for unsupported formats */
357 if (g_ascii_strncasecmp (buf, "PAPYRUS", 7) == 0)
358 {
359 g_message ("'%s' is a PAPYRUS DICOM file.\n"
360 "This plug-in does not support this type yet.",
361 gimp_filename_to_utf8 (filename));
362 g_free (dicominfo);
363 fclose (DICOM);
364 return -1;
365 }
366
367 fread (buf, 1, 4, DICOM); /* This should be dicom */
368 if (g_ascii_strncasecmp (buf,"DICM",4) != 0)
369 {
370 g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
371 _("'%s' is not a DICOM file."),
372 gimp_filename_to_utf8 (filename));
373 g_free (dicominfo);
374 fclose (DICOM);
375 return -1;
376 }
377
378 while (!feof (DICOM))
379 {
380 guint16 group_word;
381 guint16 element_word;
382 gchar value_rep[3];
383 guint32 element_length;
384 guint16 ctx_us;
385 guint8 *value;
386 guint32 tag;
387
388 if (fread (&group_word, 1, 2, DICOM) == 0)
389 break;
390 group_word = g_ntohs (GUINT16_SWAP_LE_BE (group_word));
391
392 fread (&element_word, 1, 2, DICOM);
393 element_word = g_ntohs (GUINT16_SWAP_LE_BE (element_word));
394
395 if (group_word != 0x0002 && big_endian)
396 {
397 group_word = GUINT16_SWAP_LE_BE (group_word);
398 element_word = GUINT16_SWAP_LE_BE (element_word);
399 }
400
401 tag = (group_word << 16) | element_word;
402 fread(value_rep, 2, 1, DICOM);
403 value_rep[2] = 0;
404
405 /* Check if the value rep looks valid. There probably is a
406 better way of checking this...
407 */
408 if ((/* Always need lookup for implicit encoding */
409 tag > 0x0002ffff && implicit_encoding)
410 /* This heuristics isn't used if we are doing implicit
411 encoding according to the value representation... */
412 || ((value_rep[0] < 'A' || value_rep[0] > 'Z'
413 || value_rep[1] < 'A' || value_rep[1] > 'Z')
414
415 /* I found this in one of Ednas images. It seems like a
416 bug...
417 */
418 && !(value_rep[0] == ' ' && value_rep[1]))
419 )
420 {
421 /* Look up type from the dictionary. At the time we don't
422 support this option... */
423 gchar element_length_chars[4];
424
425 /* Store the bytes that were read */
426 element_length_chars[0] = value_rep[0];
427 element_length_chars[1] = value_rep[1];
428
429 /* Unknown value rep. It is not used right now anyhow */
430 strcpy (value_rep, "??");
431
432 /* For implicit value_values the length is always four bytes,
433 so we need to read another two. */
434 fread (&element_length_chars[2], 1, 2, DICOM);
435
436 /* Now cast to integer and insert into element_length */
437 if (big_endian && group_word != 0x0002)
438 element_length =
439 g_ntohl (*((gint *) element_length_chars));
440 else
441 element_length =
442 g_ntohl (GUINT32_SWAP_LE_BE (*((gint *) element_length_chars)));
443 }
444 /* Binary value reps are OB, OW, SQ or UN */
445 else if (strncmp (value_rep, "OB", 2) == 0
446 || strncmp (value_rep, "OW", 2) == 0
447 || strncmp (value_rep, "SQ", 2) == 0
448 || strncmp (value_rep, "UN", 2) == 0)
449 {
450 fread (&element_length, 1, 2, DICOM); /* skip two bytes */
451 fread (&element_length, 1, 4, DICOM);
452 if (big_endian && group_word != 0x0002)
453 element_length = g_ntohl (element_length);
454 else
455 element_length = g_ntohl (GUINT32_SWAP_LE_BE (element_length));
456 }
457 /* Short length */
458 else
459 {
460 guint16 el16;
461
462 fread (&el16, 1, 2, DICOM);
463 if (big_endian && group_word != 0x0002)
464 element_length = g_ntohs (el16);
465 else
466 element_length = g_ntohs (GUINT16_SWAP_LE_BE (el16));
467 }
468
469 /* Sequence of items - just ignore the delimiters... */
470 if (element_length == 0xffffffff)
471 {
472 in_sequence = 1;
473 continue;
474 }
475 /* End of Sequence tag */
476 if (tag == 0xFFFEE0DD)
477 {
478 in_sequence = 0;
479 continue;
480 }
481
482 /* Sequence of items item tag... Ignore as well */
483 if (tag == 0xFFFEE000)
484 continue;
485
486 /* Even for pixel data, we don't handle very large element
487 lengths */
488
489 if (element_length >= (G_MAXUINT - 6))
490 {
491 g_message ("'%s' seems to have an incorrect value field length.",
492 gimp_filename_to_utf8 (filename));
493 gimp_quit ();
494 }
495
496 /* Read contents. Allocate a bit more to make room for casts to int
497 below. */
498 value = g_new0 (guint8, element_length + 4);
499 fread (value, 1, element_length, DICOM);
500
501 /* ignore everything inside of a sequence */
502 if (in_sequence)
503 {
504 g_free (value);
505 continue;
506 }
507 /* Some special casts that are used below */
508 ctx_us = *(guint16 *) value;
509 if (big_endian && group_word != 0x0002)
510 ctx_us = GUINT16_SWAP_LE_BE (ctx_us);
511
512 g_debug ("group: %04x, element: %04x, length: %d",
513 group_word, element_word, element_length);
514 g_debug ("Value: %s", (char*)value);
515 /* Recognize some critical tags */
516 if (group_word == 0x0002)
517 {
518 switch (element_word)
519 {
520 case 0x0010: /* transfer syntax id */
521 if (strcmp("1.2.840.10008.1.2", (char*)value) == 0)
522 {
523 implicit_encoding = TRUE;
524 g_debug ("Transfer syntax: Implicit VR Endian: Default Transfer Syntax for DICOM.");
525 }
526 else if (strcmp("1.2.840.10008.1.2.1", (char*)value) == 0)
527 {
528 g_debug ("Transfer syntax: Explicit VR Little Endian.");
529 }
530 else if (strcmp("1.2.840.10008.1.2.1.99", (char*)value) == 0)
531 {
532 g_debug ("Transfer syntax: Deflated Explicit VR Little Endian.");
533 }
534 else if (strcmp("1.2.840.10008.1.2.2", (char*)value) == 0)
535 {
536 /* This Transfer Syntax was retired in 2006. For the most recent description of it, see PS3.5 2016b */
537 big_endian = TRUE;
538 g_debug ("Transfer syntax: Deprecated Explicit VR Big Endian.");
539 }
540 else
541 {
542 g_debug ("Transfer syntax %s is not supported by GIMP.", (gchar *) value);
543 g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
544 _("Transfer syntax %s is not supported by GIMP."),
545 (gchar *) value);
546 g_free (dicominfo);
547 fclose (DICOM);
548 return -1;
549 }
550 break;
551 }
552 }
553 else if (group_word == 0x0028)
554 {
555 switch (element_word)
556 {
557 case 0x0002: /* samples per pixel */
558 samples_per_pixel = ctx_us;
559 g_debug ("spp: %d", samples_per_pixel);
560 break;
561 case 0x0004: /* photometric interpretation */
562 g_debug ("photometric interpretation: %s", (char*) value);
563 break;
564 case 0x0006: /* planar configuration */
565 g_debug ("planar configuration: %u", ctx_us);
566 dicominfo->planar = (ctx_us == 1);
567 break;
568 case 0x0008: /* number of frames */
569 g_debug ("number of frames: %d", ctx_us);
570 break;
571 case 0x0010: /* rows */
572 height = ctx_us;
573 g_debug ("height: %d", height);
574 break;
575 case 0x0011: /* columns */
576 width = ctx_us;
577 g_debug ("width: %d", width);
578 break;
579 case 0x0100: /* bits allocated */
580 bpp = ctx_us;
581 g_debug ("bpp: %d", bpp);
582 break;
583 case 0x0101: /* bits stored */
584 bits_stored = ctx_us;
585 g_debug ("bits stored: %d", bits_stored);
586 break;
587 case 0x0102: /* high bit */
588 high_bit = ctx_us;
589 g_debug ("high bit: %d", high_bit);
590 break;
591 case 0x0103: /* is pixel representation signed? */
592 is_signed = (ctx_us == 0) ? FALSE : TRUE;
593 g_debug ("is signed: %d", ctx_us);
594 break;
595 }
596 }
597
598 /* Pixel data */
599 if (group_word == 0x7fe0 && element_word == 0x0010)
600 {
601 pix_buf = value;
602 }
603 else
604 {
605 /* save this element to a parasite for later writing */
606 GimpParasite *parasite;
607 gchar pname[255];
608
609 /* all elements are retrievable using gimp_get_parasite_list() */
610 g_snprintf (pname, sizeof (pname),
611 "dcm/%04x-%04x-%s", group_word, element_word, value_rep);
612 if ((parasite = gimp_parasite_new (pname,
613 GIMP_PARASITE_PERSISTENT,
614 element_length, value)))
615 {
616 /*
617 * at this point, the image has not yet been created, so
618 * image_ID is not valid. keep the parasite around
619 * until we're able to attach it.
620 */
621
622 /* add to our list of parasites to be added (prepending
623 * for speed. we'll reverse it later)
624 */
625 elements = g_slist_prepend (elements, parasite);
626 }
627
628 g_free (value);
629 }
630 }
631
632 if ((bpp != 8) && (bpp != 16))
633 {
634 g_message ("'%s' has a bpp of %d which GIMP cannot handle.",
635 gimp_filename_to_utf8 (filename), bpp);
636 gimp_quit ();
637 }
638
639 if ((width > GIMP_MAX_IMAGE_SIZE) || (height > GIMP_MAX_IMAGE_SIZE))
640 {
641 g_message ("'%s' has a larger image size (%d x %d) than GIMP can handle.",
642 gimp_filename_to_utf8 (filename), width, height);
643 gimp_quit ();
644 }
645
646 if (samples_per_pixel > 3)
647 {
648 g_message ("'%s' has samples per pixel of %d which GIMP cannot handle.",
649 gimp_filename_to_utf8 (filename), samples_per_pixel);
650 gimp_quit ();
651 }
652
653 dicominfo->width = width;
654 dicominfo->height = height;
655 dicominfo->bpp = bpp;
656
657 dicominfo->bits_stored = bits_stored;
658 dicominfo->high_bit = high_bit;
659 dicominfo->is_signed = is_signed;
660 dicominfo->samples_per_pixel = samples_per_pixel;
661 dicominfo->maxval = -1; /* External normalization factor - not used yet */
662
663 /* Create a new image of the proper size and associate the filename with it.
664 */
665 image_ID = gimp_image_new (dicominfo->width, dicominfo->height,
666 (dicominfo->samples_per_pixel >= 3 ?
667 GIMP_RGB : GIMP_GRAY));
668 gimp_image_set_filename (image_ID, filename);
669
670 layer_ID = gimp_layer_new (image_ID, _("Background"),
671 dicominfo->width, dicominfo->height,
672 (dicominfo->samples_per_pixel >= 3 ?
673 GIMP_RGB_IMAGE : GIMP_GRAY_IMAGE),
674 100,
675 gimp_image_get_default_new_layer_mode (image_ID));
676 gimp_image_insert_layer (image_ID, layer_ID, -1, 0);
677
678 buffer = gimp_drawable_get_buffer (layer_ID);
679
680 #if GUESS_ENDIAN
681 if (bpp == 16)
682 guess_and_set_endian2 ((guint16 *) pix_buf, width * height);
683 #endif
684
685 dicom_loader (pix_buf, dicominfo, buffer);
686
687 if (elements)
688 {
689 /* flip the parasites back around into the order they were
690 * created (read from the file)
691 */
692 elements = g_slist_reverse (elements);
693 /* and add each one to the image */
694 g_slist_foreach (elements, add_parasites_to_image, (gpointer) &image_ID);
695 g_slist_free (elements);
696 }
697
698 g_free (pix_buf);
699 g_free (dicominfo);
700
701 fclose (DICOM);
702
703 g_object_unref (buffer);
704
705 return image_ID;
706 }
707
708 static void
dicom_loader(guint8 * pix_buffer,DicomInfo * info,GeglBuffer * buffer)709 dicom_loader (guint8 *pix_buffer,
710 DicomInfo *info,
711 GeglBuffer *buffer)
712 {
713 guchar *data;
714 gint row_idx;
715 gint width = info->width;
716 gint height = info->height;
717 gint samples_per_pixel = info->samples_per_pixel;
718 guint16 *buf16 = (guint16 *) pix_buffer;
719
720 if (info->bpp == 16)
721 {
722 gulong pix_idx;
723 guint shift = info->high_bit + 1 - info->bits_stored;
724
725 /* Reorder the buffer; also shift the data so that the LSB
726 * of the pixel data is at the LSB of the 16-bit array entries
727 * (i.e., compensate for high_bit and bits_stored).
728 */
729 for (pix_idx = 0; pix_idx < width * height * samples_per_pixel; pix_idx++)
730 buf16[pix_idx] = g_htons (buf16[pix_idx]) >> shift;
731 }
732
733 data = g_malloc (gimp_tile_height () * width * samples_per_pixel);
734
735 for (row_idx = 0; row_idx < height; )
736 {
737 guchar *d = data;
738 gint start;
739 gint end;
740 gint scanlines;
741 gint i;
742
743 start = row_idx;
744 end = row_idx + gimp_tile_height ();
745 end = MIN (end, height);
746
747 scanlines = end - start;
748
749 for (i = 0; i < scanlines; i++)
750 {
751 if (info->bpp == 16)
752 {
753 guint16 *row_start;
754 gint col_idx;
755
756 row_start = buf16 + (row_idx + i) * width * samples_per_pixel;
757
758 for (col_idx = 0; col_idx < width * samples_per_pixel; col_idx++)
759 {
760 /* Shift it by 8 bits, or less in case bits_stored
761 * is less than bpp.
762 */
763 d[col_idx] = (guint8) (row_start[col_idx] >>
764 (info->bits_stored - 8));
765 if (info->is_signed)
766 {
767 /* If the data is negative, make it 0. Otherwise,
768 * multiply the positive value by 2, so that the
769 * positive values span between 0 and 254.
770 */
771 if (d[col_idx] > 127)
772 d[col_idx] = 0;
773 else
774 d[col_idx] <<= 1;
775 }
776 }
777 }
778 else if (info->bpp == 8)
779 {
780 if (! info->planar)
781 {
782 guint8 *row_start;
783 gint col_idx;
784
785 row_start = (pix_buffer +
786 (row_idx + i) * width * samples_per_pixel);
787
788 for (col_idx = 0; col_idx < width * samples_per_pixel; col_idx++)
789 {
790 /* Shift it by 0 bits, or more in case bits_stored is
791 * less than bpp.
792 */
793 d[col_idx] = row_start[col_idx] << (8 - info->bits_stored);
794
795 if (info->is_signed)
796 {
797 /* If the data is negative, make it 0. Otherwise,
798 * multiply the positive value by 2, so that the
799 * positive values span between 0 and 254.
800 */
801 if (d[col_idx] > 127)
802 d[col_idx] = 0;
803 else
804 d[col_idx] <<= 1;
805 }
806 }
807 }
808 else
809 {
810 /* planar organization of color data */
811 guint8 *row_start;
812 gint col_idx;
813 gint plane_size = width * height;
814
815 row_start = (pix_buffer + (row_idx + i) * width);
816
817 for (col_idx = 0; col_idx < width; col_idx++)
818 {
819 /* Shift it by 0 bits, or more in case bits_stored is
820 * less than bpp.
821 */
822 gint pix_idx;
823 gint src_offset = col_idx;
824
825 for (pix_idx = 0; pix_idx < samples_per_pixel; pix_idx++)
826 {
827 gint dest_idx = col_idx * samples_per_pixel + pix_idx;
828
829 d[dest_idx] = row_start[src_offset] << (8 - info->bits_stored);
830 if (info->is_signed)
831 {
832 /* If the data is negative, make it 0. Otherwise,
833 * multiply the positive value by 2, so that the
834 * positive values span between 0 and 254.
835 */
836 if (d[dest_idx] > 127)
837 d[dest_idx] = 0;
838 else
839 d[dest_idx] <<= 1;
840 }
841 src_offset += plane_size;
842 }
843 }
844 }
845 }
846
847 d += width * samples_per_pixel;
848 }
849
850 gegl_buffer_set (buffer, GEGL_RECTANGLE (0, row_idx, width, scanlines), 0,
851 NULL, data, GEGL_AUTO_ROWSTRIDE);
852
853 row_idx += scanlines;
854
855 gimp_progress_update ((gdouble) row_idx / (gdouble) height);
856 }
857
858 g_free (data);
859
860 gimp_progress_update (1.0);
861 }
862
863
864 /* Guess and set endian. Guesses the endian of a buffer by
865 * checking the maximum value of the first and the last byte
866 * in the words of the buffer. It assumes that the least
867 * significant byte has a larger maximum than the most
868 * significant byte.
869 */
870 static void
guess_and_set_endian2(guint16 * buf16,int length)871 guess_and_set_endian2 (guint16 *buf16,
872 int length)
873 {
874 guint16 *p = buf16;
875 gint max_first = -1;
876 gint max_second = -1;
877
878 while (p<buf16+length)
879 {
880 if (*(guint8*)p > max_first)
881 max_first = *(guint8*)p;
882 if (((guint8*)p)[1] > max_second)
883 max_second = ((guint8*)p)[1];
884 p++;
885 }
886
887 if ( ((max_second > max_first) && (G_BYTE_ORDER == G_LITTLE_ENDIAN))
888 || ((max_second < max_first) && (G_BYTE_ORDER == G_BIG_ENDIAN)))
889 toggle_endian2 (buf16, length);
890 }
891
892 /* toggle_endian2 toggles the endian for a 16 bit entity. */
893 static void
toggle_endian2(guint16 * buf16,gint length)894 toggle_endian2 (guint16 *buf16,
895 gint length)
896 {
897 guint16 *p = buf16;
898
899 while (p < buf16 + length)
900 {
901 *p = ((*p & 0xff) << 8) | (*p >> 8);
902 p++;
903 }
904 }
905
906 typedef struct
907 {
908 guint16 group_word;
909 guint16 element_word;
910 gchar value_rep[3];
911 guint32 element_length;
912 guint8 *value;
913 gboolean free;
914 } DICOMELEMENT;
915
916 /**
917 * dicom_add_element:
918 * @elements: head of a GSList containing DICOMELEMENT structures.
919 * @group_word: Dicom Element group number for the tag to be added to
920 * @elements.
921 * @element_word: Dicom Element element number for the tag to be added
922 * to @elements.
923 * @value_rep: a string representing the Dicom VR for the new element.
924 * @value: a pointer to an integer containing the value for the
925 * element to be created.
926 *
927 * Creates a DICOMELEMENT object and inserts it into @elements.
928 *
929 * Return value: the new head of @elements
930 **/
931 static GSList *
dicom_add_element(GSList * elements,guint16 group_word,guint16 element_word,const gchar * value_rep,guint32 element_length,guint8 * value)932 dicom_add_element (GSList *elements,
933 guint16 group_word,
934 guint16 element_word,
935 const gchar *value_rep,
936 guint32 element_length,
937 guint8 *value)
938 {
939 DICOMELEMENT *element = g_slice_new0 (DICOMELEMENT);
940
941 element->group_word = group_word;
942 element->element_word = element_word;
943 strncpy (element->value_rep, value_rep, sizeof (element->value_rep));
944 element->element_length = element_length;
945 element->value = value;
946
947 return g_slist_prepend (elements, element);
948 }
949
950 static GSList *
dicom_add_element_copy(GSList * elements,guint16 group_word,guint16 element_word,gchar * value_rep,guint32 element_length,const guint8 * value)951 dicom_add_element_copy (GSList *elements,
952 guint16 group_word,
953 guint16 element_word,
954 gchar *value_rep,
955 guint32 element_length,
956 const guint8 *value)
957 {
958 elements = dicom_add_element (elements,
959 group_word, element_word, value_rep,
960 element_length,
961 g_memdup (value, element_length));
962
963 ((DICOMELEMENT *) elements->data)->free = TRUE;
964
965 return elements;
966 }
967
968 /**
969 * dicom_add_element_int:
970 * @elements: head of a GSList containing DICOMELEMENT structures.
971
972 * @group_word: Dicom Element group number for the tag to be added to
973 * @elements.
974 * @element_word: Dicom Element element number for the tag to be added to
975 * @elements.
976 * @value_rep: a string representing the Dicom VR for the new element.
977 * @value: a pointer to an integer containing the value for the
978 * element to be created.
979 *
980 * Creates a DICOMELEMENT object from the passed integer pointer and
981 * adds it to @elements. Note: value should be the address of a
982 * guint16 for @value_rep == %US or guint32 for other values of
983 * @value_rep
984 *
985 * Return value: the new head of @elements
986 */
987 static GSList *
dicom_add_element_int(GSList * elements,guint16 group_word,guint16 element_word,gchar * value_rep,guint8 * value)988 dicom_add_element_int (GSList *elements,
989 guint16 group_word,
990 guint16 element_word,
991 gchar *value_rep,
992 guint8 *value)
993 {
994 guint32 len;
995
996 if (strcmp (value_rep, "US") == 0)
997 len = 2;
998 else
999 len = 4;
1000
1001 return dicom_add_element (elements,
1002 group_word, element_word, value_rep,
1003 len, value);
1004 }
1005
1006 /**
1007 * dicom_element_done:
1008 * @data: pointer to a DICOMELEMENT structure which is to be destroyed.
1009 *
1010 * Destroys the DICOMELEMENT passed as @data
1011 **/
1012 static void
dicom_element_done(gpointer data)1013 dicom_element_done (gpointer data)
1014 {
1015 if (data)
1016 {
1017 DICOMELEMENT *e = data;
1018
1019 if (e->free)
1020 g_free (e->value);
1021
1022 g_slice_free (DICOMELEMENT, data);
1023 }
1024 }
1025
1026 /**
1027 * dicom_elements_destroy:
1028 * @elements: head of a GSList containing DICOMELEMENT structures.
1029 *
1030 * Destroys the list of DICOMELEMENTs
1031 **/
1032 static void
dicom_elements_destroy(GSList * elements)1033 dicom_elements_destroy (GSList *elements)
1034 {
1035 if (elements)
1036 g_slist_free_full (elements, dicom_element_done);
1037 }
1038
1039 /**
1040 * dicom_destroy_element:
1041 * @elements: head of a GSList containing DICOMELEMENT structures.
1042 * @ele: a DICOMELEMENT structure to be removed from @elements
1043 *
1044 * Removes the specified DICOMELEMENT from @elements and Destroys it
1045 *
1046 * Return value: the new head of @elements
1047 **/
1048 static GSList *
dicom_destroy_element(GSList * elements,DICOMELEMENT * ele)1049 dicom_destroy_element (GSList *elements,
1050 DICOMELEMENT *ele)
1051 {
1052 if (ele)
1053 {
1054 elements = g_slist_remove_all (elements, ele);
1055
1056 if (ele->free)
1057 g_free (ele->value);
1058
1059 g_slice_free (DICOMELEMENT, ele);
1060 }
1061
1062 return elements;
1063 }
1064
1065 /**
1066 * dicom_elements_compare:
1067 * @a: pointer to a DICOMELEMENT structure.
1068 * @b: pointer to a DICOMELEMENT structure.
1069 *
1070 * Determines the equality of @a and @b as strcmp
1071 *
1072 * Return value: an integer indicating the equality of @a and @b.
1073 **/
1074 static gint
dicom_elements_compare(gconstpointer a,gconstpointer b)1075 dicom_elements_compare (gconstpointer a,
1076 gconstpointer b)
1077 {
1078 DICOMELEMENT *e1 = (DICOMELEMENT *)a;
1079 DICOMELEMENT *e2 = (DICOMELEMENT *)b;
1080
1081 if (e1->group_word == e2->group_word)
1082 {
1083 if (e1->element_word == e2->element_word)
1084 {
1085 return 0;
1086 }
1087 else if (e1->element_word > e2->element_word)
1088 {
1089 return 1;
1090 }
1091 else
1092 {
1093 return -1;
1094 }
1095 }
1096 else if (e1->group_word < e2->group_word)
1097 {
1098 return -1;
1099 }
1100
1101 return 1;
1102 }
1103
1104 /**
1105 * dicom_element_find_by_num:
1106 * @head: head of a GSList containing DICOMELEMENT structures.
1107 * @group_word: Dicom Element group number for the tag to be found.
1108 * @element_word: Dicom Element element number for the tag to be found.
1109 *
1110 * Retrieves the specified DICOMELEMENT from @head, if available.
1111 *
1112 * Return value: a DICOMELEMENT matching the specified group,element,
1113 * or NULL if the specified element was not found.
1114 **/
1115 static DICOMELEMENT *
dicom_element_find_by_num(GSList * head,guint16 group_word,guint16 element_word)1116 dicom_element_find_by_num (GSList *head,
1117 guint16 group_word,
1118 guint16 element_word)
1119 {
1120 DICOMELEMENT data = { group_word,element_word, "", 0, NULL};
1121 GSList *ele = g_slist_find_custom (head,&data,dicom_elements_compare);
1122 return (ele ? ele->data : NULL);
1123 }
1124
1125 /**
1126 * dicom_get_elements_list:
1127 * @image_ID: the image_ID from which to read parasites in order to
1128 * retrieve the dicom elements
1129 *
1130 * Reads all DICOMELEMENTs from the specified image's parasites.
1131 *
1132 * Return value: a GSList of all known dicom elements
1133 **/
1134 static GSList *
dicom_get_elements_list(gint32 image_ID)1135 dicom_get_elements_list (gint32 image_ID)
1136 {
1137 GSList *elements = NULL;
1138 GimpParasite *parasite;
1139 gchar **parasites = NULL;
1140 gint count = 0;
1141
1142 parasites = gimp_image_get_parasite_list (image_ID, &count);
1143
1144 if (parasites && count > 0)
1145 {
1146 gint i;
1147
1148 for (i = 0; i < count; i++)
1149 {
1150 if (strncmp (parasites[i], "dcm", 3) == 0)
1151 {
1152 parasite = gimp_image_get_parasite (image_ID, parasites[i]);
1153
1154 if (parasite)
1155 {
1156 gchar buf[1024];
1157 gchar *ptr1;
1158 gchar *ptr2;
1159 gchar value_rep[3] = "";
1160 guint16 group_word = 0;
1161 guint16 element_word = 0;
1162
1163 /* sacrificial buffer */
1164 strncpy (buf, parasites[i], sizeof (buf));
1165
1166 /* buf should now hold a string of the form
1167 * dcm/XXXX-XXXX-AA where XXXX are Hex values for
1168 * group and element respectively AA is the Value
1169 * Representation of the element
1170 *
1171 * start off by jumping over the dcm/ to the first Hex blob
1172 */
1173 ptr1 = strchr (buf, '/');
1174
1175 if (ptr1)
1176 {
1177 gchar t[15];
1178
1179 ptr1++;
1180 ptr2 = strchr (ptr1,'-');
1181
1182 if (ptr2)
1183 *ptr2 = '\0';
1184
1185 g_snprintf (t, sizeof (t), "0x%s", ptr1);
1186 group_word = (guint16) g_ascii_strtoull (t, NULL, 16);
1187 ptr1 = ptr2 + 1;
1188 }
1189
1190 /* now get the second Hex blob */
1191 if (ptr1)
1192 {
1193 gchar t[15];
1194
1195 ptr2 = strchr (ptr1, '-');
1196
1197 if (ptr2)
1198 *ptr2 = '\0';
1199
1200 g_snprintf (t, sizeof (t), "0x%s", ptr1);
1201 element_word = (guint16) g_ascii_strtoull (t, NULL, 16);
1202 ptr1 = ptr2 + 1;
1203 }
1204
1205 /* and lastly, the VR */
1206 if (ptr1)
1207 strncpy (value_rep, ptr1, sizeof (value_rep));
1208
1209 /*
1210 * If all went according to plan, we should be able
1211 * to add this element
1212 */
1213 if (group_word > 0 && element_word > 0)
1214 {
1215 const guint8 *val = gimp_parasite_data (parasite);
1216 const guint len = gimp_parasite_data_size (parasite);
1217
1218 /* and add the dicom element, asking to have
1219 it's value copied for later garbage collection */
1220 elements = dicom_add_element_copy (elements,
1221 group_word,
1222 element_word,
1223 value_rep, len, val);
1224 }
1225
1226 gimp_parasite_free (parasite);
1227 }
1228 }
1229 }
1230 }
1231
1232 /* cleanup the array of names */
1233 g_strfreev (parasites);
1234
1235 return elements;
1236 }
1237
1238 /**
1239 * dicom_remove_gimp_specified_elements:
1240 * @elements: GSList to remove elements from
1241 * @samples_per_pixel: samples per pixel of the image to be written.
1242 * if set to %3 the planar configuration for color images
1243 * will also be removed from @elements
1244 *
1245 * Removes certain DICOMELEMENTs from the elements list which are specific to the output of this plugin.
1246 *
1247 * Return value: the new head of @elements
1248 **/
1249 static GSList *
dicom_remove_gimp_specified_elements(GSList * elements,gint samples_per_pixel)1250 dicom_remove_gimp_specified_elements (GSList *elements,
1251 gint samples_per_pixel)
1252 {
1253 DICOMELEMENT remove[] = {
1254 /* Image presentation group */
1255 /* Samples per pixel */
1256 {0x0028, 0x0002, "", 0, NULL},
1257 /* Photometric interpretation */
1258 {0x0028, 0x0004, "", 0, NULL},
1259 /* rows */
1260 {0x0028, 0x0010, "", 0, NULL},
1261 /* columns */
1262 {0x0028, 0x0011, "", 0, NULL},
1263 /* Bits allocated */
1264 {0x0028, 0x0100, "", 0, NULL},
1265 /* Bits Stored */
1266 {0x0028, 0x0101, "", 0, NULL},
1267 /* High bit */
1268 {0x0028, 0x0102, "", 0, NULL},
1269 /* Pixel representation */
1270 {0x0028, 0x0103, "", 0, NULL},
1271
1272 {0,0,"",0,NULL}
1273 };
1274 DICOMELEMENT *ele;
1275 gint i;
1276
1277 /*
1278 * Remove all Dicom elements which will be set as part of the writing of the new file
1279 */
1280 for (i=0; remove[i].group_word > 0;i++)
1281 {
1282 if ((ele = dicom_element_find_by_num (elements,remove[i].group_word,remove[i].element_word)))
1283 {
1284 elements = dicom_destroy_element (elements,ele);
1285 }
1286 }
1287 /* special case - allow this to be overwritten if necessary */
1288 if (samples_per_pixel == 3)
1289 {
1290 /* Planar configuration for color images */
1291 if ((ele = dicom_element_find_by_num (elements,0x0028,0x0006)))
1292 {
1293 elements = dicom_destroy_element (elements,ele);
1294 }
1295 }
1296 return elements;
1297 }
1298
1299 /**
1300 * dicom_ensure_required_elements_present:
1301 * @elements: GSList to remove elements from
1302 * @today_string: string containing today's date in DICOM format. This
1303 * is used to default any required Dicom elements of date
1304 * type to today's date.
1305 *
1306 * Defaults DICOMELEMENTs to the values set by previous version of
1307 * this plugin, but only if they do not already exist.
1308 *
1309 * Return value: the new head of @elements
1310 **/
1311 static GSList *
dicom_ensure_required_elements_present(GSList * elements,gchar * today_string)1312 dicom_ensure_required_elements_present (GSList *elements,
1313 gchar *today_string)
1314 {
1315 const DICOMELEMENT defaults[] = {
1316 /* Meta element group */
1317 /* 0002, 0001 - File Meta Information Version */
1318 { 0x0002, 0x0001, "OB", 2, (guint8 *) "\0\1" },
1319 /* 0002, 0010 - Transfer syntax uid */
1320 { 0x0002, 0x0010, "UI",
1321 strlen ("1.2.840.10008.1.2.1"), (guint8 *) "1.2.840.10008.1.2.1"},
1322 /* 0002, 0013 - Implementation version name */
1323 { 0x0002, 0x0013, "SH",
1324 strlen ("GIMP Dicom Plugin 1.0"), (guint8 *) "GIMP Dicom Plugin 1.0" },
1325 /* Identifying group */
1326 /* ImageType */
1327 { 0x0008, 0x0008, "CS",
1328 strlen ("ORIGINAL\\PRIMARY"), (guint8 *) "ORIGINAL\\PRIMARY" },
1329 { 0x0008, 0x0016, "UI",
1330 strlen ("1.2.840.10008.5.1.4.1.1.7"), (guint8 *) "1.2.840.10008.5.1.4.1.1.7" },
1331 /* Study date */
1332 { 0x0008, 0x0020, "DA",
1333 strlen (today_string), (guint8 *) today_string },
1334 /* Series date */
1335 { 0x0008, 0x0021, "DA",
1336 strlen (today_string), (guint8 *) today_string },
1337 /* Acquisition date */
1338 { 0x0008, 0x0022, "DA",
1339 strlen (today_string), (guint8 *) today_string },
1340 /* Content Date */
1341 { 0x0008, 0x0023, "DA",
1342 strlen (today_string), (guint8 *) today_string},
1343 /* Content Time */
1344 { 0x0008, 0x0030, "TM",
1345 strlen ("000000.000000"), (guint8 *) "000000.000000"},
1346 /* AccessionNumber */
1347 { 0x0008, 0x0050, "SH", strlen (""), (guint8 *) "" },
1348 /* Modality */
1349 { 0x0008, 0x0060, "CS", strlen ("MR"), (guint8 *) "MR" },
1350 /* ConversionType */
1351 { 0x0008, 0x0064, "CS", strlen ("WSD"), (guint8 *) "WSD" },
1352 /* ReferringPhysiciansName */
1353 { 0x0008, 0x0090, "PN", strlen (""), (guint8 *) "" },
1354 /* Patient group */
1355 /* Patient name */
1356 { 0x0010, 0x0010, "PN",
1357 strlen ("DOE^WILBER"), (guint8 *) "DOE^WILBER" },
1358 /* Patient ID */
1359 { 0x0010, 0x0020, "LO",
1360 strlen ("314159265"), (guint8 *) "314159265" },
1361 /* Patient Birth date */
1362 { 0x0010, 0x0030, "DA",
1363 strlen (today_string), (guint8 *) today_string },
1364 /* Patient sex */
1365 { 0x0010, 0x0040, "CS", strlen (""), (guint8 *) "" /* unknown */ },
1366 /* Relationship group */
1367 /* StudyId */
1368 { 0x0020, 0x0010, "IS", strlen ("1"), (guint8 *) "1" },
1369 /* SeriesNumber */
1370 { 0x0020, 0x0011, "IS", strlen ("1"), (guint8 *) "1" },
1371 /* AcquisitionNumber */
1372 { 0x0020, 0x0012, "IS", strlen ("1"), (guint8 *) "1" },
1373 /* Instance number */
1374 { 0x0020, 0x0013, "IS", strlen ("1"), (guint8 *) "1" },
1375
1376 { 0, 0, "", 0, NULL }
1377 };
1378 gint i;
1379
1380 /*
1381 * Make sure that all of the default elements have a value
1382 */
1383 for (i=0; defaults[i].group_word > 0; i++)
1384 {
1385 if (dicom_element_find_by_num (elements,
1386 defaults[i].group_word,
1387 defaults[i].element_word) == NULL)
1388 {
1389 elements = dicom_add_element (elements,
1390 defaults[i].group_word,
1391 defaults[i].element_word,
1392 defaults[i].value_rep,
1393 defaults[i].element_length,
1394 defaults[i].value);
1395 }
1396 }
1397
1398 return elements;
1399 }
1400
1401 /* save_image() saves an image in the dicom format. The DICOM format
1402 * requires a lot of tags to be set. Some of them have real uses, others
1403 * must just be filled with dummy values.
1404 */
1405 static gboolean
save_image(const gchar * filename,gint32 image_ID,gint32 drawable_ID,GError ** error)1406 save_image (const gchar *filename,
1407 gint32 image_ID,
1408 gint32 drawable_ID,
1409 GError **error)
1410 {
1411 FILE *DICOM;
1412 GimpImageType drawable_type;
1413 GeglBuffer *buffer;
1414 const Babl *format;
1415 gint width;
1416 gint height;
1417 GByteArray *group_stream;
1418 GSList *elements = NULL;
1419 gint group;
1420 GDate *date;
1421 gchar today_string[16];
1422 gchar *photometric_interp;
1423 gint samples_per_pixel;
1424 gboolean retval = TRUE;
1425 guint16 zero = 0;
1426 guint16 seven = 7;
1427 guint16 eight = 8;
1428 guchar *src = NULL;
1429
1430 drawable_type = gimp_drawable_type (drawable_ID);
1431
1432 /* Make sure we're not saving an image with an alpha channel */
1433 if (gimp_drawable_has_alpha (drawable_ID))
1434 {
1435 g_message (_("Cannot save images with alpha channel."));
1436 return FALSE;
1437 }
1438
1439 switch (drawable_type)
1440 {
1441 case GIMP_GRAY_IMAGE:
1442 format = babl_format ("Y' u8");
1443 samples_per_pixel = 1;
1444 photometric_interp = "MONOCHROME2";
1445 break;
1446
1447 case GIMP_RGB_IMAGE:
1448 format = babl_format ("R'G'B' u8");
1449 samples_per_pixel = 3;
1450 photometric_interp = "RGB";
1451 break;
1452
1453 default:
1454 g_message (_("Cannot operate on unknown image types."));
1455 return FALSE;
1456 }
1457
1458 date = g_date_new ();
1459 g_date_set_time_t (date, time (NULL));
1460 g_snprintf (today_string, sizeof (today_string),
1461 "%04d%02d%02d", date->year, date->month, date->day);
1462 g_date_free (date);
1463
1464 /* Open the output file. */
1465 DICOM = g_fopen (filename, "wb");
1466
1467 if (!DICOM)
1468 {
1469 g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
1470 _("Could not open '%s' for writing: %s"),
1471 gimp_filename_to_utf8 (filename), g_strerror (errno));
1472 return FALSE;
1473 }
1474
1475 buffer = gimp_drawable_get_buffer (drawable_ID);
1476
1477 width = gegl_buffer_get_width (buffer);
1478 height = gegl_buffer_get_height (buffer);
1479
1480 /* Print dicom header */
1481 {
1482 guint8 val = 0;
1483 gint i;
1484
1485 for (i = 0; i < 0x80; i++)
1486 fwrite (&val, 1, 1, DICOM);
1487 }
1488 fprintf (DICOM, "DICM");
1489
1490 group_stream = g_byte_array_new ();
1491
1492 elements = dicom_get_elements_list (image_ID);
1493 if (0/*replaceElementsList*/)
1494 {
1495 /* to do */
1496 }
1497 else if (1/*insist_on_basic_elements*/)
1498 {
1499 elements = dicom_ensure_required_elements_present (elements,today_string);
1500 }
1501
1502 /*
1503 * Set value of custom elements
1504 */
1505 elements = dicom_remove_gimp_specified_elements (elements,samples_per_pixel);
1506
1507 /* Image presentation group */
1508 group = 0x0028;
1509 /* Samples per pixel */
1510 elements = dicom_add_element_int (elements, group, 0x0002, "US",
1511 (guint8 *) &samples_per_pixel);
1512 /* Photometric interpretation */
1513 elements = dicom_add_element (elements, group, 0x0004, "CS",
1514 strlen (photometric_interp),
1515 (guint8 *) photometric_interp);
1516 /* Planar configuration for color images */
1517 if (samples_per_pixel == 3)
1518 elements = dicom_add_element_int (elements, group, 0x0006, "US",
1519 (guint8 *) &zero);
1520 /* rows */
1521 elements = dicom_add_element_int (elements, group, 0x0010, "US",
1522 (guint8 *) &height);
1523 /* columns */
1524 elements = dicom_add_element_int (elements, group, 0x0011, "US",
1525 (guint8 *) &width);
1526 /* Bits allocated */
1527 elements = dicom_add_element_int (elements, group, 0x0100, "US",
1528 (guint8 *) &eight);
1529 /* Bits Stored */
1530 elements = dicom_add_element_int (elements, group, 0x0101, "US",
1531 (guint8 *) &eight);
1532 /* High bit */
1533 elements = dicom_add_element_int (elements, group, 0x0102, "US",
1534 (guint8 *) &seven);
1535 /* Pixel representation */
1536 elements = dicom_add_element_int (elements, group, 0x0103, "US",
1537 (guint8 *) &zero);
1538
1539 /* Pixel data */
1540 group = 0x7fe0;
1541 src = g_new (guchar, height * width * samples_per_pixel);
1542 if (src)
1543 {
1544 gegl_buffer_get (buffer, GEGL_RECTANGLE (0, 0, width, height), 1.0,
1545 format, src,
1546 GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
1547
1548 elements = dicom_add_element (elements, group, 0x0010, "OW",
1549 width * height * samples_per_pixel,
1550 (guint8 *) src);
1551
1552 elements = dicom_add_tags (DICOM, group_stream, elements);
1553
1554 g_free (src);
1555 }
1556 else
1557 {
1558 retval = FALSE;
1559 }
1560
1561 fclose (DICOM);
1562
1563 dicom_elements_destroy (elements);
1564 g_byte_array_free (group_stream, TRUE);
1565 g_object_unref (buffer);
1566
1567 return retval;
1568 }
1569
1570 /**
1571 * dicom_print_tags:
1572 * @data: pointer to a DICOMELEMENT structure which is to be written to file
1573 * @user_data: structure containing state information and output parameters
1574 *
1575 * Writes the specified DICOMELEMENT to @user_data's group_stream member.
1576 * Between groups, flushes the group_stream to @user_data's DICOM member.
1577 */
1578 static void
dicom_print_tags(gpointer data,gpointer user_data)1579 dicom_print_tags(gpointer data,
1580 gpointer user_data)
1581 {
1582 struct {
1583 FILE *DICOM;
1584 GByteArray *group_stream;
1585 gint last_group;
1586 } *d = user_data;
1587 DICOMELEMENT *e = (DICOMELEMENT *) data;
1588
1589 if (d->last_group >= 0 && e->group_word != d->last_group)
1590 {
1591 write_group_to_file (d->DICOM, d->last_group, d->group_stream);
1592 }
1593
1594 add_tag_pointer (d->group_stream,
1595 e->group_word, e->element_word,
1596 e->value_rep,e->value, e->element_length);
1597 d->last_group = e->group_word;
1598 }
1599
1600 /**
1601 * dicom_add_tags:
1602 * @DICOM: File pointer to which @elements should be written.
1603 * @group_stream: byte array used for staging Dicom Element groups
1604 * before flushing them to disk.
1605 * @elements: GSList container the Dicom Element elements from
1606 *
1607 * Writes all Dicom tags in @elements to the file @DICOM
1608 *
1609 * Return value: the new head of @elements
1610 **/
1611 static GSList *
dicom_add_tags(FILE * DICOM,GByteArray * group_stream,GSList * elements)1612 dicom_add_tags (FILE *DICOM,
1613 GByteArray *group_stream,
1614 GSList *elements)
1615 {
1616 struct {
1617 FILE *DICOM;
1618 GByteArray *group_stream;
1619 gint last_group;
1620 } data = { DICOM, group_stream, -1 };
1621
1622 elements = g_slist_sort (elements, dicom_elements_compare);
1623 g_slist_foreach (elements, dicom_print_tags, &data);
1624 /* make sure that the final group is written to the file */
1625 write_group_to_file (data.DICOM, data.last_group, data.group_stream);
1626
1627 return elements;
1628 }
1629
1630 /* add_tag_pointer () adds to the group_stream one single value with its
1631 * corresponding value_rep. Note that we use "explicit VR".
1632 */
1633 static void
add_tag_pointer(GByteArray * group_stream,gint group,gint element,const gchar * value_rep,const guint8 * data,gint length)1634 add_tag_pointer (GByteArray *group_stream,
1635 gint group,
1636 gint element,
1637 const gchar *value_rep,
1638 const guint8 *data,
1639 gint length)
1640 {
1641 gboolean is_long;
1642 guint16 swapped16;
1643 guint32 swapped32;
1644 guint pad = 0;
1645
1646 is_long = (strstr ("OB|OW|SQ|UN", value_rep) != NULL) || length > 65535;
1647
1648 swapped16 = g_ntohs (GUINT16_SWAP_LE_BE (group));
1649 g_byte_array_append (group_stream, (guint8 *) &swapped16, 2);
1650
1651 swapped16 = g_ntohs (GUINT16_SWAP_LE_BE (element));
1652 g_byte_array_append (group_stream, (guint8 *) &swapped16, 2);
1653
1654 g_byte_array_append (group_stream, (const guchar *) value_rep, 2);
1655
1656 if (length % 2 != 0)
1657 {
1658 /* the dicom standard requires all elements to be of even byte
1659 * length. this element would be odd, so we must pad it before
1660 * adding it
1661 */
1662 pad = 1;
1663 }
1664
1665 if (is_long)
1666 {
1667
1668 g_byte_array_append (group_stream, (const guchar *) "\0\0", 2);
1669
1670 swapped32 = g_ntohl (GUINT32_SWAP_LE_BE (length + pad));
1671 g_byte_array_append (group_stream, (guint8 *) &swapped32, 4);
1672 }
1673 else
1674 {
1675 swapped16 = g_ntohs (GUINT16_SWAP_LE_BE (length + pad));
1676 g_byte_array_append (group_stream, (guint8 *) &swapped16, 2);
1677 }
1678
1679 g_byte_array_append (group_stream, data, length);
1680
1681 if (pad)
1682 {
1683 /* add a padding byte to the stream
1684 *
1685 * From ftp://medical.nema.org/medical/dicom/2009/09_05pu3.pdf:
1686 *
1687 * Values with VRs constructed of character strings, except in
1688 * the case of the VR UI, shall be padded with SPACE characters
1689 * (20H, in the Default Character Repertoire) when necessary to
1690 * achieve even length. Values with a VR of UI shall be padded
1691 * with a single trailing NULL (00H) character when necessary
1692 * to achieve even length. Values with a VR of OB shall be
1693 * padded with a single trailing NULL byte value (00H) when
1694 * necessary to achieve even length.
1695 */
1696 if (strstr ("UI|OB", value_rep) != NULL)
1697 {
1698 g_byte_array_append (group_stream, (guint8 *) "\0", 1);
1699 }
1700 else
1701 {
1702 g_byte_array_append (group_stream, (guint8 *) " ", 1);
1703 }
1704 }
1705 }
1706
1707 /* Once a group has been built it has to be wrapped with a meta-group
1708 * tag before it is written to the DICOM file. This is done by
1709 * write_group_to_file.
1710 */
1711 static gboolean
write_group_to_file(FILE * DICOM,gint group,GByteArray * group_stream)1712 write_group_to_file (FILE *DICOM,
1713 gint group,
1714 GByteArray *group_stream)
1715 {
1716 gboolean retval = TRUE;
1717 guint16 swapped16;
1718 guint32 swapped32;
1719
1720 /* Add header to the group and output it */
1721 swapped16 = g_ntohs (GUINT16_SWAP_LE_BE (group));
1722
1723 fwrite ((gchar *) &swapped16, 1, 2, DICOM);
1724 fputc (0, DICOM);
1725 fputc (0, DICOM);
1726 fputc ('U', DICOM);
1727 fputc ('L', DICOM);
1728
1729 swapped16 = g_ntohs (GUINT16_SWAP_LE_BE (4));
1730 fwrite ((gchar *) &swapped16, 1, 2, DICOM);
1731
1732 swapped32 = g_ntohl (GUINT32_SWAP_LE_BE (group_stream->len));
1733 fwrite ((gchar *) &swapped32, 1, 4, DICOM);
1734
1735 if (fwrite (group_stream->data,
1736 1, group_stream->len, DICOM) != group_stream->len)
1737 retval = FALSE;
1738
1739 g_byte_array_set_size (group_stream, 0);
1740
1741 return retval;
1742 }
1743