Commit | Line | Data |
---|---|---|
797a9f9c | 1 | #include "imext.h" |
f7450478 TC |
2 | #include "imexif.h" |
3 | #include <stdlib.h> | |
4 | #include <float.h> | |
797a9f9c | 5 | #include <string.h> |
f7450478 TC |
6 | |
7 | /* | |
8 | =head1 NAME | |
9 | ||
10 | imexif.c - EXIF support for Imager | |
11 | ||
12 | =head1 SYNOPSIS | |
13 | ||
14 | if (i_int_decode_exif(im, app1data, app1datasize)) { | |
15 | // exif block seen | |
16 | } | |
17 | ||
18 | =head1 DESCRIPTION | |
19 | ||
20 | This code provides a basic EXIF data decoder. It is intended to be | |
21 | called from the JPEG reader code when an APP1 data block is found, and | |
22 | will set tags in the supplied image. | |
23 | ||
24 | =cut | |
25 | */ | |
26 | ||
27 | typedef enum tiff_type_tag { | |
28 | tt_intel = 'I', | |
29 | tt_motorola = 'M' | |
30 | } tiff_type; | |
31 | ||
32 | typedef enum { | |
33 | ift_byte = 1, | |
34 | ift_ascii = 2, | |
35 | ift_short = 3, | |
36 | ift_long = 4, | |
37 | ift_rational = 5, | |
38 | ift_sbyte = 6, | |
39 | ift_undefined = 7, | |
40 | ift_sshort = 8, | |
41 | ift_slong = 9, | |
42 | ift_srational = 10, | |
43 | ift_float = 11, | |
44 | ift_double = 12, | |
45 | ift_last = 12 /* keep the same as the highest type code */ | |
46 | } ifd_entry_type; | |
47 | ||
48 | static int type_sizes[] = | |
49 | { | |
50 | 0, /* not used */ | |
51 | 1, /* byte */ | |
52 | 1, /* ascii */ | |
53 | 2, /* short */ | |
54 | 4, /* long */ | |
55 | 8, /* rational */ | |
56 | 1, /* sbyte */ | |
57 | 1, /* undefined */ | |
58 | 2, /* sshort */ | |
59 | 4, /* slong */ | |
60 | 8, /* srational */ | |
61 | 4, /* float */ | |
62 | 8, /* double */ | |
63 | }; | |
64 | ||
65 | typedef struct { | |
66 | int tag; | |
67 | int type; | |
68 | int count; | |
59957854 | 69 | int item_size; |
f7450478 TC |
70 | int size; |
71 | int offset; | |
72 | } ifd_entry; | |
73 | ||
74 | typedef struct { | |
75 | int tag; | |
76 | char const *name; | |
77 | } tag_map; | |
78 | ||
79 | typedef struct { | |
80 | int tag; | |
81 | char const *name; | |
82 | tag_map const *map; | |
83 | int map_count; | |
84 | } tag_value_map; | |
85 | ||
86 | #define PASTE(left, right) PASTE_(left, right) | |
87 | #define PASTE_(left, right) left##right | |
88 | #define QUOTE(value) #value | |
89 | ||
90 | #define VALUE_MAP_ENTRY(name) \ | |
91 | { \ | |
92 | PASTE(tag_, name), \ | |
93 | "exif_" QUOTE(name) "_name", \ | |
94 | PASTE(name, _values), \ | |
95 | ARRAY_COUNT(PASTE(name, _values)) \ | |
96 | } | |
97 | ||
98 | /* we don't process every tag */ | |
99 | #define tag_make 271 | |
100 | #define tag_model 272 | |
101 | #define tag_orientation 274 | |
102 | #define tag_x_resolution 282 | |
103 | #define tag_y_resolution 283 | |
104 | #define tag_resolution_unit 296 | |
105 | #define tag_copyright 33432 | |
106 | #define tag_software 305 | |
107 | #define tag_artist 315 | |
108 | #define tag_date_time 306 | |
109 | #define tag_image_description 270 | |
110 | ||
111 | #define tag_exif_ifd 34665 | |
112 | #define tag_gps_ifd 34853 | |
113 | ||
114 | #define resunit_none 1 | |
115 | #define resunit_inch 2 | |
116 | #define resunit_centimeter 3 | |
117 | ||
118 | /* tags from the EXIF ifd */ | |
119 | #define tag_exif_version 0x9000 | |
120 | #define tag_flashpix_version 0xA000 | |
121 | #define tag_color_space 0xA001 | |
122 | #define tag_component_configuration 0x9101 | |
123 | #define tag_component_bits_per_pixel 0x9102 | |
124 | #define tag_pixel_x_dimension 0xA002 | |
125 | #define tag_pixel_y_dimension 0xA003 | |
126 | #define tag_maker_note 0x927C | |
127 | #define tag_user_comment 0x9286 | |
128 | #define tag_related_sound_file 0xA004 | |
129 | #define tag_date_time_original 0x9003 | |
130 | #define tag_date_time_digitized 0x9004 | |
131 | #define tag_sub_sec_time 0x9290 | |
132 | #define tag_sub_sec_time_original 0x9291 | |
133 | #define tag_sub_sec_time_digitized 0x9292 | |
134 | #define tag_image_unique_id 0xA420 | |
135 | #define tag_exposure_time 0x829a | |
136 | #define tag_f_number 0x829D | |
137 | #define tag_exposure_program 0x8822 | |
138 | #define tag_spectral_sensitivity 0x8824 | |
59957854 | 139 | #define tag_iso_speed_ratings 0x8827 |
f7450478 TC |
140 | #define tag_oecf 0x8828 |
141 | #define tag_shutter_speed 0x9201 | |
142 | #define tag_aperture 0x9202 | |
143 | #define tag_brightness 0x9203 | |
144 | #define tag_exposure_bias 0x9204 | |
145 | #define tag_max_aperture 0x9205 | |
146 | #define tag_subject_distance 0x9206 | |
147 | #define tag_metering_mode 0x9207 | |
148 | #define tag_light_source 0x9208 | |
149 | #define tag_flash 0x9209 | |
150 | #define tag_focal_length 0x920a | |
151 | #define tag_subject_area 0x9214 | |
152 | #define tag_flash_energy 0xA20B | |
153 | #define tag_spatial_frequency_response 0xA20C | |
154 | #define tag_focal_plane_x_resolution 0xA20e | |
155 | #define tag_focal_plane_y_resolution 0xA20F | |
156 | #define tag_focal_plane_resolution_unit 0xA210 | |
157 | #define tag_subject_location 0xA214 | |
158 | #define tag_exposure_index 0xA215 | |
159 | #define tag_sensing_method 0xA217 | |
160 | #define tag_file_source 0xA300 | |
161 | #define tag_scene_type 0xA301 | |
162 | #define tag_cfa_pattern 0xA302 | |
163 | #define tag_custom_rendered 0xA401 | |
164 | #define tag_exposure_mode 0xA402 | |
165 | #define tag_white_balance 0xA403 | |
166 | #define tag_digital_zoom_ratio 0xA404 | |
167 | #define tag_focal_length_in_35mm_film 0xA405 | |
168 | #define tag_scene_capture_type 0xA406 | |
169 | #define tag_gain_control 0xA407 | |
170 | #define tag_contrast 0xA408 | |
171 | #define tag_saturation 0xA409 | |
172 | #define tag_sharpness 0xA40A | |
173 | #define tag_device_setting_description 0xA40B | |
174 | #define tag_subject_distance_range 0xA40C | |
175 | ||
59957854 TC |
176 | /* GPS tags */ |
177 | #define tag_gps_version_id 0 | |
178 | #define tag_gps_latitude_ref 1 | |
179 | #define tag_gps_latitude 2 | |
180 | #define tag_gps_longitude_ref 3 | |
181 | #define tag_gps_longitude 4 | |
182 | #define tag_gps_altitude_ref 5 | |
183 | #define tag_gps_altitude 6 | |
184 | #define tag_gps_time_stamp 7 | |
185 | #define tag_gps_satellites 8 | |
186 | #define tag_gps_status 9 | |
187 | #define tag_gps_measure_mode 10 | |
188 | #define tag_gps_dop 11 | |
189 | #define tag_gps_speed_ref 12 | |
190 | #define tag_gps_speed 13 | |
191 | #define tag_gps_track_ref 14 | |
192 | #define tag_gps_track 15 | |
193 | #define tag_gps_img_direction_ref 16 | |
194 | #define tag_gps_img_direction 17 | |
195 | #define tag_gps_map_datum 18 | |
196 | #define tag_gps_dest_latitude_ref 19 | |
197 | #define tag_gps_dest_latitude 20 | |
198 | #define tag_gps_dest_longitude_ref 21 | |
199 | #define tag_gps_dest_longitude 22 | |
200 | #define tag_gps_dest_bearing_ref 23 | |
201 | #define tag_gps_dest_bearing 24 | |
202 | #define tag_gps_dest_distance_ref 25 | |
203 | #define tag_gps_dest_distance 26 | |
204 | #define tag_gps_processing_method 27 | |
205 | #define tag_gps_area_information 28 | |
206 | #define tag_gps_date_stamp 29 | |
207 | #define tag_gps_differential 30 | |
208 | ||
f7450478 TC |
209 | /* don't use this on pointers */ |
210 | #define ARRAY_COUNT(array) (sizeof(array)/sizeof(*array)) | |
211 | ||
212 | /* in memory tiff structure */ | |
213 | typedef struct { | |
214 | /* the data we use as a tiff */ | |
215 | unsigned char *base; | |
216 | size_t size; | |
217 | ||
218 | /* intel or motorola byte order */ | |
219 | tiff_type type; | |
220 | ||
221 | /* initial ifd offset */ | |
222 | unsigned long first_ifd_offset; | |
223 | ||
224 | /* size (in entries) and data */ | |
225 | int ifd_size; | |
226 | ifd_entry *ifd; | |
227 | unsigned long next_ifd; | |
228 | } imtiff; | |
229 | ||
230 | static int tiff_init(imtiff *tiff, unsigned char *base, size_t length); | |
231 | static int tiff_load_ifd(imtiff *tiff, unsigned long offset); | |
232 | static void tiff_final(imtiff *tiff); | |
233 | static void tiff_clear_ifd(imtiff *tiff); | |
e4bf9335 | 234 | #if 0 /* currently unused, but that may change */ |
f7450478 TC |
235 | static int tiff_get_bytes(imtiff *tiff, unsigned char *to, size_t offset, |
236 | size_t count); | |
e4bf9335 | 237 | #endif |
f7450478 TC |
238 | static int tiff_get_tag_double(imtiff *, int index, double *result); |
239 | static int tiff_get_tag_int(imtiff *, int index, int *result); | |
240 | static unsigned tiff_get16(imtiff *, unsigned long offset); | |
241 | static unsigned tiff_get32(imtiff *, unsigned long offset); | |
242 | static int tiff_get16s(imtiff *, unsigned long offset); | |
243 | static int tiff_get32s(imtiff *, unsigned long offset); | |
244 | static double tiff_get_rat(imtiff *, unsigned long offset); | |
245 | static double tiff_get_rats(imtiff *, unsigned long offset); | |
59957854 | 246 | static void save_ifd0_tags(i_img *im, imtiff *tiff, unsigned long *exif_ifd_offset, unsigned long *gps_ifd_offset); |
f7450478 | 247 | static void save_exif_ifd_tags(i_img *im, imtiff *tiff); |
59957854 | 248 | static void save_gps_ifd_tags(i_img *im, imtiff *tiff); |
f7450478 TC |
249 | static void |
250 | copy_string_tags(i_img *im, imtiff *tiff, tag_map *map, int map_count); | |
251 | static void | |
252 | copy_int_tags(i_img *im, imtiff *tiff, tag_map *map, int map_count); | |
253 | static void | |
254 | copy_rat_tags(i_img *im, imtiff *tiff, tag_map *map, int map_count); | |
255 | static void | |
59957854 TC |
256 | copy_num_array_tags(i_img *im, imtiff *tiff, tag_map *map, int map_count); |
257 | static void | |
f7450478 TC |
258 | copy_name_tags(i_img *im, imtiff *tiff, tag_value_map *map, int map_count); |
259 | static void process_maker_note(i_img *im, imtiff *tiff, unsigned long offset, size_t size); | |
260 | ||
261 | /* | |
262 | =head1 PUBLIC FUNCTIONS | |
263 | ||
264 | These functions are available to other parts of Imager. They aren't | |
265 | intended to be called from outside of Imager. | |
266 | ||
267 | =over | |
268 | ||
269 | =item i_int_decode_exit | |
270 | ||
271 | i_int_decode_exif(im, data_base, data_size); | |
272 | ||
273 | The data from data_base for data_size bytes will be scanned for EXIF | |
274 | data. | |
275 | ||
276 | Any data found will be used to set tags in the supplied image. | |
277 | ||
278 | The intent is that invalid EXIF data will simply fail to set tags, and | |
279 | write to the log. In no case should this code exit when supplied | |
280 | invalid data. | |
281 | ||
282 | Returns true if an Exif header was seen. | |
283 | ||
284 | */ | |
285 | ||
286 | int | |
287 | i_int_decode_exif(i_img *im, unsigned char *data, size_t length) { | |
288 | imtiff tiff; | |
289 | unsigned long exif_ifd_offset = 0; | |
59957854 | 290 | unsigned long gps_ifd_offset = 0; |
f7450478 TC |
291 | /* basic checks - must start with "Exif\0\0" */ |
292 | ||
293 | if (length < 6 || memcmp(data, "Exif\0\0", 6) != 0) { | |
294 | return 0; | |
295 | } | |
296 | ||
297 | data += 6; | |
298 | length -= 6; | |
299 | ||
300 | if (!tiff_init(&tiff, data, length)) { | |
301 | mm_log((2, "Exif header found, but no valid TIFF header\n")); | |
302 | return 1; | |
303 | } | |
304 | if (!tiff_load_ifd(&tiff, tiff.first_ifd_offset)) { | |
305 | mm_log((2, "Exif header found, but could not load IFD 0\n")); | |
306 | tiff_final(&tiff); | |
307 | return 1; | |
308 | } | |
309 | ||
59957854 | 310 | save_ifd0_tags(im, &tiff, &exif_ifd_offset, &gps_ifd_offset); |
f7450478 TC |
311 | |
312 | if (exif_ifd_offset) { | |
313 | if (tiff_load_ifd(&tiff, exif_ifd_offset)) { | |
314 | save_exif_ifd_tags(im, &tiff); | |
315 | } | |
316 | else { | |
317 | mm_log((2, "Could not load Exif IFD\n")); | |
318 | } | |
319 | } | |
320 | ||
59957854 TC |
321 | if (gps_ifd_offset) { |
322 | if (tiff_load_ifd(&tiff, gps_ifd_offset)) { | |
323 | save_gps_ifd_tags(im, &tiff); | |
324 | } | |
325 | else { | |
326 | mm_log((2, "Could not load GPS IFD\n")); | |
327 | } | |
328 | } | |
329 | ||
f7450478 TC |
330 | tiff_final(&tiff); |
331 | ||
332 | return 1; | |
333 | } | |
334 | ||
335 | /* | |
336 | ||
337 | =back | |
338 | ||
339 | =head1 INTERNAL FUNCTIONS | |
340 | ||
341 | =head2 EXIF Processing | |
342 | ||
343 | =over | |
344 | ||
345 | =item save_ifd0_tags | |
346 | ||
59957854 | 347 | save_ifd0_tags(im, tiff, &exif_ifd_offset, &gps_ifd_offset) |
f7450478 TC |
348 | |
349 | Scans the currently loaded IFD for tags expected in IFD0 and sets them | |
350 | in the image. | |
351 | ||
352 | Sets *exif_ifd_offset to the offset of the EXIF IFD if found. | |
353 | ||
354 | =cut | |
355 | ||
356 | */ | |
357 | ||
358 | static tag_map ifd0_string_tags[] = | |
359 | { | |
af070d99 TC |
360 | { tag_make, "exif_make" }, |
361 | { tag_model, "exif_model" }, | |
362 | { tag_copyright, "exif_copyright" }, | |
363 | { tag_software, "exif_software" }, | |
364 | { tag_artist, "exif_artist" }, | |
365 | { tag_date_time, "exif_date_time" }, | |
366 | { tag_image_description, "exif_image_description" }, | |
f7450478 TC |
367 | }; |
368 | ||
369 | static const int ifd0_string_tag_count = ARRAY_COUNT(ifd0_string_tags); | |
370 | ||
371 | static tag_map ifd0_int_tags[] = | |
372 | { | |
373 | { tag_orientation, "exif_orientation", }, | |
374 | { tag_resolution_unit, "exif_resolution_unit" }, | |
375 | }; | |
376 | ||
377 | static const int ifd0_int_tag_count = ARRAY_COUNT(ifd0_int_tags); | |
378 | ||
379 | static tag_map ifd0_rat_tags[] = | |
380 | { | |
381 | { tag_x_resolution, "exif_x_resolution" }, | |
382 | { tag_y_resolution, "exif_y_resolution" }, | |
383 | }; | |
384 | ||
385 | static tag_map resolution_unit_values[] = | |
386 | { | |
387 | { 1, "none" }, | |
388 | { 2, "inches" }, | |
389 | { 3, "centimeters" }, | |
390 | }; | |
391 | ||
392 | static tag_value_map ifd0_values[] = | |
393 | { | |
394 | VALUE_MAP_ENTRY(resolution_unit), | |
395 | }; | |
396 | ||
397 | static void | |
59957854 TC |
398 | save_ifd0_tags(i_img *im, imtiff *tiff, unsigned long *exif_ifd_offset, |
399 | unsigned long *gps_ifd_offset) { | |
af070d99 | 400 | int tag_index; |
f7450478 TC |
401 | int work; |
402 | ifd_entry *entry; | |
403 | ||
404 | for (tag_index = 0, entry = tiff->ifd; | |
405 | tag_index < tiff->ifd_size; ++tag_index, ++entry) { | |
406 | switch (entry->tag) { | |
407 | case tag_exif_ifd: | |
408 | if (tiff_get_tag_int(tiff, tag_index, &work)) | |
409 | *exif_ifd_offset = work; | |
410 | break; | |
59957854 TC |
411 | |
412 | case tag_gps_ifd: | |
413 | if (tiff_get_tag_int(tiff, tag_index, &work)) | |
414 | *gps_ifd_offset = work; | |
415 | break; | |
f7450478 TC |
416 | } |
417 | } | |
418 | ||
419 | copy_string_tags(im, tiff, ifd0_string_tags, ifd0_string_tag_count); | |
420 | copy_int_tags(im, tiff, ifd0_int_tags, ifd0_int_tag_count); | |
421 | copy_rat_tags(im, tiff, ifd0_rat_tags, ARRAY_COUNT(ifd0_rat_tags)); | |
422 | copy_name_tags(im, tiff, ifd0_values, ARRAY_COUNT(ifd0_values)); | |
59957854 | 423 | /* copy_num_array_tags(im, tiff, ifd0_num_arrays, ARRAY_COUNT(ifd0_num_arrays)); */ |
f7450478 TC |
424 | } |
425 | ||
426 | /* | |
427 | =item save_exif_ifd_tags | |
428 | ||
429 | save_exif_ifd_tags(im, tiff) | |
430 | ||
431 | Scans the currently loaded IFD for the tags expected in the EXIF IFD | |
432 | and sets them as tags in the image. | |
433 | ||
434 | =cut | |
435 | ||
436 | */ | |
437 | ||
438 | static tag_map exif_ifd_string_tags[] = | |
439 | { | |
440 | { tag_exif_version, "exif_version", }, | |
441 | { tag_flashpix_version, "exif_flashpix_version", }, | |
442 | { tag_related_sound_file, "exif_related_sound_file", }, | |
443 | { tag_date_time_original, "exif_date_time_original", }, | |
444 | { tag_date_time_digitized, "exif_date_time_digitized", }, | |
445 | { tag_sub_sec_time, "exif_sub_sec_time" }, | |
446 | { tag_sub_sec_time_original, "exif_sub_sec_time_original" }, | |
447 | { tag_sub_sec_time_digitized, "exif_sub_sec_time_digitized" }, | |
448 | { tag_image_unique_id, "exif_image_unique_id" }, | |
449 | { tag_spectral_sensitivity, "exif_spectral_sensitivity" }, | |
450 | }; | |
451 | ||
452 | static const int exif_ifd_string_tag_count = ARRAY_COUNT(exif_ifd_string_tags); | |
453 | ||
454 | static tag_map exif_ifd_int_tags[] = | |
455 | { | |
456 | { tag_color_space, "exif_color_space" }, | |
457 | { tag_exposure_program, "exif_exposure_program" }, | |
f7450478 TC |
458 | { tag_metering_mode, "exif_metering_mode" }, |
459 | { tag_light_source, "exif_light_source" }, | |
460 | { tag_flash, "exif_flash" }, | |
461 | { tag_focal_plane_resolution_unit, "exif_focal_plane_resolution_unit" }, | |
462 | { tag_subject_location, "exif_subject_location" }, | |
463 | { tag_sensing_method, "exif_sensing_method" }, | |
464 | { tag_custom_rendered, "exif_custom_rendered" }, | |
465 | { tag_exposure_mode, "exif_exposure_mode" }, | |
466 | { tag_white_balance, "exif_white_balance" }, | |
467 | { tag_focal_length_in_35mm_film, "exif_focal_length_in_35mm_film" }, | |
468 | { tag_scene_capture_type, "exif_scene_capture_type" }, | |
469 | { tag_contrast, "exif_contrast" }, | |
470 | { tag_saturation, "exif_saturation" }, | |
471 | { tag_sharpness, "exif_sharpness" }, | |
472 | { tag_subject_distance_range, "exif_subject_distance_range" }, | |
473 | }; | |
474 | ||
475 | ||
476 | static const int exif_ifd_int_tag_count = ARRAY_COUNT(exif_ifd_int_tags); | |
477 | ||
478 | static tag_map exif_ifd_rat_tags[] = | |
479 | { | |
480 | { tag_exposure_time, "exif_exposure_time" }, | |
481 | { tag_f_number, "exif_f_number" }, | |
482 | { tag_shutter_speed, "exif_shutter_speed" }, | |
483 | { tag_aperture, "exif_aperture" }, | |
484 | { tag_brightness, "exif_brightness" }, | |
485 | { tag_exposure_bias, "exif_exposure_bias" }, | |
486 | { tag_max_aperture, "exif_max_aperture" }, | |
487 | { tag_subject_distance, "exif_subject_distance" }, | |
488 | { tag_focal_length, "exif_focal_length" }, | |
489 | { tag_flash_energy, "exif_flash_energy" }, | |
490 | { tag_focal_plane_x_resolution, "exif_focal_plane_x_resolution" }, | |
491 | { tag_focal_plane_y_resolution, "exif_focal_plane_y_resolution" }, | |
492 | { tag_exposure_index, "exif_exposure_index" }, | |
493 | { tag_digital_zoom_ratio, "exif_digital_zoom_ratio" }, | |
494 | { tag_gain_control, "exif_gain_control" }, | |
495 | }; | |
496 | ||
497 | static const int exif_ifd_rat_tag_count = ARRAY_COUNT(exif_ifd_rat_tags); | |
498 | ||
499 | static tag_map exposure_mode_values[] = | |
500 | { | |
501 | { 0, "Auto exposure" }, | |
502 | { 1, "Manual exposure" }, | |
503 | { 2, "Auto bracket" }, | |
504 | }; | |
505 | static tag_map color_space_values[] = | |
506 | { | |
507 | { 1, "sRGB" }, | |
508 | { 0xFFFF, "Uncalibrated" }, | |
509 | }; | |
510 | ||
511 | static tag_map exposure_program_values[] = | |
512 | { | |
513 | { 0, "Not defined" }, | |
514 | { 1, "Manual" }, | |
515 | { 2, "Normal program" }, | |
516 | { 3, "Aperture priority" }, | |
517 | { 4, "Shutter priority" }, | |
518 | { 5, "Creative program" }, | |
519 | { 6, "Action program" }, | |
520 | { 7, "Portrait mode" }, | |
521 | { 8, "Landscape mode" }, | |
522 | }; | |
523 | ||
524 | static tag_map metering_mode_values[] = | |
525 | { | |
526 | { 0, "unknown" }, | |
527 | { 1, "Average" }, | |
528 | { 2, "CenterWeightedAverage" }, | |
529 | { 3, "Spot" }, | |
530 | { 4, "MultiSpot" }, | |
531 | { 5, "Pattern" }, | |
532 | { 6, "Partial" }, | |
533 | { 255, "other" }, | |
534 | }; | |
535 | ||
536 | static tag_map light_source_values[] = | |
537 | { | |
538 | { 0, "unknown" }, | |
539 | { 1, "Daylight" }, | |
540 | { 2, "Fluorescent" }, | |
541 | { 3, "Tungsten (incandescent light)" }, | |
542 | { 4, "Flash" }, | |
543 | { 9, "Fine weather" }, | |
544 | { 10, "Cloudy weather" }, | |
545 | { 11, "Shade" }, | |
546 |