]> granicus.if.org Git - imagemagick/blob - coders/fits.c
(no commit message)
[imagemagick] / coders / fits.c
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 %                                John Cristy                                  %
17 %                                 July 1992                                   %
18 %                                                                             %
19 %                                                                             %
20 %  Copyright 1999-2013 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 \f
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 \f
70 /*
71   Forward declarations.
72 */
73 #define FITSBlocksize  2880UL
74 \f
75 /*
76   Forward declarations.
77 */
78 static MagickBooleanType
79   WriteFITSImage(const ImageInfo *,Image *,ExceptionInfo *);
80 \f
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 */
106 static 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 \f
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
145 static 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) ((int) ReadBlobLong(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
170 static void GetFITSPixelExtrema(Image *image,const int bits_per_pixel,
171   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   number_pixels=(MagickSizeType) image->columns*image->rows;
187   *minima=GetFITSPixel(image,bits_per_pixel);
188   *maxima=(*minima);
189   for (i=1; i < (MagickOffsetType) number_pixels; i++)
190   {
191     pixel=GetFITSPixel(image,bits_per_pixel);
192     if (pixel < *minima)
193       *minima=pixel;
194     if (pixel > *maxima)
195       *maxima=pixel;
196   }
197   (void) SeekBlob(image,offset,SEEK_SET);
198 }
199
200 static inline double GetFITSPixelRange(const size_t depth)
201 {
202   return((double) ((MagickOffsetType) GetQuantumRange(depth)));
203 }
204
205 static void SetFITSUnsignedPixels(const size_t length,
206   const size_t bits_per_pixel,const EndianType endian,unsigned char *pixels)
207 {
208   register ssize_t
209     i;
210
211   if (endian != MSBEndian)
212     pixels+=(bits_per_pixel >> 3)-1;
213   for (i=0; i < (ssize_t) length; i++)
214   {
215     *pixels^=0x80;
216     pixels+=bits_per_pixel >> 3;
217   }
218 }
219
220 static Image *ReadFITSImage(const ImageInfo *image_info,
221   ExceptionInfo *exception)
222 {
223   typedef struct _FITSInfo
224   {
225     MagickBooleanType
226       extend,
227       simple;
228
229     int
230       bits_per_pixel,
231       columns,
232       rows,
233       number_axes,
234       number_planes;
235
236     double
237       min_data,
238       max_data,
239       zero,
240       scale;
241
242     EndianType
243       endian;
244   } FITSInfo;
245
246   char
247     *comment,
248     keyword[9],
249     property[MaxTextExtent],
250     value[73];
251
252   double
253     pixel,
254     scale;
255
256   FITSInfo
257     fits_info;
258
259   Image
260     *image;
261
262   int
263     c;
264
265   MagickBooleanType
266     status;
267
268   MagickSizeType
269     number_pixels;
270
271   register ssize_t
272     i,
273     x;
274
275   register Quantum
276     *q;
277
278   ssize_t
279     count,
280     scene,
281     y;
282
283   /*
284     Open image file.
285   */
286   assert(image_info != (const ImageInfo *) NULL);
287   assert(image_info->signature == MagickSignature);
288   if (image_info->debug != MagickFalse)
289     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",
290       image_info->filename);
291   assert(exception != (ExceptionInfo *) NULL);
292   assert(exception->signature == MagickSignature);
293   image=AcquireImage(image_info,exception);
294   status=OpenBlob(image_info,image,ReadBinaryBlobMode,exception);
295   if (status == MagickFalse)
296     {
297       image=DestroyImageList(image);
298       return((Image *) NULL);
299     }
300   /*
301     Initialize image header.
302   */
303   (void) ResetMagickMemory(&fits_info,0,sizeof(fits_info));
304   fits_info.extend=MagickFalse;
305   fits_info.simple=MagickFalse;
306   fits_info.bits_per_pixel=8;
307   fits_info.columns=1;
308   fits_info.rows=1;
309   fits_info.rows=1;
310   fits_info.number_planes=1;
311   fits_info.min_data=0.0;
312   fits_info.max_data=0.0;
313   fits_info.zero=0.0;
314   fits_info.scale=1.0;
315   fits_info.endian=MSBEndian;
316   /*
317     Decode image header.
318   */
319   for (comment=(char *) NULL; EOFBlob(image) == MagickFalse; )
320   {
321     for ( ; EOFBlob(image) == MagickFalse; )
322     {
323       register char
324         *p;
325
326       count=ReadBlob(image,8,(unsigned char *) keyword);
327       if (count != 8)
328         break;
329       for (i=0; i < 8; i++)
330       {
331         if (isspace((int) ((unsigned char) keyword[i])) != 0)
332           break;
333         keyword[i]=tolower((int) ((unsigned char) keyword[i]));
334       }
335       keyword[i]='\0';
336       count=ReadBlob(image,72,(unsigned char *) value);
337       if (count != 72)
338         break;
339       value[72]='\0';
340       p=value;
341       if (*p == '=')
342         {
343           p+=2;
344           while (isspace((int) ((unsigned char) *p)) != 0)
345             p++;
346         }
347       if (LocaleCompare(keyword,"end") == 0)
348         break;
349       if (LocaleCompare(keyword,"extend") == 0)
350         fits_info.extend=(*p == 'T') || (*p == 't') ? MagickTrue : MagickFalse;
351       if (LocaleCompare(keyword,"simple") == 0)
352         fits_info.simple=(*p == 'T') || (*p == 't') ? MagickTrue : MagickFalse;
353       if (LocaleCompare(keyword,"bitpix") == 0)
354         fits_info.bits_per_pixel=StringToLong(p);
355       if (LocaleCompare(keyword,"naxis") == 0)
356         fits_info.number_axes=StringToLong(p);
357       if (LocaleCompare(keyword,"naxis1") == 0)
358         fits_info.columns=StringToLong(p);
359       if (LocaleCompare(keyword,"naxis2") == 0)
360         fits_info.rows=StringToLong(p);
361       if (LocaleCompare(keyword,"naxis3") == 0)
362         fits_info.number_planes=StringToLong(p);
363       if (LocaleCompare(keyword,"datamax") == 0)
364         fits_info.max_data=StringToDouble(p,(char **) NULL);
365       if (LocaleCompare(keyword,"datamin") == 0)
366         fits_info.min_data=StringToDouble(p,(char **) NULL);
367       if (LocaleCompare(keyword,"bzero") == 0)
368         fits_info.zero=StringToDouble(p,(char **) NULL);
369       if (LocaleCompare(keyword,"bscale") == 0)
370         fits_info.scale=StringToDouble(p,(char **) NULL);
371       if (LocaleCompare(keyword,"comment") == 0)
372         {
373           if (comment == (char *) NULL)
374             comment=ConstantString(p);
375           else
376             (void) ConcatenateString(&comment,p);
377         }
378       if (LocaleCompare(keyword,"xendian") == 0)
379         {
380           if (LocaleNCompare(p,"big",3) == 0)
381             fits_info.endian=MSBEndian;
382           else
383             fits_info.endian=LSBEndian;
384         }
385       (void) FormatLocaleString(property,MaxTextExtent,"fits:%s",keyword);
386       (void) SetImageProperty(image,property,p,exception);
387     }
388     c=0;
389     while (((TellBlob(image) % FITSBlocksize) != 0) && (c != EOF))
390       c=ReadBlobByte(image);
391     if (fits_info.extend == MagickFalse)
392       break;
393     number_pixels=(MagickSizeType) fits_info.columns*fits_info.rows;
394     if ((fits_info.simple != MagickFalse) && (fits_info.number_axes >= 1) &&
395         (fits_info.number_axes <= 4) && (number_pixels != 0))
396       break;
397   }
398   /*
399     Verify that required image information is defined.
400   */
401   if (comment != (char *) NULL)
402     {
403       (void) SetImageProperty(image,"comment",comment,exception);
404       comment=DestroyString(comment);
405     }
406   if (EOFBlob(image) != MagickFalse)
407     ThrowFileException(exception,CorruptImageError,"UnexpectedEndOfFile",
408       image->filename);
409   number_pixels=(MagickSizeType) fits_info.columns*fits_info.rows;
410   if ((fits_info.simple == MagickFalse) || (fits_info.number_axes < 1) ||
411       (fits_info.number_axes > 4) || (number_pixels == 0))
412     ThrowReaderException(CorruptImageError,"ImageTypeNotSupported");
413   for (scene=0; scene < (ssize_t) fits_info.number_planes; scene++)
414   {
415     image->columns=(size_t) fits_info.columns;
416     image->rows=(size_t) fits_info.rows;
417     image->depth=(size_t) (fits_info.bits_per_pixel < 0 ? -1 : 1)*
418       fits_info.bits_per_pixel;
419     image->endian=fits_info.endian;
420     image->scene=(size_t) scene;
421     if ((image_info->ping != MagickFalse) && (image_info->number_scenes != 0))
422       if (image->scene >= (image_info->scene+image_info->number_scenes-1))
423         break;
424     /*
425       Initialize image structure.
426     */
427     (void) SetImageColorspace(image,GRAYColorspace,exception);
428     if ((fits_info.min_data == 0.0) && (fits_info.max_data == 0.0))
429       {
430         if (fits_info.zero == 0.0)
431           GetFITSPixelExtrema(image,fits_info.bits_per_pixel,
432             &fits_info.min_data,&fits_info.max_data);
433         else
434           fits_info.max_data=GetFITSPixelRange((size_t)
435             fits_info.bits_per_pixel);
436       }
437     else
438       fits_info.max_data=GetFITSPixelRange((size_t) fits_info.bits_per_pixel);
439     /*
440       Convert FITS pixels to pixel packets.
441     */
442     scale=QuantumRange/(fits_info.max_data-fits_info.min_data);
443     for (y=(ssize_t) image->rows-1; y >= 0; y--)
444     {
445       q=QueueAuthenticPixels(image,0,y,image->columns,1,exception);
446       if (q == (Quantum *) NULL)
447         break;
448       for (x=0; x < (ssize_t) image->columns; x++)
449       {
450         pixel=GetFITSPixel(image,fits_info.bits_per_pixel);
451         if ((image->depth == 16) || (image->depth == 32) ||
452             (image->depth == 64))
453           SetFITSUnsignedPixels(1,image->depth,image->endian,
454             (unsigned char *) &pixel);
455         SetPixelGray(image,ClampToQuantum(scale*(fits_info.scale*(pixel-
456           fits_info.min_data)+fits_info.zero)),q);
457         q+=GetPixelChannels(image);
458       }
459       if (SyncAuthenticPixels(image,exception) == MagickFalse)
460         break;
461       if (image->previous == (Image *) NULL)
462         {
463           status=SetImageProgress(image,LoadImageTag,(MagickOffsetType) y,
464             image->rows);
465           if (status == MagickFalse)
466             break;
467         }
468     }
469     if (EOFBlob(image) != MagickFalse)
470       {
471         ThrowFileException(exception,CorruptImageError,"UnexpectedEndOfFile",
472           image->filename);
473         break;
474       }
475     /*
476       Proceed to next image.
477     */
478     if (image_info->number_scenes != 0)
479       if (image->scene >= (image_info->scene+image_info->number_scenes-1))
480         break;
481     if (scene < (ssize_t) (fits_info.number_planes-1))
482       {
483         /*
484           Allocate next image structure.
485         */
486         AcquireNextImage(image_info,image,exception);
487         if (GetNextImageInList(image) == (Image *) NULL)
488           {
489             image=DestroyImageList(image);
490             return((Image *) NULL);
491           }
492         image=SyncNextImageInList(image);
493         status=SetImageProgress(image,LoadImagesTag,TellBlob(image),
494           GetBlobSize(image));
495         if (status == MagickFalse)
496           break;
497       }
498   }
499   (void) CloseBlob(image);
500   return(GetFirstImageInList(image));
501 }
502 \f
503 /*
504 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
505 %                                                                             %
506 %                                                                             %
507 %                                                                             %
508 %   R e g i s t e r F I T S I m a g e                                         %
509 %                                                                             %
510 %                                                                             %
511 %                                                                             %
512 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
513 %
514 %  RegisterFITSImage() adds attributes for the FITS image format to
515 %  the list of supported formats.  The attributes include the image format
516 %  tag, a method to read and/or write the format, whether the format
517 %  supports the saving of more than one frame to the same file or blob,
518 %  whether the format supports native in-memory I/O, and a brief
519 %  description of the format.
520 %
521 %  The format of the RegisterFITSImage method is:
522 %
523 %      size_t RegisterFITSImage(void)
524 %
525 */
526 ModuleExport size_t RegisterFITSImage(void)
527 {
528   MagickInfo
529     *entry;
530
531   entry=SetMagickInfo("FITS");
532   entry->decoder=(DecodeImageHandler *) ReadFITSImage;
533   entry->encoder=(EncodeImageHandler *) WriteFITSImage;
534   entry->magick=(IsImageFormatHandler *) IsFITS;
535   entry->adjoin=MagickFalse;
536   entry->seekable_stream=MagickTrue;
537   entry->description=ConstantString("Flexible Image Transport System");
538   entry->module=ConstantString("FITS");
539   (void) RegisterMagickInfo(entry);
540   entry=SetMagickInfo("FTS");
541   entry->decoder=(DecodeImageHandler *) ReadFITSImage;
542   entry->encoder=(EncodeImageHandler *) WriteFITSImage;
543   entry->magick=(IsImageFormatHandler *) IsFITS;
544   entry->adjoin=MagickFalse;
545   entry->seekable_stream=MagickTrue;
546   entry->description=ConstantString("Flexible Image Transport System");
547   entry->module=ConstantString("FTS");
548   (void) RegisterMagickInfo(entry);
549   return(MagickImageCoderSignature);
550 }
551 \f
552 /*
553 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
554 %                                                                             %
555 %                                                                             %
556 %                                                                             %
557 %   U n r e g i s t e r F I T S I m a g e                                     %
558 %                                                                             %
559 %                                                                             %
560 %                                                                             %
561 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
562 %
563 %  UnregisterFITSImage() removes format registrations made by the
564 %  FITS module from the list of supported formats.
565 %
566 %  The format of the UnregisterFITSImage method is:
567 %
568 %      UnregisterFITSImage(void)
569 %
570 */
571 ModuleExport void UnregisterFITSImage(void)
572 {
573   (void) UnregisterMagickInfo("FITS");
574   (void) UnregisterMagickInfo("FTS");
575 }
576 \f
577 /*
578 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
579 %                                                                             %
580 %                                                                             %
581 %                                                                             %
582 %   W r i t e F I T S I m a g e                                               %
583 %                                                                             %
584 %                                                                             %
585 %                                                                             %
586 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
587 %
588 %  WriteFITSImage() writes a Flexible Image Transport System image to a
589 %  file as gray scale intensities [0..255].
590 %
591 %  The format of the WriteFITSImage method is:
592 %
593 %      MagickBooleanType WriteFITSImage(const ImageInfo *image_info,
594 %        Image *image,ExceptionInfo *exception)
595 %
596 %  A description of each parameter follows.
597 %
598 %    o image_info: the image info.
599 %
600 %    o image:  The image.
601 %
602 %    o exception: return any errors or warnings in this structure.
603 %
604 */
605 static MagickBooleanType WriteFITSImage(const ImageInfo *image_info,
606   Image *image,ExceptionInfo *exception)
607 {
608   char
609     header[FITSBlocksize],
610     *fits_info;
611
612   MagickBooleanType
613     status;
614
615   QuantumInfo
616     *quantum_info;
617
618   register const Quantum
619     *p;
620
621   size_t
622     length;
623
624   ssize_t
625     count,
626     offset,
627     y;
628
629   unsigned char
630     *pixels;
631
632   /*
633     Open output image file.
634   */
635   assert(image_info != (const ImageInfo *) NULL);
636   assert(image_info->signature == MagickSignature);
637   assert(image != (Image *) NULL);
638   assert(image->signature == MagickSignature);
639   if (image->debug != MagickFalse)
640     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
641   assert(exception != (ExceptionInfo *) NULL);
642   assert(exception->signature == MagickSignature);
643   status=OpenBlob(image_info,image,WriteBinaryBlobMode,exception);
644   if (status == MagickFalse)
645     return(status);
646   if (IssRGBCompatibleColorspace(image->colorspace) == MagickFalse)
647     (void) TransformImageColorspace(image,sRGBColorspace,exception);
648   /*
649     Allocate image memory.
650   */
651   fits_info=(char *) AcquireQuantumMemory(FITSBlocksize,sizeof(*fits_info));
652   if (fits_info == (char *) NULL)
653     ThrowWriterException(ResourceLimitError,"MemoryAllocationFailed");
654   (void) ResetMagickMemory(fits_info,' ',FITSBlocksize*sizeof(*fits_info));
655   /*
656     Initialize image header.
657   */
658   image->depth=GetImageQuantumDepth(image,MagickFalse);
659   image->endian=MSBEndian;
660   quantum_info=AcquireQuantumInfo((const ImageInfo *) NULL,image);
661   if (quantum_info == (QuantumInfo *) NULL)
662     ThrowWriterException(ResourceLimitError,"MemoryAllocationFailed");
663   offset=0;
664   (void) FormatLocaleString(header,FITSBlocksize,
665     "SIMPLE  =                    T");
666   (void) strncpy(fits_info+offset,header,strlen(header));
667   offset+=80;
668   (void) FormatLocaleString(header,FITSBlocksize,"BITPIX  =           %10ld",
669     (long) (quantum_info->format == FloatingPointQuantumFormat ? -1 : 1)*
670     image->depth);
671   (void) strncpy(fits_info+offset,header,strlen(header));
672   offset+=80;
673   (void) FormatLocaleString(header,FITSBlocksize,"NAXIS   =           %10lu",
674     IsImageGray(image,exception) != MagickFalse ? 2UL : 3UL);
675   (void) strncpy(fits_info+offset,header,strlen(header));
676   offset+=80;
677   (void) FormatLocaleString(header,FITSBlocksize,"NAXIS1  =           %10lu",
678     (unsigned long) image->columns);
679   (void) strncpy(fits_info+offset,header,strlen(header));
680   offset+=80;
681   (void) FormatLocaleString(header,FITSBlocksize,"NAXIS2  =           %10lu",
682     (unsigned long) image->rows);
683   (void) strncpy(fits_info+offset,header,strlen(header));
684   offset+=80;
685   if (IsImageGray(image,exception) == MagickFalse)
686     {
687       (void) FormatLocaleString(header,FITSBlocksize,
688         "NAXIS3  =           %10lu",3UL);
689       (void) strncpy(fits_info+offset,header,strlen(header));
690       offset+=80;
691     }
692   (void) FormatLocaleString(header,FITSBlocksize,"BSCALE  =         %E",1.0);
693   (void) strncpy(fits_info+offset,header,strlen(header));
694   offset+=80;
695   (void) FormatLocaleString(header,FITSBlocksize,"BZERO   =         %E",
696     image->depth > 8 ? GetFITSPixelRange(image->depth)/2.0 : 0.0);
697   (void) strncpy(fits_info+offset,header,strlen(header));
698   offset+=80;
699   (void) FormatLocaleString(header,FITSBlocksize,"DATAMAX =         %E",
700     1.0*((MagickOffsetType) GetQuantumRange(image->depth)));
701   (void) strncpy(fits_info+offset,header,strlen(header));
702   offset+=80;
703   (void) FormatLocaleString(header,FITSBlocksize,"DATAMIN =         %E",0.0);
704   (void) strncpy(fits_info+offset,header,strlen(header));
705   offset+=80;
706   if (image->endian == LSBEndian)
707     {
708       (void) FormatLocaleString(header,FITSBlocksize,"XENDIAN = 'SMALL'");
709       (void) strncpy(fits_info+offset,header,strlen(header));
710       offset+=80;
711     }
712   (void) FormatLocaleString(header,FITSBlocksize,"HISTORY %.72s",
713     GetMagickVersion((size_t *) NULL));
714   (void) strncpy(fits_info+offset,header,strlen(header));
715   offset+=80;
716   (void) strncpy(header,"END",FITSBlocksize);
717   (void) strncpy(fits_info+offset,header,strlen(header));
718   offset+=80;
719   (void) WriteBlob(image,FITSBlocksize,(unsigned char *) fits_info);
720   /*
721     Convert image to fits scale PseudoColor class.
722   */
723   pixels=GetQuantumPixels(quantum_info);
724   if (IsImageGray(image,exception) != MagickFalse)
725     {
726       length=GetQuantumExtent(image,quantum_info,GrayQuantum);
727       for (y=(ssize_t) image->rows-1; y >= 0; y--)
728       {
729         p=GetVirtualPixels(image,0,y,image->columns,1,exception);
730         if (p == (const Quantum *) NULL)
731           break;
732         length=ExportQuantumPixels(image,(CacheView *) NULL,quantum_info,
733           GrayQuantum,pixels,exception);
734         if (image->depth == 16)
735           SetFITSUnsignedPixels(image->columns,image->depth,image->endian,
736             pixels);
737         if (((image->depth == 32) || (image->depth == 64)) &&
738             (quantum_info->format != FloatingPointQuantumFormat))
739           SetFITSUnsignedPixels(image->columns,image->depth,image->endian,
740             pixels);
741         count=WriteBlob(image,length,pixels);
742         if (count != (ssize_t) length)
743           break;
744         status=SetImageProgress(image,SaveImageTag,(MagickOffsetType) y,
745           image->rows);
746         if (status == MagickFalse)
747           break;
748       }
749     }
750   else
751     {
752       length=GetQuantumExtent(image,quantum_info,RedQuantum);
753       for (y=(ssize_t) image->rows-1; y >= 0; y--)
754       {
755         p=GetVirtualPixels(image,0,y,image->columns,1,exception);
756         if (p == (const Quantum *) NULL)
757           break;
758         length=ExportQuantumPixels(image,(CacheView *) NULL,quantum_info,
759           RedQuantum,pixels,exception);
760         if (image->depth == 16)
761           SetFITSUnsignedPixels(image->columns,image->depth,image->endian,
762             pixels);
763         if (((image->depth == 32) || (image->depth == 64)) &&
764             (quantum_info->format != FloatingPointQuantumFormat))
765           SetFITSUnsignedPixels(image->columns,image->depth,image->endian,
766             pixels);
767         count=WriteBlob(image,length,pixels);
768         if (count != (ssize_t) length)
769           break;
770         status=SetImageProgress(image,SaveImageTag,(MagickOffsetType) y,
771           image->rows);
772         if (status == MagickFalse)
773           break;
774       }
775       length=GetQuantumExtent(image,quantum_info,GreenQuantum);
776       for (y=(ssize_t) image->rows-1; y >= 0; y--)
777       {
778         p=GetVirtualPixels(image,0,y,image->columns,1,exception);
779         if (p == (const Quantum *) NULL)
780           break;
781         length=ExportQuantumPixels(image,(CacheView *) NULL,quantum_info,
782           GreenQuantum,pixels,exception);
783         if (image->depth == 16)
784           SetFITSUnsignedPixels(image->columns,image->depth,image->endian,
785             pixels);
786         if (((image->depth == 32) || (image->depth == 64)) &&
787             (quantum_info->format != FloatingPointQuantumFormat))
788           SetFITSUnsignedPixels(image->columns,image->depth,image->endian,
789             pixels);
790         count=WriteBlob(image,length,pixels);
791         if (count != (ssize_t) length)
792           break;
793         status=SetImageProgress(image,SaveImageTag,(MagickOffsetType) y,
794           image->rows);
795         if (status == MagickFalse)
796           break;
797       }
798       length=GetQuantumExtent(image,quantum_info,BlueQuantum);
799       for (y=(ssize_t) image->rows-1; y >= 0; y--)
800       {
801         p=GetVirtualPixels(image,0,y,image->columns,1,exception);
802         if (p == (const Quantum *) NULL)
803           break;
804         length=ExportQuantumPixels(image,(CacheView *) NULL,quantum_info,
805           BlueQuantum,pixels,exception);
806         if (image->depth == 16)
807           SetFITSUnsignedPixels(image->columns,image->depth,image->endian,
808             pixels);
809         if (((image->depth == 32) || (image->depth == 64)) &&
810             (quantum_info->format != FloatingPointQuantumFormat))
811           SetFITSUnsignedPixels(image->columns,image->depth,image->endian,
812             pixels);
813         count=WriteBlob(image,length,pixels);
814         if (count != (ssize_t) length)
815           break;
816         status=SetImageProgress(image,SaveImageTag,(MagickOffsetType) y,
817           image->rows);
818         if (status == MagickFalse)
819           break;
820       }
821     }
822   quantum_info=DestroyQuantumInfo(quantum_info);
823   length=(size_t) (FITSBlocksize-TellBlob(image) % FITSBlocksize);
824   if (length != 0)
825     {
826       (void) ResetMagickMemory(fits_info,0,length*sizeof(*fits_info));
827       (void) WriteBlob(image,length,(unsigned char *) fits_info);
828     }
829   fits_info=DestroyString(fits_info);
830   (void) CloseBlob(image);
831   return(MagickTrue);
832 }