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