Coverage Report

Created: 2025-11-16 07:22

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