Coverage Report

Created: 2026-04-01 07:49

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/graphicsmagick/coders/fits.c
Line
Count
Source
1
/*
2
% Copyright (C) 2003-2026 GraphicsMagick Group
3
% Copyright (C) 2002 ImageMagick Studio
4
% Copyright 1991-1999 E. I. du Pont de Nemours and Company
5
%
6
% This program is covered by multiple licenses, which are described in
7
% Copyright.txt. You should have received a copy of Copyright.txt with this
8
% package; otherwise see http://www.graphicsmagick.org/www/Copyright.html.
9
%
10
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
11
%                                                                             %
12
%                                                                             %
13
%                                                                             %
14
%                        FFFFF  IIIII  TTTTT  SSSSS                           %
15
%                        F        I      T    SS                              %
16
%                        FFF      I      T     SSS                            %
17
%                        F        I      T       SS                           %
18
%                        F      IIIII    T    SSSSS                           %
19
%                                                                             %
20
%                                                                             %
21
%            Read/Write Flexible Image Transport System Images.               %
22
%                                                                             %
23
%                                                                             %
24
%                              Software Design                                %
25
%                                John Cristy                                  %
26
%                                 July 1992                                   %
27
%                              Jaroslav Fojtik                                %
28
%                                2008 - 2022                                  %
29
%                                                                             %
30
%                                                                             %
31
%                                                                             %
32
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
33
%
34
%
35
*/
36

37
/*
38
  Include declarations.
39
*/
40
#include "magick/studio.h"
41
#include "magick/blob.h"
42
#include "magick/colormap.h"
43
#include "magick/constitute.h"
44
#include "magick/enum_strings.h"
45
#include "magick/magick.h"
46
#include "magick/monitor.h"
47
#include "magick/pixel_cache.h"
48
#include "magick/utility.h"
49
#include "magick/version.h"
50
#include "magick/static.h"
51

52
/*
53
  Forward declarations.
54
*/
55
static unsigned int
56
  WriteFITSImage(const ImageInfo *,Image *);
57
58
static int
59
InsertRowHDU(char *buffer, const char *data, int offset);
60

61
62
64.2M
#define FITS_BLOCK_SIZE 2880
63
4.20M
#define FITS_ROW_SIZE 80
64
65
66
/* Convert signed values to unsigned - and reverse. */
67
static void FixSignedValues(unsigned char *data, int size, unsigned step, unsigned endian)
68
37.4k
{
69
37.4k
  if (endian != MSBEndian)
70
1.61k
    {
71
1.61k
      data += step - 1;  /* LSB has most signifficant byte at the end */
72
1.61k
    }                    /* MSB has most signifficant byte first */
73
74
278k
  while (size-->0)
75
241k
    {
76
241k
      *data ^= 0x80;
77
241k
      data += step;
78
241k
    }
79
37.4k
}
80
81
/*
82
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
83
%                                                                             %
84
%                                                                             %
85
%                                                                             %
86
%   I s F I T S                                                               %
87
%                                                                             %
88
%                                                                             %
89
%                                                                             %
90
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
91
%
92
%  Method IsFITS returns True if the image format type, identified by the
93
%  magick string, is FITS.
94
%
95
%  The format of the IsFITS method is:
96
%
97
%      unsigned int IsFITS(const unsigned char *magick,const size_t length)
98
%
99
%  A description of each parameter follows:
100
%
101
%    o status:  Method IsFITS returns True if the image format type is FITS.
102
%
103
%    o magick: This string is generally the first few bytes of an image file
104
%      or blob.
105
%
106
%    o length: Specifies the length of the magick string.
107
%
108
%
109
*/
110
static unsigned int IsFITS(const unsigned char *magick,const size_t length)
111
0
{
112
0
  if (length < 6)
113
0
    return(False);
114
0
  if (LocaleNCompare((char *) magick,"IT0",3) == 0)
115
0
    return(True);
116
0
  if (LocaleNCompare((char *) magick,"SIMPLE",6) == 0)
117
0
    return(True);
118
0
  return(False);
119
0
}
120

121
/*
122
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
123
%                                                                             %
124
%                                                                             %
125
%                                                                             %
126
%   R e a d F I T S I m a g e                                                 %
127
%                                                                             %
128
%                                                                             %
129
%                                                                             %
130
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
131
%
132
%  Method ReadFITSImage reads a FITS image file and returns it.  It
133
%  allocates the memory necessary for the new Image structure and returns a
134
%  pointer to the new image.
135
%
136
%  The format of the ReadFITSImage method is:
137
%
138
%      Image *ReadFITSImage(const ImageInfo *image_info,
139
%        ExceptionInfo *exception)
140
%
141
%  A description of each parameter follows:
142
%
143
%    o image: Method ReadFITSImage returns a pointer to the image after
144
%      reading.  A null image is returned if there is a memory shortage or if
145
%      the image cannot be read.
146
%
147
%    o filename: Specifies the name of the image to read.
148
%
149
%    o exception: return any errors or warnings in this structure.
150
%
151
%
152
*/
153
typedef struct _FITSInfo
154
{
155
  char
156
    extensions_exist;
157
158
  int
159
    simple,
160
    bits_per_pixel,
161
    columns,
162
    rows,
163
    number_axes,
164
    number_scenes;
165
166
  double
167
    min_data,
168
    max_data,
169
    zero,
170
    scale;
171
} FITSInfo;
172
173
static Image *ReadFITSImage(const ImageInfo *image_info,
174
                            ExceptionInfo *exception)
