/** * tiffread.c * File input routines for minitiff. * * Copyright (C) 2006 Cosmin Truta. * * minitiff is open-source software, distributed under the zlib license. * For conditions of distribution and use, see copyright notice in minitiff.h. **/ #include #include #include #include #include "minitiff.h" #include "tiffdef.h" /** * Error messages. **/ static const char * tiff_err_read = "Error reading TIFF file"; static const char * tiff_err_notiff = "Not a TIFF file"; static const char * tiff_err_invalid = "Invalid TIFF file"; static const char * tiff_err_unsupported = "Unsupported data format in TIFF file"; static const char * tiff_err_compr = "Unsupported compression in TIFF file"; static const char * tiff_err_memory = "Out of memory"; static const char * tiff_warn_tag = "Unrecognized tag(s) in TIFF file"; static const char * tiff_warn_metadata = "Unrecognized EXIF/IPTC/XMP metadata in TIFF file"; static const char * tiff_warn_multiple = "Selected first image from multi-image TIFF file"; /** * Utilities. **/ struct minitiff_get_struct { unsigned int (*get_ushort)(const unsigned char *buf_ptr); unsigned long (*get_ulong) (const unsigned char *buf_ptr); }; static unsigned int get_ushort_m(const unsigned char *buf_ptr) { return ((unsigned int)buf_ptr[0] << 8) + ((unsigned int)buf_ptr[1]); } static unsigned int get_ushort_i(const unsigned char *buf_ptr) { return ((unsigned int)buf_ptr[0]) + ((unsigned int)buf_ptr[1] << 8); } static unsigned long get_ulong_m(const unsigned char *buf_ptr) { return ((unsigned long)buf_ptr[0] << 24) + ((unsigned long)buf_ptr[1] << 16) + ((unsigned long)buf_ptr[2] << 8) + ((unsigned long)buf_ptr[3]); } static unsigned long get_ulong_i(const unsigned char *buf_ptr) { return ((unsigned long)buf_ptr[0]) + ((unsigned long)buf_ptr[1] << 8) + ((unsigned long)buf_ptr[2] << 16) + ((unsigned long)buf_ptr[3] << 24); } static unsigned long get_ulong_value(struct minitiff_get_struct *get_ptr, int tag_type, const unsigned char *buf_ptr) { switch (tag_type) { case 1: /* byte */ return (unsigned long)buf_ptr[0]; case 3: /* ushort */ return (unsigned long)get_ptr->get_ushort(buf_ptr); case 4: /* ulong */ return get_ptr->get_ulong(buf_ptr); default: return (unsigned long)-1L; /* error */ } } static size_t read_ulong_values(struct minitiff_get_struct *get_ptr, int tag_type, unsigned long values[], size_t count, FILE *fp) { unsigned char buf[4]; size_t value_size; size_t i; if (tag_type == 1) /* byte */ value_size = 1; else if (tag_type == 3) /* ushort */ value_size = 2; else if (tag_type == 4) /* ulong */ value_size = 4; else return 0; /* read nothing */ for (i = 0; i < count; ++i) { if (fread(buf, value_size, 1, fp) != 1) break; values[i] = get_ulong_value(get_ptr, tag_type, buf); } return i; } /** * Read initializer. **/ void minitiff_read_info(struct minitiff_info *tiff_ptr, FILE *fp) { struct minitiff_get_struct getter; unsigned char buf[12]; unsigned char *vbuf = buf + 8; long dir_offset; unsigned int dir_size, i; unsigned int tag_id, tag_type; size_t count; size_t bits_per_sample_count; unsigned int bits_per_sample_tag_type, strip_offsets_tag_type; long bits_per_sample_offset, strip_offsets_offset; int unknown_tag_found, unknown_metadata_found; /* Read the TIFF header. */ if (fread(buf, 8, 1, fp) != 1) goto err_read; if (memcmp(buf, minitiff_sig_m, 4) == 0) { tiff_ptr->byte_order = 'M'; getter.get_ushort = get_ushort_m; getter.get_ulong = get_ulong_m; } else if (memcmp(buf, minitiff_sig_i, 4) == 0) { tiff_ptr->byte_order = 'I'; getter.get_ushort = get_ushort_i; getter.get_ulong = get_ulong_i; } else { minitiff_error(tiff_ptr, tiff_err_notiff); return; } bits_per_sample_count = 0; bits_per_sample_tag_type = strip_offsets_tag_type = 0; bits_per_sample_offset = strip_offsets_offset = 0; dir_offset = (long)getter.get_ulong(buf + 4); if (dir_offset < 8L) goto err_invalid; if (fseek(fp, dir_offset, SEEK_SET) != 0) goto err_read; /* Read the TIFF directory. */ if (fread(buf, 2, 1, fp) != 1) goto err_read; dir_size = getter.get_ushort(buf); unknown_tag_found = unknown_metadata_found = 0; for (i = 0; i < dir_size; ++i) { if (fread(buf, 12, 1, fp) != 1) goto err_read; tag_id = getter.get_ushort(buf); tag_type = getter.get_ushort(buf + 2); count = (size_t)getter.get_ulong(buf + 4); if (count == 0) goto err_unsupported; switch (tag_id) { case TIFF_TAG_SUBFILE_TYPE: if (count != 1 || (get_ulong_value(&getter, tag_type, vbuf) & ~(2UL)) != 0) goto err_unsupported; break; case TIFF_TAG_WIDTH: if (count != 1) goto err_unsupported; tiff_ptr->width = (size_t)get_ulong_value(&getter, tag_type, vbuf); break; case TIFF_TAG_HEIGHT: if (count != 1) goto err_unsupported; tiff_ptr->height = (size_t)get_ulong_value(&getter, tag_type, vbuf); break; case TIFF_TAG_BITS_PER_SAMPLE: if (count == 1) tiff_ptr->bits_per_sample = (unsigned int)get_ulong_value(&getter, tag_type, vbuf); else { bits_per_sample_count = count; bits_per_sample_tag_type = tag_type; bits_per_sample_offset = (long)getter.get_ulong(vbuf); } break; case TIFF_TAG_COMPRESSION: if (count == 1 && get_ulong_value(&getter, tag_type, vbuf) == 1) tiff_ptr->compression = 1; else minitiff_error(tiff_ptr, tiff_err_compr); break; case TIFF_TAG_PHOTOMETRIC: if (count != 1) goto err_unsupported; tiff_ptr->photometric = (unsigned int)get_ulong_value(&getter, tag_type, vbuf); break; case TIFF_TAG_STRIP_OFFSETS: tiff_ptr->strip_offsets_count = count; if (count == 1) { if (tiff_ptr->strip_offsets != NULL) goto err_invalid; tiff_ptr->strip_offsets = (long *)malloc(sizeof(long)); if (tiff_ptr->strip_offsets == NULL) goto err_memory; tiff_ptr->strip_offsets[0] = (long)get_ulong_value(&getter, tag_type, vbuf); } else { strip_offsets_tag_type = tag_type; strip_offsets_offset = (long)getter.get_ulong(vbuf); } break; case TIFF_TAG_ORIENTATION: if (count != 1 || get_ulong_value(&getter, tag_type, vbuf) != 1) minitiff_warning(tiff_ptr, "Non-default TIFF image orientation"); break; case TIFF_TAG_SAMPLES_PER_PIXEL: if (count != 1) goto err_unsupported; tiff_ptr->samples_per_pixel = (unsigned int)get_ulong_value(&getter, tag_type, vbuf); break; case TIFF_TAG_ROWS_PER_STRIP: if (count != 1) goto err_unsupported; tiff_ptr->rows_per_strip = (unsigned int)get_ulong_value(&getter, tag_type, vbuf); break; case TIFF_TAG_STRIP_BYTE_COUNTS: /* ignored for uncompressed images */ break; case TIFF_TAG_PLANAR_CONFIGURATION: case TIFF_TAG_PREDICTOR: if (count != 1 || get_ulong_value(&getter, tag_type, vbuf) != 1) goto err_unsupported; break; case TIFF_TAG_DOCUMENT_NAME: case TIFF_TAG_IMAGE_DESCRIPTION: case TIFF_TAG_MAKE: case TIFF_TAG_MODEL: case TIFF_TAG_MIN_SAMPLE_VALUE: case TIFF_TAG_MAX_SAMPLE_VALUE: case TIFF_TAG_X_RESOLUTION: case TIFF_TAG_Y_RESOLUTION: case TIFF_TAG_X_POSITION: case TIFF_TAG_Y_POSITION: case TIFF_TAG_RESOLUTION_UNIT: case TIFF_TAG_PAGE_NAME: case TIFF_TAG_PAGE_NUMBER: case TIFF_TAG_SOFTWARE: case TIFF_TAG_DATE_TIME: case TIFF_TAG_ARTIST: case TIFF_TAG_HOST_COMPUTER: /* ignore */ break; case TIFF_TAGEXT_XMP: case TIFF_TAGEXT_IPTC: case TIFF_TAGEXT_EXIF_IFD: case TIFF_TAGEXT_GPS_IFD: case TIFF_TAGEXT_INTEROPERABILITY_IFD: case TIFF_TAGEXT_PRINT_IM: if (!unknown_metadata_found) { unknown_metadata_found = 1; minitiff_warning(tiff_ptr, tiff_warn_metadata); } break; default: if (!unknown_tag_found) { unknown_tag_found = 1; minitiff_warning(tiff_ptr, tiff_warn_tag); } break; } } /* Is this the last TIFF directory? */ if (fread(buf, 4, 1, fp) != 1) goto err_read; if (getter.get_ulong(buf) != 0) minitiff_warning(tiff_ptr, tiff_warn_multiple); /* Finish up the incomplete readings. */ if (bits_per_sample_offset != 0) { unsigned long values[4]; count = bits_per_sample_count; if (count != (size_t)tiff_ptr->samples_per_pixel) goto err_invalid; if (count > 4) goto err_unsupported; if (fseek(fp, bits_per_sample_offset, SEEK_SET) != 0) goto err_read; if (read_ulong_values(&getter, bits_per_sample_tag_type, values, count, fp) != count) goto err_read; while (--count > 0) if (values[0] != values[count]) goto err_unsupported; tiff_ptr->bits_per_sample = (unsigned int)values[0]; } if (strip_offsets_offset != 0) { count = tiff_ptr->strip_offsets_count; if (count == 0 || count > tiff_ptr->height) goto err_invalid; tiff_ptr->strip_offsets = (long *)malloc(count * sizeof(long)); if (tiff_ptr->strip_offsets == NULL) goto err_memory; if (fseek(fp, strip_offsets_offset, SEEK_SET) != 0) goto err_read; if (read_ulong_values(&getter, strip_offsets_tag_type, (unsigned long *)tiff_ptr->strip_offsets, count, fp) != count) goto err_read; } /* Terminate. */ return; /* Quick and dirty goto labels. */ err_read: minitiff_error(tiff_ptr, tiff_err_read); err_invalid: minitiff_error(tiff_ptr, tiff_err_invalid); err_unsupported: minitiff_error(tiff_ptr, tiff_err_unsupported); err_memory: minitiff_error(tiff_ptr, tiff_err_memory); } /** * Row reader. **/ void minitiff_read_row(struct minitiff_info *tiff_ptr, unsigned char *row_ptr, size_t row_index, FILE *fp) { size_t row_size, strip_index; unsigned int bytes_per_sample, sample_max; long offset; size_t i; /* Do not do validation here. */ /* Call minitiff_validate_info() before calling this function. */ bytes_per_sample = (tiff_ptr->bits_per_sample + 7) / 8; row_size = tiff_ptr->width * tiff_ptr->samples_per_pixel * bytes_per_sample; /* Position the file pointer to the beginning of the row, * if that has not been done already. */ strip_index = row_index / tiff_ptr->rows_per_strip; if (strip_index >= tiff_ptr->strip_offsets_count) goto err_invalid; offset = tiff_ptr->strip_offsets[strip_index] + row_size * (row_index % tiff_ptr->rows_per_strip); if (offset <= 0) goto err_invalid; if (ftell(fp) != offset) if (fseek(fp, offset, SEEK_SET) != 0) goto err_read; /* Read the row, and do all the necessary adjustments. */ if (fread(row_ptr, row_size, 1, fp) != 1) goto err_read; if (tiff_ptr->photometric == 0) /* white is zero */ { if (bytes_per_sample > 1) goto err_unsupported; sample_max = (1 << tiff_ptr->bits_per_sample) - 1; for (i = 0; i < row_size; ++i) row_ptr[i] = (unsigned char)(sample_max - row_ptr[i]); } /* Terminate. */ return; /* Quick and dirty goto labels. */ err_read: minitiff_error(tiff_ptr, tiff_err_read); err_invalid: minitiff_error(tiff_ptr, tiff_err_invalid); err_unsupported: minitiff_error(tiff_ptr, tiff_err_unsupported); }