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