175
30.2k
{
176
30.2k
  char
177
30.2k
    keyword[FITS_ROW_SIZE+1],
178
30.2k
    value[FITS_ROW_SIZE+1];
179
180
30.2k
  FITSInfo
181
30.2k
    fits_info;
182
183
30.2k
  Image
184
30.2k
    *image;
185
186
30.2k
  int
187
30.2k
    c;
188
189
30.2k
  int
190
30.2k
    logging;
191
192
30.2k
  long
193
30.2k
    packet_size,
194
30.2k
    scene,
195
30.2k
    y,
196
30.2k
    ax_number;
197
198
30.2k
  register PixelPacket
199
30.2k
    *q;
200
201
30.2k
  unsigned char
202
30.2k
    *fits_pixels;
203
204
30.2k
  unsigned int
205
30.2k
    status,
206
30.2k
    value_expected;
207
208
30.2k
  magick_uintmax_t
209
30.2k
    number_pixels;
210
211
30.2k
  ImportPixelAreaOptions import_options;
212
213
30.2k
  magick_off_t
214
30.2k
    file_size,
215
30.2k
    remaining;
216
217
  /*
218
    Open image file.
219
  */
220
30.2k
  assert(image_info != (const ImageInfo *) NULL);
221
30.2k
  assert(image_info->signature == MagickSignature);
222
30.2k
  assert(exception != (ExceptionInfo *) NULL);
223
30.2k
  assert(exception->signature == MagickSignature);
224
225
30.2k
  logging = LogMagickEvent(CoderEvent,GetMagickModule(),"enter");
226
227
30.2k
  image=AllocateImage(image_info);
228
30.2k
  image->rows=0;
229
30.2k
  image->columns=0;
230
30.2k
  status=OpenBlob(image_info,image,ReadBinaryBlobMode,exception);
231
30.2k
  if (status == False)
232
30.2k
    ThrowReaderException(FileOpenError,UnableToOpenFile,image);
233
234
30.2k
  file_size=GetBlobSize(image);
235
236
  /*
237
    Verify file header
238
  */
239
30.2k
  if (ReadBlob(image,sizeof(keyword),keyword) != sizeof(keyword))
240
27.1k
    ThrowReaderException(CorruptImageError,UnexpectedEndOfFile,image);
241
242
27.1k
  if ((LocaleNCompare(keyword,"IT0",3) != 0) &&
243
14.5k
      (LocaleNCompare(keyword,"SIMPLE",6) != 0))
244
27.1k
    ThrowReaderException(CorruptImageError,ImproperImageHeader,image);
245
246
  /*
247
    Seek back to beginning of file
248
  */
249
27.1k
  SeekBlob(image,0,SEEK_SET);
250
251
  /*
252
    Initialize common part of image header.
253
  */
254
27.1k
  fits_info.extensions_exist=0;
255
27.1k
  fits_info.simple=False;
256
257
27.1k
  ImportPixelAreaOptionsInit(&import_options);
258
27.1k
  import_options.endian = MSBEndian;
259
27.1k
  import_options.sample_type = UnsignedQuantumSampleType;
260
261
  /*
262
    Decode image header.
263
  */
264
27.1k
  if ((c=ReadBlobByte(image)) == EOF)
265
27.1k
    ThrowReaderException(CorruptImageError,UnexpectedEndOfFile,image);
266
267
47.0k
 ReadExtension:
268
47.0k
  if (logging) (void)LogMagickEvent(CoderEvent,GetMagickModule(),
269
0
                                    "Reading FITS HDU at position: %Xh",
270
0
                                    (unsigned)TellBlob(image) );
271
272
  /*
273
    Initialize image header for all subheaders.
274
  */
275
47.0k
  fits_info.bits_per_pixel=8;
276
47.0k
  fits_info.columns=1;
277
47.0k
  fits_info.rows=1;
278
47.0k
  fits_info.number_axes=0;
279
47.0k
  fits_info.number_scenes=1;
280
47.0k
  fits_info.min_data=0.0;
281
47.0k
  fits_info.max_data=0.0;
282
47.0k
  fits_info.zero=0.0;
283
47.0k
  fits_info.scale=1.0;
284
47.0k
  number_pixels = 0;
285
286
47.0k
  for ( ; ; )
287
1.01M
    {
288
1.01M
      if (!isalnum((int) c))
289
402k
        c=ReadBlobByte(image);
290
616k
      else
291
616k
        {
292
616k
          register char
293
616k
            *p;
294
295
          /*
296
            Determine a keyword and its value.
297
          */
298
616k
          p=keyword;
299
616k
          do
300
3.81M
            {
301
3.81M
              if ((p-keyword) < (FITS_ROW_SIZE-1))
302
3.77M
                *p++=c;
303
3.81M
              if ((c=ReadBlobByte(image)) == EOF)
304
3.81M
                ThrowReaderException(CorruptImageError,UnexpectedEndOfFile,image);
305
3.81M
            } while (isalnum(c) || (c == '_'));
306
613k
          *p='\0';
307
613k
          if (LocaleCompare(keyword,"END") == 0)
308
23.5k
            break;
309
589k
          value_expected=False;
310
3.53M
          while (isspace(c) || (c == '='))
311
2.94M
            {
312
2.94M
              if (c == '=')
313
156k
                value_expected=True;
314
2.94M
              if ((c=ReadBlobByte(image)) == EOF)
315
2.94M
                ThrowReaderException(CorruptImageError,UnexpectedEndOfFile,image);
316
2.94M
            }
317
587k
          if (value_expected == False)
318
448k
            continue;
319
138k
          p=value;
320
138k
          if (c == '\'')
321
4.02k
            c=ReadBlobByte(image);
322
323
521k
          while (isalnum(c) || (c == '-') || (c == '+') || (c == '.'))
324
383k
            {
325
383k
              if ((p-value) < (FITS_ROW_SIZE-1))
326
377k
                *p++=c;
327
383k
              if ((c=ReadBlobByte(image)) == EOF)
328
382k
                ThrowReaderException(CorruptImageError,UnexpectedEndOfFile,image);
329
382k
            }
330
137k
          *p='\0';
331
          /*
332
            Assign a value to the specified keyword.
333
          */
334
137k
          if (LocaleCompare(keyword,"SIMPLE") == 0)
335
30.1k
            fits_info.simple = (*value=='T') || (*value=='t');
336
137k
          if (LocaleCompare(keyword,"EXTEND") == 0)
337
2.95k
            fits_info.extensions_exist = (*value== 'T') || (*value=='t');
338
137k
          if (LocaleCompare(keyword,"BITPIX") == 0)
339
6.47k
            {
340
6.47k
              fits_info.bits_per_pixel=MagickAtoI(value);
341
              /*
342
                BITPIX valid values:
343
                8 -- Character or unsigned binary integer
344
                16 -- 16-bit two's complement binary integer
345
                32 -- 32-bit two's complement binary integer
346
                -32 -- 32-bit floating point, single precision
347
                -64 -- 64-bit floating point, double precision
348
              */
349
6.47k
              if (fits_info.bits_per_pixel > 0)
350
1.71k
                import_options.sample_type = UnsignedQuantumSampleType;
351
6.47k
              if (fits_info.bits_per_pixel < 0)
352
4.26k
                import_options.sample_type = FloatQuantumSampleType;
353
6.47k
            }
354
137k
          if (!LocaleNCompare(keyword,"NAXIS",5))
355
57.7k
            {
356
57.7k
              if (keyword[5] == 0) ax_number=-1;
357
36.3k
              else
358
36.3k
                {
359
36.3k
                  if (isdigit((int) keyword[5]))
360
35.4k
                    ax_number = MagickAtoI(keyword+5);
361
949
                  else ax_number=-2;  /*unsupported fits keyword*/
362
36.3k
                }
363
57.7k
              y=0;
364
57.7k
              if (ax_number >= -1)
365
55.7k
                y = MagickAtoI(value);
366
57.7k
              switch (ax_number)
367
57.7k
                {
368
21.3k
                case -1:fits_info.number_axes = y; break;
369
22.1k
                case 1: fits_info.columns = (y <= 0) ? 1 : y; break;
370
5.61k
                case 2: fits_info.rows = (y <= 0) ? 1 : y; break;
371
2.47k
                case 3: fits_info.number_scenes = (y <=0 ) ? 1 : y; break;
372
57.7k
                }
373
57.7k
              if (ax_number > 0)
374
32.6k
                {
375
32.6k
                  if (number_pixels == 0)
376
20.6k
                    number_pixels = (magick_uintmax_t) y;
377
12.0k
                  else
378
12.0k
                    number_pixels *= (y <= 0) ? 1U : (magick_uintmax_t) y;
379
32.6k
                }
380
57.7k
            }
381
382
137k
          if (LocaleCompare(keyword,"DATAMAX") == 0)
383
1.20k
            fits_info.max_data=MagickAtoF(value);
384
137k
          if (LocaleCompare(keyword,"DATAMIN") == 0)
385
953
            fits_info.min_data=MagickAtoF(value);
386
137k
          if (LocaleCompare(keyword,"BZERO") == 0)
387
423
            fits_info.zero=MagickAtoF(value);
388
137k
          if (LocaleCompare(keyword,"BSCALE") == 0)
389
801
            fits_info.scale=MagickAtoF(value);
390
137k
          if (LocaleCompare(keyword,"XENDIAN") == 0)
391
517
            {
392
517
              if (LocaleCompare(keyword,"BIG") == 0)
393
0
                import_options.endian = MSBEndian;
394
517
              else
395
517
                import_options.endian = LSBEndian;
396
517
            }
397
137k
        }
398
36.7M
      while ((TellBlob(image) % 80) != 0)
399
36.1M
        if ((c=ReadBlobByte(image)) == EOF)
400
529k
          ThrowReaderException(CorruptImageError,UnexpectedEndOfFile,image);
401
529k
      if ((c=ReadBlobByte(image)) == EOF)
402
523k
        ThrowReaderException(CorruptImageError,UnexpectedEndOfFile,image);
403
523k
    }
404
405
34.4M
  while ((TellBlob(image) % FITS_BLOCK_SIZE) != 0) /* Read till the rest of a block. */
406
34.4M
    if ((c=ReadBlobByte(image)) == EOF)
407
21.3k
      ThrowReaderException(CorruptImageError,UnexpectedEndOfFile,image);
408
409
  /*
410
    Verify that required image information is defined.
411
  */
412
21.3k
  if (logging)
413
0
    {
414
0
      (void) LogMagickEvent(CoderEvent,GetMagickModule(),
415
0
                            "HDU read finished at offset %" MAGICK_OFF_F "Xh, "
416
0
                            "number of pixels is: %" MAGICK_UINTMAX_F "u (%d*%d*%d)",
417
0
                            TellBlob(image),
418
0
                            number_pixels,
419
0
                            fits_info.columns,
420
0
                            fits_info.rows,
421
0
                            fits_info.number_scenes);
422
0
      (void) LogMagickEvent(CoderEvent,GetMagickModule(),
423
0
                            "FITS header:\n"
424
0
                            "    extensions_exist : 0x%02x\n"
425
0
                            "    simple           : %d\n"
426
0
                            "    bits_per_pixel   : %d\n"
427
0
                            "    columns          : %d\n"
428
0
                            "    rows             : %d\n"
429
0
                            "    number_axes      : %d\n"
430
0
                            "    number_scenes    : %d\n"
431
0
                            "    min_data         : %g\n"
432
0
                            "    max_data         : %g\n"
433
0
                            "    zero             : %g\n"
434
0
                            "    scale            : %g",
435
0
                            (int) fits_info.extensions_exist,
436
0
                            fits_info.simple,
437
0
                            fits_info.bits_per_pixel,
438
0
                            fits_info.columns,
439
0
                            fits_info.rows,
440
0
                            fits_info.number_axes,
441
0
                            fits_info.number_scenes,
442
0
                            fits_info.min_data,
443
0
                            fits_info.max_data,
444
0
                            fits_info.zero,
445
0
                            fits_info.scale);
446
0
    }
447
448
21.3k
  if ((fits_info.bits_per_pixel != 8) &&
449
3.47k
      (fits_info.bits_per_pixel != 16) &&
450
3.27k
      (fits_info.bits_per_pixel != 32) &&
451
2.89k
      (fits_info.bits_per_pixel != 64) &&
452
2.45k
      (fits_info.bits_per_pixel != -32) &&
453
1.31k
      (fits_info.bits_per_pixel != -64))
454
21.2k
    ThrowReaderException(CorruptImageError,ImproperImageHeader,image);
455
456
21.2k
  if ((fits_info.columns <= 0) ||
457
21.2k
      (fits_info.rows <= 0) ||
458
21.2k
      (fits_info.number_axes < 0) ||
459
21.2k
      (fits_info.number_scenes < 0))
460
21.2k
    ThrowReaderException(CorruptImageError,ImproperImageHeader,image);
461
462
  /*
463
    Validate that remaining file size is sufficient for claimed
464
    image properties
465
  */
466
21.2k
  packet_size=AbsoluteValue(fits_info.bits_per_pixel)/8;
467
21.2k
  remaining = file_size-TellBlob(image);
468
21.2k
  if (remaining == 0)
469
47
    {
470
47
      ThrowReaderException(CorruptImageError,InsufficientImageDataInFile,
471
47
                           image);
472
0
    }
473
21.2k
  if (file_size != 0)
474
21.2k
    {
475
21.2k
      double
476
21.2k
        ratio = (((double) number_pixels*packet_size)/remaining);
477
478
21.2k
      (void) LogMagickEvent(CoderEvent,GetMagickModule(),
479
21.2k
                            "Remaining: %" MAGICK_OFF_F "d, Ratio: %g",
480
21.2k
                            remaining, ratio);
481
482
21.2k
      if (ratio > 1.5)
483
22
        {
484
22
          (void) LogMagickEvent(CoderEvent,GetMagickModule(),
485
22
                                "Unreasonable file size "
486
22
                                "(ratio of pixels to remaining file size %g)",
487
22
                                ratio);
488
22
          ThrowReaderException(CorruptImageError,InsufficientImageDataInFile,
489
22
                               image);
490
0
        }
491
21.2k
    }
492
493
21.1k
  if ((!fits_info.simple) || (fits_info.number_axes < 1) ||
494
13.2k
      (fits_info.number_axes > 4) || (number_pixels == 0))
495
9.48k
    {
496
9.48k
      if (!fits_info.extensions_exist)     /* when extensions exists, process further */
497
9.44k
        ThrowReaderException(CorruptImageError,ImageTypeNotSupported,image);
498
499
9.44k
      number_pixels = (number_pixels*AbsoluteValue(fits_info.bits_per_pixel)) / 8;
500
9.44k
      number_pixels = ((number_pixels+FITS_BLOCK_SIZE-1) /
501
9.44k
                       FITS_BLOCK_SIZE) * FITS_BLOCK_SIZE; /* raw data block size */
502
9.44k
      (void) LogMagickEvent(CoderEvent,GetMagickModule(),
503
9.44k
                            "Seek CUR: %" MAGICK_OFF_F "u",(magick_off_t) number_pixels);
504
9.44k
      SeekBlob(image,(magick_off_t) number_pixels,SEEK_CUR);
505
9.44k
    }
506
11.6k
  else
507
11.6k
    {
508
11.6k
      for (scene=0; scene < (long) fits_info.number_scenes; scene++)
509
11.6k
        {
510
11.6k
          if (image->rows!=0 && image->columns!=0)
511
10.4k
            {
512
              /*
513
                Allocate next image structure.
514
              */
515
10.4k
              AllocateNextImage(image_info,image);
516
10.4k
              if (image->next == (Image *) NULL)
517
0
                {
518
0
                  DestroyImageList(image);
519
0
                  return((Image *) NULL);
520
0
                }
521
10.4k
              image=SyncNextImageInList(image);
522
10.4k
              if (!MagickMonitorFormatted(TellBlob(image),file_size,exception,
523
10.4k
                                          LoadImagesText,image->filename))
524
0
                break;
525
10.4k
            }
526
527
          /*
528
            Create linear colormap.
529
          */
530
11.6k
          image->columns = fits_info.columns;
531
11.6k
          image->rows = fits_info.rows;
532
11.6k
          if (fits_info.bits_per_pixel>=0)
533
10.2k
            image->depth = Min(QuantumDepth,fits_info.bits_per_pixel);
534
1.41k
          else
535
1.41k
            image->depth = QuantumDepth;              /* double type cell */
536
          /* image->storage_class=PseudoClass; */
537
11.6k
          image->storage_class = DirectClass;
538
11.6k
          image->scene=scene;
539
11.6k
          image->is_grayscale = 1;
540
541
11.6k
          if (image->logging)
542
11.6k
            (void) LogMagickEvent(CoderEvent,GetMagickModule(),
543
11.6k
                                  "Frame[%lu] geometry %lux%lu, %u bits/pixel, %s",
544
11.6k
                                  scene, image->columns, image->rows,
545
11.6k
                                  fits_info.bits_per_pixel,
546
11.6k
                                  ClassTypeToString(image->storage_class));
547
548
11.6k
          if (image->depth<=8 && fits_info.bits_per_pixel==8)
549
9.95k
            if (!AllocateImageColormap(image,1 << image->depth))
550
11.6k
              ThrowReaderException(FileOpenError,UnableToOpenFile,image);
551
552
11.6k
          if (image_info->ping && (image_info->subrange != 0))
553
0
            if (image->scene >= (image_info->subimage+image_info->subrange-1))
554
0
              break;
555
556
11.6k
          if (CheckImagePixelLimits(image, exception) != MagickPass)
557
11.5k
            ThrowReaderException(ResourceLimitError,ImagePixelLimitExceeded,image);
558
559
          /*
560
            Initialize image structure.
561
          */
562
11.5k
          packet_size=AbsoluteValue(fits_info.bits_per_pixel)/8;
563
564
          /*
565
            Validate that remaining file size is sufficient for claimed
566
            image properties
567
          */
568
11.5k
          remaining = file_size-TellBlob(image);
569
11.5k
          if (remaining == 0)
570
0
            {
571
0
              ThrowReaderException(CorruptImageError,InsufficientImageDataInFile,
572
0
                                   image);
573
0
            }
574
11.5k
          if (file_size != 0)
575
11.5k
            {
576
11.5k
              double
577
11.5k
                ratio = (((double) image->columns*image->rows*packet_size)/remaining);
578
579
11.5k
              (void) LogMagickEvent(CoderEvent,GetMagickModule(),
580
11.5k
                                    "Remaining: %" MAGICK_OFF_F "d, Ratio: %g",
581
11.5k
                                    remaining, ratio);
582
583
11.5k
              if (ratio > 1.5)
584
9
                {
585
9
                  (void) LogMagickEvent(CoderEvent,GetMagickModule(),
586
9
                                        "Unreasonable file size "
587
9
                                        "(ratio of pixels to remaining file size %g)",
588
9
                                        ratio);
589
9
                  ThrowReaderException(CorruptImageError,InsufficientImageDataInFile,
590
9
                                       image);
591
0
                }
592
11.5k
            }
593
594
11.5k
          fits_pixels=MagickAllocateResourceLimitedArray(unsigned char *, image->columns, packet_size);
595
11.5k
          if (fits_pixels == (unsigned char *) NULL)
596
11.5k
            ThrowReaderException(ResourceLimitError,MemoryAllocationFailed,image);
597
598
          /*
599
            Recover minimum and maximum from data if not present in HDU
600
          */
601
11.5k
          if ((fits_info.min_data == 0.0) && (fits_info.max_data == 0.0))
602
11.3k
            {  /*  Determine minimum and maximum intensity. */
603
11.3k
              if (fits_info.bits_per_pixel==-32)
604
642
                (void) MagickFindRawImageMinMax(image, import_options.endian, image->columns,
605
642
                                                image->rows, FloatPixel, packet_size*image->columns,
606
642
                                                fits_pixels, &import_options.double_minvalue,
607
642
                                                &import_options.double_maxvalue);
608
11.3k
              if (fits_info.bits_per_pixel==-64)
609
609
                (void) MagickFindRawImageMinMax(image, import_options.endian, image->columns,
610
609
                                                image->rows, DoublePixel, packet_size*image->columns,
611
609
                                                fits_pixels, &import_options.double_minvalue,
612
609
                                                &import_options.double_maxvalue);
613
11.3k
            }
614
226
          else
615
226
            {
616
226
              import_options.double_maxvalue = fits_info.max_data;
617
226
              import_options.double_minvalue = fits_info.min_data;
618
226
            }
619
620
          /*
621
            Convert FITS pixels to pixel packets.
622
          */
623
624
273k
          for (y=(long) image->rows-1; y >= 0; y--)
625
269k
            {
626
269k
              q=SetImagePixels(image,0,y,image->columns,1);
627
269k
              if (q == (PixelPacket *) NULL)
628
30
                break;
629
630
269k
              if (ReadBlob(image, (size_t)packet_size*image->columns, fits_pixels) != (size_t)packet_size*image->columns)
631
498
                {
632
498
                  if (logging) (void)LogMagickEvent(CoderEvent,GetMagickModule(),
633
0
                                                    "  fits cannot read scanrow %u from a file.", (unsigned)y );
634
498
                  break; /* goto ExitLoop; */
635
498
                }
636
637
268k
              switch(fits_info.bits_per_pixel)
638
268k
                {
639
3.53k
                case 16: FixSignedValues(fits_pixels, image->columns, 2, import_options.endian);
640
3.53k
                  break;
641
783
                case 32: FixSignedValues(fits_pixels, image->columns, 4, import_options.endian);
642
783
                  break;
643
10.7k
                case 64: FixSignedValues(fits_pixels, image->columns, 8, import_options.endian);
644
10.7k
                  break;
645
268k
                }
646
647
268k
              if (ImportImagePixelArea(image, GrayQuantum, packet_size*8, fits_pixels, &import_options,0) == MagickFail)
648
6.44k
                {
649
6.44k
                  if (logging)
650
0
                    (void)LogMagickEvent(CoderEvent,GetMagickModule(),
651
0
                                         "  fits failed to ImportImagePixelArea for a row %u", (unsigned)y);
652
6.44k
                  break;
653
6.44k
                }
654
655
262k
              if (!SyncImagePixels(image))
656
0
                break;
657
262k
              if (QuantumTick(y,image->rows))
658
79.9k
                if (!MagickMonitorFormatted(y,image->rows,exception,
659
79.9k
                                            LoadImageText,image->filename,
660
79.9k
                                            image->columns,image->rows))
661
0
                  break;
662
262k
            }
663
11.5k
          MagickFreeResourceLimitedMemory(unsigned char *,fits_pixels);
664
11.5k
          if (EOFBlob(image))
665
498
            {
666
498
              ThrowException(exception,CorruptImageError,UnexpectedEndOfFile, image->filename);
667
498
              break;
668
498
            }
669
670
11.0k
          StopTimer(&image->timer);
671
672
          /*
673
            Proceed to next image.
674
          */
675
11.0k
          if (image_info->subrange != 0)
676
11.0k
            if (image->scene >= (image_info->subimage+image_info->subrange-1))
677
11.0k
              break;
678
11.0k
        }
679
11.6k
    }
680
681
21.0k
  if (fits_info.extensions_exist)
682
20.4k
    {
683
29.7M
      while ((TellBlob(image) % FITS_BLOCK_SIZE) != 0) /* Read till the rest of a block. */
684
29.7M
        if ((c=ReadBlobByte(image)) == EOF)
685
416
          break;
686
20.4k
      if (!EOFBlob(image))
687
19.9k
        {                                           /* Try to read a next extension block header. */
688
19.9k
          if ((c=ReadBlobByte(image)) != EOF)
689
19.9k
            goto ReadExtension;
690
19.9k
        }
691
20.4k
    }
692
693
1.06k
  CloseBlob(image);
694
695
  /*
696
    Verify frames
697
  */
698
1.06k
  image=GetFirstImageInList(image);
699
1.06k
  do
700
8.31k
    {
701
8.31k
      if ((image->columns == 0) || (image->rows == 0) || !GetPixelCachePresent(image))
702
27
        {
703
27
          if (logging)
704
0
            (void) LogMagickEvent(CoderEvent,GetMagickModule(),
705
0
                                  "Scene %lu: Image columns=%lu, rows=%lu, pixel-cache=%s",
706
0
                                  image->scene, image->columns, image->rows,
707
0
                                  GetPixelCachePresent(image) ? "Present" : "Missing!");
708
27
          ThrowReaderException(CorruptImageError,ImproperImageHeader,image);
709
0
        }
710
8.29k
      if (image->next)
711
7.25k
        image=image->next;
712
1.03k
      else
713
1.03k
        break;
714
8.29k
    } while (1);
715
716
1.03k
  image=GetFirstImageInList(image);
717
718
1.03k
  if (logging) (void)LogMagickEvent(CoderEvent,GetMagickModule(),"return");
719
1.03k
  return(image);
720
1.06k
}
721

