1/*
2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3%                                                                             %
4%                                                                             %
5%                                                                             %
6%                        FFFFF  IIIII  TTTTT  SSSSS                           %
7%                        F        I      T    SS                              %
8%                        FFF      I      T     SSS                            %
9%                        F        I      T       SS                           %
10%                        F      IIIII    T    SSSSS                           %
11%                                                                             %
12%                                                                             %
13%            Read/Write Flexible Image Transport System Images.               %
14%                                                                             %
15%                              Software Design                                %
16%                                   Cristy                                    %
17%                                 July 1992                                   %
18%                                                                             %
19%                                                                             %
20%  Copyright 1999-2016 ImageMagick Studio LLC, a non-profit organization      %
21%  dedicated to making software imaging solutions freely available.           %
22%                                                                             %
23%  You may not use this file except in compliance with the License.  You may  %
24%  obtain a copy of the License at                                            %
25%                                                                             %
26%    http://www.imagemagick.org/script/license.php                            %
27%                                                                             %
28%  Unless required by applicable law or agreed to in writing, software        %
29%  distributed under the License is distributed on an "AS IS" BASIS,          %
30%  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   %
31%  See the License for the specific language governing permissions and        %
32%  limitations under the License.                                             %
33%                                                                             %
34%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
35%
36%
37*/
38
39/*
40  Include declarations.
41*/
42#include "MagickCore/studio.h"
43#include "MagickCore/attribute.h"
44#include "MagickCore/blob.h"
45#include "MagickCore/blob-private.h"
46#include "MagickCore/cache.h"
47#include "MagickCore/color-private.h"
48#include "MagickCore/colorspace.h"
49#include "MagickCore/colorspace-private.h"
50#include "MagickCore/constitute.h"
51#include "MagickCore/exception.h"
52#include "MagickCore/exception-private.h"
53#include "MagickCore/image.h"
54#include "MagickCore/image-private.h"
55#include "MagickCore/list.h"
56#include "MagickCore/magick.h"
57#include "MagickCore/memory_.h"
58#include "MagickCore/module.h"
59#include "MagickCore/monitor.h"
60#include "MagickCore/monitor-private.h"
61#include "MagickCore/pixel-accessor.h"
62#include "MagickCore/property.h"
63#include "MagickCore/quantum-private.h"
64#include "MagickCore/static.h"
65#include "MagickCore/statistic.h"
66#include "MagickCore/string_.h"
67#include "MagickCore/string-private.h"
68#include "MagickCore/module.h"
69
70/*
71  Forward declarations.
72*/
73#define FITSBlocksize  2880UL
74
75/*
76  Forward declarations.
77*/
78static MagickBooleanType
79  WriteFITSImage(const ImageInfo *,Image *,ExceptionInfo *);
80
81/*
82%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
83%                                                                             %
84%                                                                             %
85%                                                                             %
86%   I s F I T S                                                               %
87%                                                                             %
88%                                                                             %
89%                                                                             %
90%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
91%
92%  IsFITS() returns MagickTrue if the image format type, identified by the
93%  magick string, is FITS.
94%
95%  The format of the IsFITS method is:
96%
97%      MagickBooleanType IsFITS(const unsigned char *magick,const size_t length)
98%
99%  A description of each parameter follows:
100%
101%    o magick: compare image format pattern against these bytes.
102%
103%    o length: Specifies the length of the magick string.
104%
105*/
106static MagickBooleanType IsFITS(const unsigned char *magick,const size_t length)
107{
108  if (length < 6)
109    return(MagickFalse);
110  if (LocaleNCompare((const char *) magick,"IT0",3) == 0)
111    return(MagickTrue);
112  if (LocaleNCompare((const char *) magick,"SIMPLE",6) == 0)
113    return(MagickTrue);
114  return(MagickFalse);
115}
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%  ReadFITSImage() reads a FITS image file and returns it.  It allocates the
129%  memory necessary for the new Image structure and returns a pointer to the
130%  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_info: the image info.
140%
141%    o exception: return any errors or warnings in this structure.
142%
143*/
144
145static inline double GetFITSPixel(Image *image,int bits_per_pixel)
146{
147  switch (image->depth >> 3)
148  {
149    case 1:
150      return((double) ReadBlobByte(image));
151    case 2:
152      return((double) ((short) ReadBlobShort(image)));
153    case 4:
154    {
155      if (bits_per_pixel > 0)
156        return((double) ReadBlobSignedLong(image));
157      return((double) ReadBlobFloat(image));
158    }
159    case 8:
160    {
161      if (bits_per_pixel > 0)
162        return((double) ((MagickOffsetType) ReadBlobLongLong(image)));
163    }
164    default:
165      break;
166  }
167  return(ReadBlobDouble(image));
168}
169
170static MagickOffsetType GetFITSPixelExtrema(Image *image,
171  const int bits_per_pixel,double *minima,double *maxima)
172{
173  double
174    pixel;
175
176  MagickOffsetType
177    offset;
178
179  MagickSizeType
180    number_pixels;
181
182  register MagickOffsetType
183    i;
184
185  offset=TellBlob(image);
186  if (offset == -1)
187    return(-1);
188  number_pixels=(MagickSizeType) image->columns*image->rows;
189  *minima=GetFITSPixel(image,bits_per_pixel);
190  *maxima=(*minima);
191  for (i=1; i < (MagickOffsetType) number_pixels; i++)
192  {
193    pixel=GetFITSPixel(image,bits_per_pixel);
194    if (pixel < *minima)
195      *minima=pixel;
196    if (pixel > *maxima)
197      *maxima=pixel;
198  }
199  return(SeekBlob(image,offset,SEEK_SET));
200}
201
202static inline double GetFITSPixelRange(const size_t depth)
203{
204  return((double) ((MagickOffsetType) GetQuantumRange(depth)));
205}
206
207static void SetFITSUnsignedPixels(const size_t length,
208  const size_t bits_per_pixel,const EndianType endian,unsigned char *pixels)
209{
210  register ssize_t
211    i;
212
213  if (endian != MSBEndian)
214    pixels+=(bits_per_pixel >> 3)-1;
215  for (i=0; i < (ssize_t) length; i++)
216  {
217    *pixels^=0x80;
218    pixels+=bits_per_pixel >> 3;
219  }
220}
221
222static Image *ReadFITSImage(const ImageInfo *image_info,
223  ExceptionInfo *exception)
224{
225  typedef struct _FITSInfo
226  {
227    MagickBooleanType
228      extend,
229      simple;
230
231    int
232      bits_per_pixel,
233      columns,
234      rows,
235      number_axes,
236      number_planes;
237
238    double
239      min_data,
240      max_data,
241      zero,
242      scale;
243
244    EndianType
245      endian;
246  } FITSInfo;
247
248  char
249    *comment,
250    keyword[9],
251    property[MagickPathExtent],
252    value[73];
253
254  double
255    pixel,
256    scale;
257
258  FITSInfo
259    fits_info;
260
261  Image
262    *image;
263
264  int
265    c;
266
267  MagickBooleanType
268    status;
269
270  MagickSizeType
271    number_pixels;
272
273  register ssize_t
274    i,
275    x;
276
277  register Quantum
278    *q;
279
280  ssize_t
281    count,
282    scene,
283    y;
284
285  /*
286    Open image file.
287  */
288  assert(image_info != (const ImageInfo *) NULL);
289  assert(image_info->signature == MagickCoreSignature);
290  if (image_info->debug != MagickFalse)
291    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",
292      image_info->filename);
293  assert(exception != (ExceptionInfo *) NULL);
294  assert(exception->signature == MagickCoreSignature);
295  image=AcquireImage(image_info,exception);
296  status=OpenBlob(image_info,image,ReadBinaryBlobMode,exception);
297  if (status == MagickFalse)
298    {
299      image=DestroyImageList(image);
300      return((Image *) NULL);
301    }
302  /*
303    Initialize image header.
304  */
305  (void) ResetMagickMemory(&fits_info,0,sizeof(fits_info));
306  fits_info.extend=MagickFalse;
307  fits_info.simple=MagickFalse;
308  fits_info.bits_per_pixel=8;
309  fits_info.columns=1;
310  fits_info.rows=1;
311  fits_info.number_planes=1;
312  fits_info.min_data=0.0;
313  fits_info.max_data=0.0;
314  fits_info.zero=0.0;
315  fits_info.scale=1.0;
316  fits_info.endian=MSBEndian;
317  /*
318    Decode image header.
319  */
320  for (comment=(char *) NULL; EOFBlob(image) == MagickFalse; )
321  {
322    for ( ; EOFBlob(image) == MagickFalse; )
323    {
324      register char
325        *p;
326
327      count=ReadBlob(image,8,(unsigned char *) keyword);
328      if (count != 8)
329        break;
330      for (i=0; i < 8; i++)
331      {
332        if (isspace((int) ((unsigned char) keyword[i])) != 0)
333          break;
334        keyword[i]=tolower((int) ((unsigned char) keyword[i]));
335      }
336      keyword[i]='\0';
337      count=ReadBlob(image,72,(unsigned char *) value);
338      value[72]='\0';
339      if (count != 72)
340        break;
341      p=value;
342      if (*p == '=')
343        {
344          p+=2;
345          while (isspace((int) ((unsigned char) *p)) != 0)
346            p++;
347        }
348      if (LocaleCompare(keyword,"end") == 0)
349        break;
350      if (LocaleCompare(keyword,"extend") == 0)
351        fits_info.extend=(*p == 'T') || (*p == 't') ? MagickTrue : MagickFalse;
352      if (LocaleCompare(keyword,"simple") == 0)
353        fits_info.simple=(*p == 'T') || (*p == 't') ? MagickTrue : MagickFalse;
354      if (LocaleCompare(keyword,"bitpix") == 0)
355        fits_info.bits_per_pixel=StringToLong(p);
356      if (LocaleCompare(keyword,"naxis") == 0)
357        fits_info.number_axes=StringToLong(p);
358      if (LocaleCompare(keyword,"naxis1") == 0)
359        fits_info.columns=StringToLong(p);
360      if (LocaleCompare(keyword,"naxis2") == 0)
361        fits_info.rows=StringToLong(p);
362      if (LocaleCompare(keyword,"naxis3") == 0)
363        fits_info.number_planes=StringToLong(p);
364      if (LocaleCompare(keyword,"datamax") == 0)
365        fits_info.max_data=StringToDouble(p,(char **) NULL);
366      if (LocaleCompare(keyword,"datamin") == 0)
367        fits_info.min_data=StringToDouble(p,(char **) NULL);
368      if (LocaleCompare(keyword,"bzero") == 0)
369        fits_info.zero=StringToDouble(p,(char **) NULL);
370      if (LocaleCompare(keyword,"bscale") == 0)
371        fits_info.scale=StringToDouble(p,(char **) NULL);
372      if (LocaleCompare(keyword,"comment") == 0)
373        {
374          if (comment == (char *) NULL)
375            comment=ConstantString(p);
376          else
377            (void) ConcatenateString(&comment,p);
378        }
379      if (LocaleCompare(keyword,"xendian") == 0)
380        {
381          if (LocaleNCompare(p,"big",3) == 0)
382            fits_info.endian=MSBEndian;
383          else
384            fits_info.endian=LSBEndian;
385        }
386      (void) FormatLocaleString(property,MagickPathExtent,"fits:%s",keyword);
387      (void) SetImageProperty(image,property,p,exception);
388    }
389    c=0;
390    while (((TellBlob(image) % FITSBlocksize) != 0) && (c != EOF))
391      c=ReadBlobByte(image);
392    if (fits_info.extend == MagickFalse)
393      break;
394    if ((fits_info.bits_per_pixel != 8) && (fits_info.bits_per_pixel != 16) &&
395        (fits_info.bits_per_pixel != 32) && (fits_info.bits_per_pixel != 64) &&
396        (fits_info.bits_per_pixel != -32) && (fits_info.bits_per_pixel != -64))
397      ThrowReaderException(CorruptImageError,"ImproperImageHeader");
398    number_pixels=(MagickSizeType) fits_info.columns*fits_info.rows;
399    if ((fits_info.simple != MagickFalse) && (fits_info.number_axes >= 1) &&
400        (fits_info.number_axes <= 4) && (number_pixels != 0))
401      break;
402  }
403  /*
404    Verify that required image information is defined.
405  */
406  if (comment != (char *) NULL)
407    {
408      (void) SetImageProperty(image,"comment",comment,exception);
409      comment=DestroyString(comment);
410    }
411  if (EOFBlob(image) != MagickFalse)
412    ThrowFileException(exception,CorruptImageError,"UnexpectedEndOfFile",
413      image->filename);
414  number_pixels=(MagickSizeType) fits_info.columns*fits_info.rows;
415  if ((fits_info.simple == MagickFalse) || (fits_info.number_axes < 1) ||
416      (fits_info.number_axes > 4) || (number_pixels == 0))
417    ThrowReaderException(CorruptImageError,"ImageTypeNotSupported");
418  for (scene=0; scene < (ssize_t) fits_info.number_planes; scene++)
419  {
420    image->columns=(size_t) fits_info.columns;
421    image->rows=(size_t) fits_info.rows;
422    image->depth=(size_t) (fits_info.bits_per_pixel < 0 ? -1 : 1)*
423      fits_info.bits_per_pixel;
424    image->endian=fits_info.endian;
425    image->scene=(size_t) scene;
426    if ((image_info->ping != MagickFalse) && (image_info->number_scenes != 0))
427      if (image->scene >= (image_info->scene+image_info->number_scenes-1))
428        break;
429    status=SetImageExtent(image,image->columns,image->rows,exception);
430    if (status == MagickFalse)
431      return(DestroyImageList(image));
432    /*
433      Initialize image structure.
434    */
435    (void) SetImageColorspace(image,GRAYColorspace,exception);
436    if ((fits_info.min_data == 0.0) && (fits_info.max_data == 0.0))
437      {
438        if (fits_info.zero == 0.0)
439          (void) GetFITSPixelExtrema(image,fits_info.bits_per_pixel,
440            &fits_info.min_data,&fits_info.max_data);
441        else
442          fits_info.max_data=GetFITSPixelRange((size_t)
443            fits_info.bits_per_pixel);
444      }
445    else
446      fits_info.max_data=GetFITSPixelRange((size_t) fits_info.bits_per_pixel);
447    /*
448      Convert FITS pixels to pixel packets.
449    */
450    scale=QuantumRange/(fits_info.max_data-fits_info.min_data);
451    for (y=(ssize_t) image->rows-1; y >= 0; y--)
452    {
453      q=QueueAuthenticPixels(image,0,y,image->columns,1,exception);
454      if (q == (Quantum *) NULL)
455        break;
456      for (x=0; x < (ssize_t) image->columns; x++)
457      {
458        pixel=GetFITSPixel(image,fits_info.bits_per_pixel);
459        if ((image->depth == 16) || (image->depth == 32) ||
460            (image->depth == 64))
461          SetFITSUnsignedPixels(1,image->depth,image->endian,
462            (unsigned char *) &pixel);
463        SetPixelGray(image,ClampToQuantum(scale*(fits_info.scale*(pixel-
464          fits_info.min_data)+fits_info.zero)),q);
465        q+=GetPixelChannels(image);
466      }
467      if (SyncAuthenticPixels(image,exception) == MagickFalse)
468        break;
469      if (image->previous == (Image *) NULL)
470        {
471          status=SetImageProgress(image,LoadImageTag,(MagickOffsetType) y,
472            image->rows);
473          if (status == MagickFalse)
474            break;
475        }
476    }
477    if (EOFBlob(image) != MagickFalse)
478      {
479        ThrowFileException(exception,CorruptImageError,"UnexpectedEndOfFile",
480          image->filename);
481        break;
482      }
483    /*
484      Proceed to next image.
485    */
486    if (image_info->number_scenes != 0)
487      if (image->scene >= (image_info->scene+image_info->number_scenes-1))
488        break;
489    if (scene < (ssize_t) (fits_info.number_planes-1))
490      {
491        /*
492          Allocate next image structure.
493        */
494        AcquireNextImage(image_info,image,exception);
495        if (GetNextImageInList(image) == (Image *) NULL)
496          {
497            image=DestroyImageList(image);
498            return((Image *) NULL);
499          }
500        image=SyncNextImageInList(image);
501        status=SetImageProgress(image,LoadImagesTag,TellBlob(image),
502          GetBlobSize(image));
503        if (status == MagickFalse)
504          break;
505      }
506  }
507  (void) CloseBlob(image);
508  return(GetFirstImageInList(image));
509}
510
511/*
512%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
513%                                                                             %
514%                                                                             %
515%                                                                             %
516%   R e g i s t e r F I T S I m a g e                                         %
517%                                                                             %
518%                                                                             %
519%                                                                             %
520%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
521%
522%  RegisterFITSImage() adds attributes for the FITS image format to
523%  the list of supported formats.  The attributes include the image format
524%  tag, a method to read and/or write the format, whether the format
525%  supports the saving of more than one frame to the same file or blob,
526%  whether the format supports native in-memory I/O, and a brief
527%  description of the format.
528%
529%  The format of the RegisterFITSImage method is:
530%
531%      size_t RegisterFITSImage(void)
532%
533*/
534ModuleExport size_t RegisterFITSImage(void)
535{
536  MagickInfo
537    *entry;
538
539  entry=AcquireMagickInfo("FITS","FITS","Flexible Image Transport System");
540  entry->decoder=(DecodeImageHandler *) ReadFITSImage;
541  entry->encoder=(EncodeImageHandler *) WriteFITSImage;
542  entry->magick=(IsImageFormatHandler *) IsFITS;
543  entry->flags^=CoderAdjoinFlag;
544  entry->flags|=CoderSeekableStreamFlag;
545  (void) RegisterMagickInfo(entry);
546  entry=AcquireMagickInfo("FITS","FTS","Flexible Image Transport System");
547  entry->decoder=(DecodeImageHandler *) ReadFITSImage;
548  entry->encoder=(EncodeImageHandler *) WriteFITSImage;
549  entry->magick=(IsImageFormatHandler *) IsFITS;
550  entry->flags^=CoderAdjoinFlag;
551  entry->flags|=CoderSeekableStreamFlag;
552  (void) RegisterMagickInfo(entry);
553  return(MagickImageCoderSignature);
554}
555
556/*
557%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
558%                                                                             %
559%                                                                             %
560%                                                                             %
561%   U n r e g i s t e r F I T S I m a g e                                     %
562%                                                                             %
563%                                                                             %
564%                                                                             %
565%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
566%
567%  UnregisterFITSImage() removes format registrations made by the
568%  FITS module from the list of supported formats.
569%
570%  The format of the UnregisterFITSImage method is:
571%
572%      UnregisterFITSImage(void)
573%
574*/
575ModuleExport void UnregisterFITSImage(void)
576{
577  (void) UnregisterMagickInfo("FITS");
578  (void) UnregisterMagickInfo("FTS");
579}
580
581/*
582%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
583%                                                                             %
584%                                                                             %
585%                                                                             %
586%   W r i t e F I T S I m a g e                                               %
587%                                                                             %
588%                                                                             %
589%                                                                             %
590%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
591%
592%  WriteFITSImage() writes a Flexible Image Transport System image to a
593%  file as gray scale intensities [0..255].
594%
595%  The format of the WriteFITSImage method is:
596%
597%      MagickBooleanType WriteFITSImage(const ImageInfo *image_info,
598%        Image *image,ExceptionInfo *exception)
599%
600%  A description of each parameter follows.
601%
602%    o image_info: the image info.
603%
604%    o image:  The image.
605%
606%    o exception: return any errors or warnings in this structure.
607%
608*/
609static MagickBooleanType WriteFITSImage(const ImageInfo *image_info,
610  Image *image,ExceptionInfo *exception)
611{
612  char
613    header[FITSBlocksize],
614    *fits_info;
615
616  MagickBooleanType
617    status;
618
619  QuantumInfo
620    *quantum_info;
621
622  register const Quantum
623    *p;
624
625  size_t
626    length;
627
628  ssize_t
629    count,
630    offset,
631    y;
632
633  unsigned char
634    *pixels;
635
636  /*
637    Open output image file.
638  */
639  assert(image_info != (const ImageInfo *) NULL);
640  assert(image_info->signature == MagickCoreSignature);
641  assert(image != (Image *) NULL);
642  assert(image->signature == MagickCoreSignature);
643  if (image->debug != MagickFalse)
644    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
645  assert(exception != (ExceptionInfo *) NULL);
646  assert(exception->signature == MagickCoreSignature);
647  status=OpenBlob(image_info,image,WriteBinaryBlobMode,exception);
648  if (status == MagickFalse)
649    return(status);
650  (void) TransformImageColorspace(image,sRGBColorspace,exception);
651  /*
652    Allocate image memory.
653  */
654  fits_info=(char *) AcquireQuantumMemory(FITSBlocksize,sizeof(*fits_info));
655  if (fits_info == (char *) NULL)
656    ThrowWriterException(ResourceLimitError,"MemoryAllocationFailed");
657  (void) ResetMagickMemory(fits_info,' ',FITSBlocksize*sizeof(*fits_info));
658  /*
659    Initialize image header.
660  */
661  image->depth=GetImageQuantumDepth(image,MagickFalse);
662  image->endian=MSBEndian;
663  quantum_info=AcquireQuantumInfo(image_info,image);
664  if (quantum_info == (QuantumInfo *) NULL)
665    ThrowWriterException(ResourceLimitError,"MemoryAllocationFailed");
666  offset=0;
667  (void) FormatLocaleString(header,FITSBlocksize,
668    "SIMPLE  =                    T");
669  (void) strncpy(fits_info+offset,header,strlen(header));
670  offset+=80;
671  (void) FormatLocaleString(header,FITSBlocksize,"BITPIX  =           %10ld",
672    (long) ((quantum_info->format == FloatingPointQuantumFormat ? -1 : 1)*
673    image->depth));
674  (void) strncpy(fits_info+offset,header,strlen(header));
675  offset+=80;
676  (void) FormatLocaleString(header,FITSBlocksize,"NAXIS   =           %10lu",
677    SetImageGray(image,exception) != MagickFalse ? 2UL : 3UL);
678  (void) strncpy(fits_info+offset,header,strlen(header));
679  offset+=80;
680  (void) FormatLocaleString(header,FITSBlocksize,"NAXIS1  =           %10lu",
681    (unsigned long) image->columns);
682  (void) strncpy(fits_info+offset,header,strlen(header));
683  offset+=80;
684  (void) FormatLocaleString(header,FITSBlocksize,"NAXIS2  =           %10lu",
685    (unsigned long) image->rows);
686  (void) strncpy(fits_info+offset,header,strlen(header));
687  offset+=80;
688  if (SetImageGray(image,exception) == MagickFalse)
689    {
690      (void) FormatLocaleString(header,FITSBlocksize,
691        "NAXIS3  =           %10lu",3UL);
692      (void) strncpy(fits_info+offset,header,strlen(header));
693      offset+=80;
694    }
695  (void) FormatLocaleString(header,FITSBlocksize,"BSCALE  =         %E",1.0);
696  (void) strncpy(fits_info+offset,header,strlen(header));
697  offset+=80;
698  (void) FormatLocaleString(header,FITSBlocksize,"BZERO   =         %E",
699    image->depth > 8 ? GetFITSPixelRange(image->depth)/2.0 : 0.0);
700  (void) strncpy(fits_info+offset,header,strlen(header));
701  offset+=80;
702  (void) FormatLocaleString(header,FITSBlocksize,"DATAMAX =         %E",
703    1.0*((MagickOffsetType) GetQuantumRange(image->depth)));
704  (void) strncpy(fits_info+offset,header,strlen(header));
705  offset+=80;
706  (void) FormatLocaleString(header,FITSBlocksize,"DATAMIN =         %E",0.0);
707  (void) strncpy(fits_info+offset,header,strlen(header));
708  offset+=80;
709  if (image->endian == LSBEndian)
710    {
711      (void) FormatLocaleString(header,FITSBlocksize,"XENDIAN = 'SMALL'");
712      (void) strncpy(fits_info+offset,header,strlen(header));
713      offset+=80;
714    }
715  (void) FormatLocaleString(header,FITSBlocksize,"HISTORY %.72s",
716    GetMagickVersion((size_t *) NULL));
717  (void) strncpy(fits_info+offset,header,strlen(header));
718  offset+=80;
719  (void) strncpy(header,"END",FITSBlocksize);
720  (void) strncpy(fits_info+offset,header,strlen(header));
721  offset+=80;
722  (void) WriteBlob(image,FITSBlocksize,(unsigned char *) fits_info);
723  /*
724    Convert image to fits scale PseudoColor class.
725  */
726  pixels=(unsigned char *) GetQuantumPixels(quantum_info);
727  if (SetImageGray(image,exception) != MagickFalse)
728    {
729      length=GetQuantumExtent(image,quantum_info,GrayQuantum);
730      for (y=(ssize_t) image->rows-1; y >= 0; y--)
731      {
732        p=GetVirtualPixels(image,0,y,image->columns,1,exception);
733        if (p == (const Quantum *) NULL)
734          break;
735        length=ExportQuantumPixels(image,(CacheView *) NULL,quantum_info,
736          GrayQuantum,pixels,exception);
737        if (image->depth == 16)
738          SetFITSUnsignedPixels(image->columns,image->depth,image->endian,
739            pixels);
740        if (((image->depth == 32) || (image->depth == 64)) &&
741            (quantum_info->format != FloatingPointQuantumFormat))
742          SetFITSUnsignedPixels(image->columns,image->depth,image->endian,
743            pixels);
744        count=WriteBlob(image,length,pixels);
745        if (count != (ssize_t) length)
746          break;
747        status=SetImageProgress(image,SaveImageTag,(MagickOffsetType) y,
748          image->rows);
749        if (status == MagickFalse)
750          break;
751      }
752    }
753  else
754    {
755      length=GetQuantumExtent(image,quantum_info,RedQuantum);
756      for (y=(ssize_t) image->rows-1; y >= 0; y--)
757      {
758        p=GetVirtualPixels(image,0,y,image->columns,1,exception);
759        if (p == (const Quantum *) NULL)
760          break;
761        length=ExportQuantumPixels(image,(CacheView *) NULL,quantum_info,
762          RedQuantum,pixels,exception);
763        if (image->depth == 16)
764          SetFITSUnsignedPixels(image->columns,image->depth,image->endian,
765            pixels);
766        if (((image->depth == 32) || (image->depth == 64)) &&
767            (quantum_info->format != FloatingPointQuantumFormat))
768          SetFITSUnsignedPixels(image->columns,image->depth,image->endian,
769            pixels);
770        count=WriteBlob(image,length,pixels);
771        if (count != (ssize_t) length)
772          break;
773        status=SetImageProgress(image,SaveImageTag,(MagickOffsetType) y,
774          image->rows);
775        if (status == MagickFalse)
776          break;
777      }
778      length=GetQuantumExtent(image,quantum_info,GreenQuantum);
779      for (y=(ssize_t) image->rows-1; y >= 0; y--)
780      {
781        p=GetVirtualPixels(image,0,y,image->columns,1,exception);
782        if (p == (const Quantum *) NULL)
783          break;
784        length=ExportQuantumPixels(image,(CacheView *) NULL,quantum_info,
785          GreenQuantum,pixels,exception);
786        if (image->depth == 16)
787          SetFITSUnsignedPixels(image->columns,image->depth,image->endian,
788            pixels);
789        if (((image->depth == 32) || (image->depth == 64)) &&
790            (quantum_info->format != FloatingPointQuantumFormat))
791          SetFITSUnsignedPixels(image->columns,image->depth,image->endian,
792            pixels);
793        count=WriteBlob(image,length,pixels);
794        if (count != (ssize_t) length)
795          break;
796        status=SetImageProgress(image,SaveImageTag,(MagickOffsetType) y,
797          image->rows);
798        if (status == MagickFalse)
799          break;
800      }
801      length=GetQuantumExtent(image,quantum_info,BlueQuantum);
802      for (y=(ssize_t) image->rows-1; y >= 0; y--)
803      {
804        p=GetVirtualPixels(image,0,y,image->columns,1,exception);
805        if (p == (const Quantum *) NULL)
806          break;
807        length=ExportQuantumPixels(image,(CacheView *) NULL,quantum_info,
808          BlueQuantum,pixels,exception);
809        if (image->depth == 16)
810          SetFITSUnsignedPixels(image->columns,image->depth,image->endian,
811            pixels);
812        if (((image->depth == 32) || (image->depth == 64)) &&
813            (quantum_info->format != FloatingPointQuantumFormat))
814          SetFITSUnsignedPixels(image->columns,image->depth,image->endian,
815            pixels);
816        count=WriteBlob(image,length,pixels);
817        if (count != (ssize_t) length)
818          break;
819        status=SetImageProgress(image,SaveImageTag,(MagickOffsetType) y,
820          image->rows);
821        if (status == MagickFalse)
822          break;
823      }
824    }
825  quantum_info=DestroyQuantumInfo(quantum_info);
826  length=(size_t) (FITSBlocksize-TellBlob(image) % FITSBlocksize);
827  if (length != 0)
828    {
829      (void) ResetMagickMemory(fits_info,0,length*sizeof(*fits_info));
830      (void) WriteBlob(image,length,(unsigned char *) fits_info);
831    }
832  fits_info=DestroyString(fits_info);
833  (void) CloseBlob(image);
834  return(MagickTrue);
835}
836