| /** |
| * pnmin.c |
| * PNM file input. |
| * |
| * Copyright (C) 2002-2008 Cosmin Truta. |
| * This file is part of the pnmio library, distributed under the zlib license. |
| * For conditions of distribution and use, see copyright notice in pnmio.h. |
| **/ |
| |
| |
| #include <errno.h> |
| #include <limits.h> |
| #include <stddef.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include "pnmio.h" |
| |
| |
| /** |
| * Checks if the character is a space: ' ', '\t', '\n' or '\r'. |
| **/ |
| #define pnm_is_space(ch) \ |
| ((ch) == ' ' || (ch) == '\t' || (ch) == '\n' || (ch) == '\r') |
| |
| |
| /** |
| * Checks if the character is a digit: '0' .. '9'. |
| **/ |
| #define pnm_is_digit(ch) \ |
| ((ch) >= '0' && (ch) <= '9') |
| |
| |
| /** |
| * Reads a character from a file stream. |
| * Comment sequences starting with '#' are skipped until the end of line. |
| * End of line sequences (LF, CR, CR+LF) are translated to '\n'. |
| * Returns the character read, or EOF on input failure. |
| **/ |
| static int pnm_fget_char(FILE *stream) |
| { |
| int ch = getc(stream); |
| |
| /* skip the comments */ |
| if (ch == '#') |
| { |
| do |
| { |
| ch = getc(stream); |
| } while (ch != EOF && ch != '\n' && ch != '\r'); |
| } |
| |
| /* translate the line endings */ |
| if (ch == '\r') |
| { |
| ch = getc(stream); |
| if (ch != '\n') |
| { |
| ungetc(ch, stream); |
| ch = '\n'; |
| } |
| } |
| |
| return ch; |
| } |
| |
| |
| /** |
| * Reads (scans) an unsigned integer from a file stream. |
| * Returns 1 on success, 0 on matching failure, or EOF on input failure. |
| **/ |
| static int pnm_fscan_uint(FILE *stream, unsigned int *value) |
| { |
| int ch; |
| unsigned int tmp; |
| |
| /* skip the leading whitespaces */ |
| do |
| { |
| ch = pnm_fget_char(stream); |
| } while (pnm_is_space(ch)); |
| if (ch == EOF) /* input failure */ |
| return EOF; |
| if (!pnm_is_digit(ch)) /* matching failure */ |
| { |
| ungetc(ch, stream); |
| return 0; |
| } |
| |
| /* read the value */ |
| *value = 0; |
| do |
| { |
| tmp = *value * 10 + (ch - '0'); |
| if (tmp >= *value) |
| *value = tmp; |
| else /* overflow */ |
| { |
| *value = UINT_MAX; |
| errno = ERANGE; |
| } |
| ch = getc(stream); |
| } while (pnm_is_digit(ch)); |
| |
| /* put back the trailing non-whitespace, if any */ |
| if (!pnm_is_space(ch)) |
| ungetc(ch, stream); |
| |
| return 1; |
| } |
| |
| |
| /** |
| * Reads a PNM header structure from a file stream and validates it. |
| * Returns 1 on success, 0 on validation failure, |
| * or -1 on input or matching failure. |
| * Reading PAM ("P7") headers is not currently implemented. |
| **/ |
| int pnm_fget_header(pnm_struct *pnm_ptr, FILE *stream) |
| { |
| unsigned int format; |
| int ch; |
| |
| /* clear the PNM info structure */ |
| memset(pnm_ptr, 0, sizeof(pnm_struct)); |
| |
| /* read the PNM file signature */ |
| ch = getc(stream); |
| if (ch == EOF) /* input failure */ |
| return -1; |
| /* any subsequent failure is a matching failure */ |
| if (ch != 'P') |
| return -1; |
| ch = getc(stream); |
| if (ch < '1' || ch > '9') |
| return -1; |
| format = (unsigned int)(ch - '0'); |
| ch = pnm_fget_char(stream); /* start using pnm_fget_char() */ |
| if (!pnm_is_space(ch)) |
| return -1; |
| |
| /* read the header */ |
| pnm_ptr->format = format; |
| if (format >= PNM_P1 && format <= PNM_P6) /* old-style PNM header */ |
| { |
| pnm_ptr->depth = (format == PNM_P3 || format == PNM_P6) ? 3 : 1; |
| if (pnm_fscan_uint(stream, &pnm_ptr->width) != 1 || |
| pnm_fscan_uint(stream, &pnm_ptr->height) != 1) |
| return -1; |
| if (format == PNM_P1 || format == PNM_P4) |
| pnm_ptr->maxval = 1; |
| else |
| { |
| if (pnm_fscan_uint(stream, &pnm_ptr->maxval) != 1) |
| return -1; |
| } |
| return pnm_is_valid(pnm_ptr) ? 1 : 0; |
| } |
| else /* TODO: if (format == PNM_P7) ... */ |
| return -1; |
| } |
| |
| |
| /** |
| * Reads an array of PNM sample values from a file stream. |
| * The values are expected to be in the format specified by pnm_ptr->format. |
| * The array length is pnm_ptr->depth * pnm_ptr->width * num_rows. |
| * The validity check performed on the PNM structure is only partial. |
| * Returns 1 on success, 0 on validation failure, |
| * or -1 on input or matching failure. |
| * If reading is incomplete, the remaining sample values are set to 0. |
| **/ |
| int pnm_fget_values(const pnm_struct *pnm_ptr, |
| unsigned int *sample_values, |
| unsigned int num_rows, |
| FILE *stream) |
| { |
| unsigned int format = pnm_ptr->format; |
| unsigned int depth = pnm_ptr->depth; |
| unsigned int width = pnm_ptr->width; |
| unsigned int maxval = pnm_ptr->maxval; |
| size_t row_length = (size_t)depth * (size_t)width; |
| size_t num_samples = num_rows * row_length; |
| int ch, ch8; |
| #if PNM_UINT_BIT > 16 |
| int ch16, ch24; |
| #endif |
| int mask; |
| size_t i, j; |
| |
| /* read the sample values */ |
| switch (format) |
| { |
| case PNM_P1: |
| for (i = 0; i < num_samples; ++i) |
| { |
| do |
| { |
| ch = pnm_fget_char(stream); |
| } while (pnm_is_space(ch)); |
| if (ch != '0' && ch != '1') |
| { |
| ungetc(ch, stream); |
| break; |
| } |
| sample_values[i] = (ch == '0') ? 1 : 0; |
| } |
| break; |
| case PNM_P2: |
| case PNM_P3: |
| for (i = 0; i < num_samples; ++i) |
| { |
| if (pnm_fscan_uint(stream, &sample_values[i]) != 1) |
| break; |
| } |
| break; |
| case PNM_P4: |
| for (i = j = 0; i < num_samples; ) |
| { |
| ch = getc(stream); |
| if (ch == EOF) |
| break; |
| for (mask = 0x80; mask != 0; mask >>= 1) |
| { |
| sample_values[i++] = (ch & mask) ? 0 : 1; |
| if (++j == row_length) |
| { |
| j = 0; |
| break; |
| } |
| } |
| } |
| break; |
| case PNM_P5: |
| case PNM_P6: |
| case PNM_P7: |
| if (maxval <= 0xffU) /* 1 byte per sample */ |
| { |
| for (i = 0; i < num_samples; ++i) |
| { |
| ch = getc(stream); |
| if (ch == EOF) |
| break; |
| sample_values[i] = (unsigned int)ch; |
| } |
| } |
| else if (maxval <= 0xffffU) /* 2 bytes per sample */ |
| { |
| for (i = 0; i < num_samples; ++i) |
| { |
| ch8 = getc(stream); |
| ch = getc(stream); |
| if (ch == EOF) |
| break; |
| sample_values[i] = ((unsigned int)ch8 << 8) + (unsigned int)ch; |
| } |
| } |
| #if PNM_UINT_BIT > 16 |
| else if (maxval <= 0xffffffffU) /* 3 or 4 bytes per sample */ |
| { |
| ch24 = 0; |
| for (i = 0; i < num_samples; ++i) |
| { |
| if (maxval > 0xffffffU) |
| ch24 = getc(stream); |
| ch16 = getc(stream); |
| ch8 = getc(stream); |
| ch = getc(stream); |
| if (ch == EOF) |
| break; |
| sample_values[i] = |
| ((unsigned int)ch24 << 24) + ((unsigned int)ch16 << 16) + |
| ((unsigned int)ch8 << 8) + ((unsigned int)ch); |
| } |
| } |
| #endif |
| else /* maxval > PNM_UINT_MAX */ |
| { |
| errno = EINVAL; |
| return 0; |
| } |
| break; |
| default: |
| errno = EINVAL; |
| return 0; |
| } |
| |
| /* check the result */ |
| if (i < num_samples) |
| { |
| memset(sample_values + i, 0, (num_samples - i) * sizeof(unsigned int)); |
| return -1; |
| } |
| return 1; |
| } |
| |
| |
| /** |
| * Reads an array of sample bytes from a raw PNM file stream. |
| * Multi-byte samples are stored in network order, as in the PNM stream. |
| * The byte count is sample_size * pnm_ptr->depth * pnm_ptr->width * num_rows. |
| * The validity check performed on the PNM structure is only partial. |
| * Returns 1 on success, 0 on validation failure, or -1 on input failure. |
| * If reading is incomplete, the remaining sample bytes are set to 0. |
| **/ |
| int pnm_fget_bytes(const pnm_struct *pnm_ptr, |
| unsigned char *sample_bytes, |
| size_t sample_size, |
| unsigned int num_rows, |
| FILE *stream) |
| { |
| unsigned int format = pnm_ptr->format; |
| unsigned int depth = pnm_ptr->depth; |
| unsigned int width = pnm_ptr->width; |
| unsigned int maxval = pnm_ptr->maxval; |
| size_t row_length = (size_t)depth * (size_t)width; |
| size_t num_samples = num_rows * row_length; |
| size_t raw_sample_size; |
| int ch, mask; |
| size_t i, j; |
| |
| /* validate the given sample size */ |
| if (maxval <= 0xffU) |
| raw_sample_size = 1; |
| else if (maxval <= 0xffffU) |
| raw_sample_size = 2; |
| #if PNM_UINT_BIT > 16 |
| else if (maxval <= 0xffffffU) |
| raw_sample_size = 3; |
| else if (maxval <= 0xffffffffU) |
| raw_sample_size = 4; |
| #endif |
| else /* maxval > PNM_UINT_MAX */ |
| raw_sample_size = !sample_size; |
| if (raw_sample_size != sample_size) |
| { |
| errno = EINVAL; |
| return 0; |
| } |
| |
| /* read the raw sample bytes */ |
| switch (format) |
| { |
| case PNM_P4: |
| for (i = j = 0; i < num_samples; ) |
| { |
| ch = getc(stream); |
| if (ch == EOF) |
| break; |
| for (mask = 0x80; mask != 0; mask >>= 1) |
| { |
| sample_bytes[i++] = (unsigned char)((ch & mask) ? 0 : 1); |
| if (++j == row_length) |
| { |
| j = 0; |
| break; |
| } |
| } |
| } |
| break; |
| case PNM_P5: |
| case PNM_P6: |
| case PNM_P7: |
| i = fread(sample_bytes, sample_size, num_samples, stream); |
| break; |
| default: |
| errno = EINVAL; |
| return 0; |
| } |
| |
| /* check the result */ |
| if (i < num_samples) |
| { |
| memset(sample_bytes + i, 0, sample_size * num_samples - i); |
| return -1; |
| } |
| return 1; |
| } |