722
/*
723
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
724
%                                                                             %
725
%                                                                             %
726
%                                                                             %
727
%   R e g i s t e r F I T S I m a g e                                         %
728
%                                                                             %
729
%                                                                             %
730
%                                                                             %
731
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
732
%
733
%  Method RegisterFITSImage adds attributes for the FITS image format to
734
%  the list of supported formats.  The attributes include the image format
735
%  tag, a method to read and/or write the format, whether the format
736
%  supports the saving of more than one frame to the same file or blob,
737
%  whether the format supports native in-memory I/O, and a brief
738
%  description of the format.
739
%
740
%  The format of the RegisterFITSImage method is:
741
%
742
%      RegisterFITSImage(void)
743
%
744
*/
745
ModuleExport void RegisterFITSImage(void)
746
5
{
747
5
  MagickInfo
748
5
    *entry;
749
750
5
  entry=SetMagickInfo("FITS");
751
5
  entry->decoder=(DecoderHandler) ReadFITSImage;
752
5
  entry->encoder=(EncoderHandler) WriteFITSImage;
753
5
  entry->magick=(MagickHandler) IsFITS;
754
5
  entry->adjoin=False;
755
5
  entry->seekable_stream=True;
756
5
  entry->description="Flexible Image Transport System";
757
5
  entry->module="FITS";
758
5
  (void) RegisterMagickInfo(entry);
759
5
}
760

761
/*
762
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
763
%                                                                             %
764
%                                                                             %
765
%                                                                             %
766
%   U n r e g i s t e r F I T S I m a g e                                     %
767
%                                                                             %
768
%                                                                             %
769
%                                                                             %
770
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
771
%
772
%  Method UnregisterFITSImage removes format registrations made by the
773
%  FITS module from the list of supported formats.
774
%
775
%  The format of the UnregisterFITSImage method is:
776
%
777
%      UnregisterFITSImage(void)
778
%
779
*/
780
ModuleExport void UnregisterFITSImage(void)
781
0
{
782
0
  (void) UnregisterMagickInfo("FITS");
783
0
}
784

785
786
/*
787
  This functions inserts one row into a HDU. Note that according to
788
  FITS spec a card image contains 80 bytes of ASCII data.
789
790
  buffer - 2880 byte logical FITS record (initially memset to 0).
791
  data   - string data to append at offset
792
  offset - offset into FITS record to write the data.
793
*/
794
static int InsertRowHDU(char *buffer, const char *data, int offset)
795
26.9k
{
796
26.9k
  size_t
797
26.9k
    len;
798
799
26.9k
  if (data == NULL)
800
0
    return offset;
801
26.9k
  len = strlen(data);
802
26.9k
  len = Min(len,80); /* Each card image is 80 bytes max */
803
804
26.9k
  if (len > (size_t) (((size_t)FITS_BLOCK_SIZE)-offset))
805
0
    len = ((size_t) FITS_BLOCK_SIZE)-offset;
806
807
26.9k
  (void) memcpy(buffer+offset,data,len);
808
26.9k
  return offset +80;
809
26.9k
}
810
811
/*
812
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
813
%                                                                             %
814
%                                                                             %
815
%                                                                             %
816
%   W r i t e F I T S I m a g e                                               %
817
%                                                                             %
818
%                                                                             %
819
%                                                                             %
820
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
821
%
822
%  Method WriteFITSImage writes a Flexible Image Transport System image to a
823
%  file as gray scale intensities [0..255].
824
%
825
%  The format of the WriteFITSImage method is:
826
%
827
%      unsigned int WriteFITSImage(const ImageInfo *image_info,Image *image)
828
%
829
%  A description of each parameter follows.
830
%
831
%    o status: Method WriteFITSImage return True if the image is written.
832
%      False is returned is there is a memory shortage or if the image file
833
%      fails to write.
834
%
835
%    o image_info: Specifies a pointer to a ImageInfo structure.
836
%
837
%    o image:  A pointer to an Image structure.
838
%
839
%
840
*/
841
static MagickPassFail WriteFITSImage(const ImageInfo *image_info,Image *image)
842
422
{
843
422
  char
844
422
    buffer[FITS_BLOCK_SIZE],
845
422
    *fits_info;
846
847
422
  long
848
422
    y;
849
850
422
  register const PixelPacket
851
422
    *p;
852
853
422
  unsigned char
854
422
    *pixels;
855
856
422
  unsigned int
857
422
    quantum_size;
858
859
422
  unsigned long
860
422
    packet_size;
861
862
422
  ExportPixelAreaOptions export_options;
863
864
422
  MagickPassFail
865
422
    status;
866
867
  /*
868
    Open output image file.
869
  */
870
422
  assert(image_info != (const ImageInfo *) NULL);
871
422
  assert(image_info->signature == MagickSignature);
872
422
  assert(image != (Image *) NULL);
873
422
  assert(image->signature == MagickSignature);
874
422
  status=OpenBlob(image_info,image,WriteBinaryBlobMode,&image->exception);
875
422
  if (status == MagickFail)
876
422
    ThrowWriterException(FileOpenError,UnableToOpenFile,image);
877
422
  if (TransformColorspace(image,RGBColorspace) == MagickFail)
878
422
    ThrowWriterException(CoderError,UnableToTransformColorspace,image);
879
880
422
  ExportPixelAreaOptionsInit(&export_options);
881
422
  export_options.endian = MSBEndian;
882
422
  export_options.sample_type = UnsignedQuantumSampleType;
883
884
422
  do
885
2.68k
    {
886
2.68k
      if (image->depth <= 8)
887
2.16k
        {
888
2.16k
          quantum_size=8;
889
2.16k
        }
890
527
      else if (image->depth <= 16)
891
527
        {
892
527
          quantum_size=16;
893
527
        }
894
0
      else
895
0
        {
896
0
          quantum_size=32;
897
0
        }
898
899
      /*
900
        Allocate image memory.
901
      */
902
2.68k
      packet_size=quantum_size/8;
903
2.68k
      fits_info=MagickAllocateResourceLimitedMemory(char *,FITS_BLOCK_SIZE);
904
2.68k
      if (fits_info == (char *) NULL)
905
0
        {
906
0
          ThrowWriterException(ResourceLimitError,MemoryAllocationFailed,image);
907
0
        }
908
2.68k
      pixels=MagickAllocateResourceLimitedArray(unsigned char *,packet_size,image->columns);
909
2.68k
      if (pixels == (unsigned char *) NULL)
910
0
        {
911
0
          MagickFreeResourceLimitedMemory(char *,fits_info);
912
0
          ThrowWriterException(ResourceLimitError,MemoryAllocationFailed,image);
913
0
        }
914
915
      /*
916
        Initialize image header.
917
      */
918
2.68k
      memset(fits_info,' ',FITS_BLOCK_SIZE);
919
2.68k
      y = 0;
920
2.68k
      y = InsertRowHDU(fits_info, "SIMPLE  =                    T", y);
921
2.68k
      MagickFormatString(buffer, sizeof(buffer),        "BITPIX  =                    %u",quantum_size);
922
2.68k
      y = InsertRowHDU(fits_info, buffer, y);
923
2.68k
      y = InsertRowHDU(fits_info, "NAXIS   =                    2", y);
924
2.68k
      MagickFormatString(buffer, sizeof(buffer), "NAXIS1  =           %10lu",image->columns);
925
2.68k
      y = InsertRowHDU(fits_info, buffer, y);
926
2.68k
      MagickFormatString(buffer, sizeof(buffer), "NAXIS2  =           %10lu",image->rows);
927
2.68k
      y = InsertRowHDU(fits_info, buffer, y);
928
2.68k
      MagickFormatString(buffer, sizeof(buffer), "DATAMIN =           %10u",0);
929
2.68k
      y = InsertRowHDU(fits_info, buffer, y);
930
2.68k
      MagickFormatString(buffer, sizeof(buffer), "DATAMAX =           %10lu", MaxValueGivenBits(quantum_size));
931
2.68k
      y = InsertRowHDU(fits_info, buffer, y);
932
2.68k
      if (quantum_size>8)
933
527
        {
934
527
          MagickFormatString(buffer, sizeof(buffer), "BZERO   =           %10u", (quantum_size <= 16) ? 32768U : 2147483648U);
935
527
          y = InsertRowHDU(fits_info, buffer, y);
936
527
        }
937
      /* Magick version data can only be 60 bytes. */
938
2.68k
      (void) MagickFormatString(buffer, sizeof(buffer), "HISTORY Created by %.60s.",MagickPackageName " " MagickLibVersionText);
939
2.68k
      y = InsertRowHDU(fits_info, buffer, y);
940
2.68k
      if (image->next!=NULL)
941
2.26k
        y = InsertRowHDU(fits_info, "EXTEND   =                    T", y);
942
2.68k
      y = InsertRowHDU(fits_info, "END", y);
943
2.68k
      (void) WriteBlob(image, FITS_BLOCK_SIZE, (char *)fits_info);
944
945
      /*
946
        Convert image to fits scale PseudoColor class.
947
      */
948
144k
      for (y=(long) image->rows-1; y >= 0; y--)
949
142k
        {
950
142k
          p=AcquireImagePixels(image,0,y,image->columns,1,&image->exception);
951
142k
          if (p == (const PixelPacket *) NULL)
952
0
            break;
953
142k
          if (ExportImagePixelArea(image,GrayQuantum,quantum_size,pixels,&export_options,0) == MagickFail)
954
0
            break;
955
142k
          if (quantum_size == 16)
956
22.3k
            FixSignedValues(pixels, image->columns, 2, export_options.endian);
957
142k
          if (quantum_size == 32)
958
0
            FixSignedValues(pixels, image->columns, 4, export_options.endian);
959
142k
          if (WriteBlob(image, (size_t) packet_size*image->columns,pixels) != (size_t) packet_size*image->columns)
960
0
            break;
961
142k
          if (QuantumTick((size_t) image->rows-y-1,image->rows))
962
36.3k
            {
963
36.3k
              status=MagickMonitorFormatted((size_t) image->rows-y-1,image->rows,
964
36.3k
                                            &image->exception,SaveImageText,
965
36.3k
                                            image->filename,
966
36.3k
                                            image->columns,image->rows);
967
36.3k
              if (status == False)
968
0
                break;
969
36.3k
            }
970
142k
        }
971
972
      /* Calculate of padding */
973
2.68k
      y = FITS_BLOCK_SIZE - (image->columns * image->rows * packet_size) % FITS_BLOCK_SIZE;
974
2.68k
      if (y > 0)
975
2.68k
        {
976
2.68k
          memset(fits_info, 0, y);
977
2.68k
          (void)WriteBlob(image,y,(char *) fits_info);
978
2.68k
        }
979
2.68k
      MagickFreeResourceLimitedMemory(char *,fits_info);
980
2.68k
      MagickFreeResourceLimitedMemory(unsigned char *,pixels);
981
982
2.68k
      if (image->next == (Image *) NULL)
983
422
        {
984
422
          if (image->logging)
985
110
            (void)LogMagickEvent(CoderEvent,GetMagickModule(), "No more image frames in list.");
986
422
          break;
987
422
        }
988
2.26k
      image = SyncNextImageInList(image);
989
2.26k
    } while (1);
990
991
  /* Rewind image list. */
992
2.68k
  while (image->previous != (Image *)NULL)
993
2.26k
    image=image->previous;
994
995
422
  status &= CloseBlob(image);
996
422
  return(status);
997
422
}