/* imagsize:  figure out the image size of BMP, GIF, JPEG, PBM, PGM, PPM, PNG,
              TIFF, WEBP, XBM, and XPM files

   Original "image_size" program Copyright (C)1997,1998,2014 by Jef Poskanzer
   <jef@acme.com>.  All rights reserved.

   Redistribution and use in source and binary forms, with or without
   modification, are permitted provided that the following conditions are met:
   1. Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
   2. Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.

   THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY
   EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
   WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY
   DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
   (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
   THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

   The JPEG quality estimation algorithm used in this program comes from the
   "jpegdump" program, which is Copyright (C)1992 Handmade Software, Inc. by
   Allan N. Hessenflow.

   Permission to use, copy, modify, and distribute the JPEG quality estimation
   routine for any purpose and without fee is hereby granted, provided that the
   above copyright notice appear in all copies and that both that copyright
   notice and this permission notice appear in supporting documentation.  This
   software is provided "as is" without express or implied warranty.

   Derived "imagsize" program Copyright (C)2004-2026 by Brian Lindholm.  All
   rights reserved.  Redistribution is permitted as described as above. */

/* various includes */
#include <config.h>

#if HAVE_INTTYPES_H
# include <inttypes.h>
#endif
#include <limits.h>
#ifdef HAVE_STDLIB_H
# include <stdlib.h>
#endif
#ifdef HAVE_STDIO_H
# include <stdio.h>
#endif
#ifdef HAVE_STRING_H
# include <string.h>
#endif
#ifdef HAVE_SYS_STAT_H
# include <sys/stat.h>
#endif
#ifdef HAVE_FCNTL_H
# include <fcntl.h>
#endif

#ifdef HAVE_UNISTD_H
# include <unistd.h>
# define OPTEND -1
#else
# define OPTEND EOF
#endif
#ifdef HAVE_GETOPT_H
# include <getopt.h>
#endif

#ifdef __MINGW32__
extern int getopt(int argc, char * const *argv, const char *optstring);
extern char *optarg;
extern int optind;
#endif

#ifdef DJGPP
unsigned short _djstat_flags = 63;  /* Speed up stat command for DJGPP */
#endif

#ifndef PATH_MAX
# define PATH_MAX 256
#endif

/* Define a few JPEG markers. */
#define M_SOF 0xc0
#define M_SOI 0xd8
#define M_EOI 0xd9
#define M_SOS 0xda
#define M_DQT 0xdb

/* Define return codes */
#define NOT_OK 0
#define OK 1

char *fmt0 = (sizeof(off_t) <= sizeof(long) ?
  "%s\tsize=%lu\tgeom=%dx%d\tdepth=%d\tqual=%.0f,%.0f\tsamp=%s\ttype=%s\n" :
  "%s\tsize=%llu\tgeom=%dx%d\tdepth=%d\tqual=%.0f,%.0f\tsamp=%s\ttype=%s\n");
char *fmt1 = (sizeof(off_t) <= sizeof(long) ?
  "-name %s -size %lu -geometry %dx%d -bitdepth %d -quality %.0f,%.0f -sampling %s -type %s\n" :
  "-name %s -size %llu -geometry %dx%d -bitdepth %d -quality %.0f,%.0f -sampling %s -type %s\n");
char *fmt2 = "<img src=\"%s\" alt=\"%s\" width=\"%d\" height=\"%d\">\n";
char *fmt0a = (sizeof(off_t) <= sizeof(long) ?
  "%s\tsize=%lu\tgeom=0x0\tdepth=0\tqual=0,0\tsamp=none\ttype=%s\n" :
  "%s\tsize=%llu\tgeom=0x0\tdepth=0\tqual=0,0\tsamp=none\ttype=%s\n");
char *fmt1a = (sizeof(off_t) <= sizeof(long) ?
  "-name %s -size %lu -geometry 0x0 -bitdepth 0 -quality 0,0 -sampling=none -type %s\n" :
  "-name %s -size %llu -geometry 0x0 -bitdepth 0 -quality 0,0 -sampling=none -type %s\n");
char *fmt2a = "<img src=\"%s\" alt=\"%s\" width=\"0\" height=\"0\">\n";

/* Sample quantization tables from JPEG spec --- only needed for
 * guesstimate of quality factor.  Note these are in zigzag order. */
static int std_luminance_quant_tbl[64] = {
  16, 11, 12, 14, 12, 10, 16, 14,
  13, 14, 18, 17, 16, 19, 24, 40,
  26, 24, 22, 22, 24, 49, 35, 37,
  29, 40, 58, 51, 61, 60, 57, 51,
  56, 55, 64, 72, 92, 78, 64, 68,
  87, 69, 55, 56, 80, 109, 81, 87,
  95, 98, 103, 104, 103, 62, 77, 113,
  121, 112, 100, 120, 92, 101, 103, 99
};
static int std_chrominance_quant_tbl[64] = {
  17, 18, 18, 24, 21, 24, 47, 26,
  26, 47, 99, 66, 56, 66, 99, 99,
  99, 99, 99, 99, 99, 99, 99, 99,
  99, 99, 99, 99, 99, 99, 99, 99,
  99, 99, 99, 99, 99, 99, 99, 99,
  99, 99, 99, 99, 99, 99, 99, 99,
  99, 99, 99, 99, 99, 99, 99, 99,
  99, 99, 99, 99, 99, 99, 99, 99
};
static int *deftabs[2] = { std_luminance_quant_tbl, std_chrominance_quant_tbl };

/* Other global variables */
static char errstr[200];

/* Function pre-defines */
static void help(FILE *where);
static int failure(char *reason);
static int get(FILE *f, unsigned char *chP);
static int mustbe(FILE *f, unsigned char ch);
static int gif(FILE *f, int *widthP, int *heightP, int *depth);
static int bmp(FILE *f, int *widthP, int *heightP, int *depth);
static int jpeg(FILE *f, int *widthP, int *heightP, double *qual, int *depth, int *typenum, char *sampling);
static int xbm(FILE *f, int *widthP, int *heightP);
static int xpm(FILE *f, int *widthP, int *heightP, int *depth);
static int ppm(FILE *f, int *widthP, int *heightP);
static int png(FILE *f, int *widthP, int *heightP, int *bpp, int *typenum);
static int tiff_be(FILE *f, int *widthP, int *heightP, int *depth);
static int tiff_le(FILE *f, int *widthP, int *heightP, int *depth);
static int webp(FILE *f, int *widthP, int *heightP, int *depth, int *typenum);
static int image_size(FILE *f, int *widthP, int *heightP, double *qual, int *depth, char *type, char *sample);

static void help(FILE *where)
{
  fprintf(where, "imagsize " PACKAGE_VERSION "\n"
    "usage: imagsize [-f file_list] [-h(elp)] [-n(ormal_format)] [-p(ipe)]\n"
    "         [-x(window_format)] [-v(erbose)] [-w(eb_format)] file...\n");
}

static int failure(char *reason)
{
  (void) strcpy(errstr, reason);
  return(NOT_OK);
}

static int get(FILE *f, unsigned char *chP)
{
  int ich = getc(f);
  if (ich == EOF) return(failure("imagsize error: unknown-EOF"));
  *chP = (unsigned char) ich;
  return(OK);
}

static int mustbe(FILE *f, unsigned char ch)
{
  char buffer[200];
  unsigned char ch2;

  if (!get(f, &ch2)) return(NOT_OK);
  if (ch2 != ch) {
    sprintf(buffer, "imagsize error: corrupted image -- expecting %x at %ld", (unsigned int) ch, ftell(f));
    return(failure(buffer));
  }
  return(OK);
}

static int gif(FILE *f, int *widthP, int *heightP, int *depth)
{
  unsigned char ch, w1, w2, h1, h2, b1;

  /* Read rest of signature. */
  if (!mustbe(f, (unsigned char) 'F')) return(NOT_OK);
  if (!mustbe(f, (unsigned char) '8')) return(NOT_OK);
  if (!get(f, &ch)) return(NOT_OK);
  if (ch != (unsigned char) '7' && ch != (unsigned char) '9') return(failure("imagsize error: corrupted image file"));
  if (!mustbe(f, 'a')) return(NOT_OK);
  /* Width and height are the next things in the file. */
  if (!get(f, &w1)) return(NOT_OK);
  if (!get(f, &w2)) return(NOT_OK);
  *widthP = (int) w2 * 256 + (int) w1;
  if (!get(f, &h1)) return(NOT_OK);
  if (!get(f, &h2)) return(NOT_OK);
  *heightP = (int) h2 * 256 + (int) h1;
  if (!get(f, &b1)) return(NOT_OK);
  *depth = ((int) b1 & 0x07) + 1;
  /* Indicate successful finish */
  return(OK);
}

static int bmp(FILE *f, int *widthP, int *heightP, int *depth)
{
  unsigned char s1, s2, s3, s4;
  long l;
  int bp, bpp;

  /* Skip rest of bitmap file header */
  if (fseek(f, 12, 1)) return(NOT_OK);
  /* Get size of bitmap header */
  if (!get(f, &s1)) return(NOT_OK);
  if (!get(f, &s2)) return(NOT_OK);
  if (!get(f, &s3)) return(NOT_OK);
  if (!get(f, &s4)) return(NOT_OK);
  l = (long) s4 * (long) 16777216 + (long) s3 * (long) 65536 + (long) s2 * (long) 256 + (long) s1;
  if (l <= 12) {  /* old style BMP */
    if (!get(f, &s1)) return(NOT_OK);
    if (!get(f, &s2)) return(NOT_OK);
    *widthP = (int) s2 * 256 + (int) s1;
    if (!get(f, &s1)) return(NOT_OK);
    if (!get(f, &s2)) return(NOT_OK);
    *heightP = (int) s2 * 256 + (int) s1;
  }
  else {  /* new style BMP */
    if (!get(f, &s1)) return(NOT_OK);
    if (!get(f, &s2)) return(NOT_OK);
    if (!get(f, &s3)) return(NOT_OK);
    if (!get(f, &s4)) return(NOT_OK);
    *widthP = (int) s4 * 16777216 + (int) s3 * 65536 + (int) s2 * 256 + (int) s1;
    if (!get(f, &s1)) return(NOT_OK);
    if (!get(f, &s2)) return(NOT_OK);
    if (!get(f, &s3)) return(NOT_OK);
    if (!get(f, &s4)) return(NOT_OK);
    *heightP = (int) s4 * 16777216 + (int) s3 * 65536 + (int) s2 * 256 + (int) s1;
  }
  /* Get bitmap depth */
  if (!get(f, &s1)) return(NOT_OK);
  if (!get(f, &s2)) return(NOT_OK);
  bp = (int) s2 * 256 + (int) s1;
  if (!get(f, &s1)) return(NOT_OK);
  if (!get(f, &s2)) return(NOT_OK);
  bpp = (int) s2 * 256 + (int) s1;
  *depth = bp * bpp;
  /* Indicate successful finish */
  return(OK);
}

static int jpeg(FILE *f, int *widthP, int *heightP, double *qual, int *depth, int *typenum, char *sampling)
{
  int allones, coefindex, done, entrysize, l, length, *reftable, tableindex, val;
  double cumsf, x;
  unsigned char ch, l1, l2, w1, w2, h1, h2, ti, v1, v2, s1, s2, s3, dep, bits, junk;

  /* JPEG blocks consist of a 0xff, a marker byte, a block size (two bytes, big-endian), and the rest of the block.
   * The block size includes itself - i.e. is the block size was 2 then there would be no additional bytes in the
   * block.  So, what we do here is read blocks until we get an SOF0-SOF3 marker, and then extract the width and
   * height from that. */

  done = 0x00;
  while (!feof(f)) {
    if (!mustbe(f, (unsigned char) 0xff)) return(NOT_OK);
    if (!get(f, &ch)) return(NOT_OK);
    if (!get(f, &l1)) return(NOT_OK);
    if (!get(f, &l2)) return(NOT_OK);
    l = (int) l1 * 256 + (int) l2;
    /* Is it one of the blocks we're looking for? */
    switch (ch) {
      case M_SOF + 0:  /* baseline DCT */
      case M_SOF + 1:  /* sequential DCT */
      case M_SOF + 2:  /* progressive DCT */
      case M_SOF + 3:  /* lossless */
      case M_SOF + 5:  /* differential sequential DCT */
      case M_SOF + 6:  /* differential progressive DCT */
      case M_SOF + 7:  /* differential lossless */
      case M_SOF + 9:  /* sequential DCT, arithmetic */
      case M_SOF + 10:  /* progressive DCT, arithmetic */
      case M_SOF + 11:  /* lossless, arithmetic */
      case M_SOF + 13:  /* differential sequential DCT, arithmetic */
      case M_SOF + 14:  /* differential progressive DCT, arithmetic */
      case M_SOF + 15:  /* differential lossless, arithmetic */
        *typenum = (int) (ch - (unsigned char) M_SOF);
        /* Get the sample precision. */
        if (!get(f, &bits)) return(NOT_OK);
        /* Read the height and width. */
        if (!get(f, &h1)) return(NOT_OK);
        if (!get(f, &h2)) return(NOT_OK);
        *heightP = (int) h1 * 256 + (int) h2;
        if (!get(f, &w1)) return(NOT_OK);
        if (!get(f, &w2)) return(NOT_OK);
        *widthP = (int) w1 * 256 + (int) w2;
        if (!get(f, &dep)) return(NOT_OK);
        /* Compute the depth. */
        *depth = (int) dep * (int) bits;
        /* Determine the sampling scheme */
        if ((int) dep == 1) {
          if (!get(f, &junk)) return(NOT_OK);  /* Skip the id. */
          if (!get(f, &s1)) return(NOT_OK);
          sampling[0] = (char) (s1 >> 4) + '0';
          sampling[1] = 'x';
          sampling[2] = (char) (s1 & 0x0f) + '0';
          sampling[3] = (char) 0;
          if (!get(f, &junk)) return(NOT_OK);  /* Skip the quantization table. */
          if (fseek(f, l - 11, 1)) return(NOT_OK);
        }
        else if ((int) dep == 3) {
          if (!get(f, &junk)) return(NOT_OK);  /* Skip the id. */
          if (!get(f, &s1)) return(NOT_OK);
          sampling[0] = (char) (s1 >> 4) + '0';
          sampling[1] = 'x';
          sampling[2] = (char) (s1 & 0x0f) + '0';
          sampling[3] = ',';
          if (!get(f, &junk)) return(NOT_OK);  /* Skip the quantization table. */
          if (!get(f, &junk)) return(NOT_OK);  /* Skip the id. */
          if (!get(f, &s2)) return(NOT_OK);
          sampling[4] = (char) (s2 >> 4) + '0';
          sampling[5] = 'x';
          sampling[6] = (char) (s2 & 0x0f) + '0';
          sampling[7] = ',';
          if (!get(f, &junk)) return(NOT_OK);  /* Skip the quantization table. */
          if (!get(f, &junk)) return(NOT_OK);  /* Skip the id. */
          if (!get(f, &s3)) return(NOT_OK);
          sampling[8] = (char) (s3 >> 4) + '0';
          sampling[9] = 'x';
          sampling[10] = (char) (s3 & 0x0f) + '0';
          sampling[11] = (char) 0;
          if (!get(f, &junk)) return(NOT_OK);  /* Skip the quantization table. */
          if (fseek(f, l - 17, 1)) return(NOT_OK);
        }
        else
          return(NOT_OK);
        done = done | 0x01;
        if (done == 3) return(OK);
        break;
      case M_DQT:
        length = l - 2;
        while (length > 0) {
          cumsf = 0.0;
          allones = 1;
          if (!get(f, &ti)) return(NOT_OK);
          length--;
          tableindex = (int) ti & 0x0f;
          entrysize = ((int) ti & 0xf0) >> 4;
          if (tableindex < 2) {
            reftable = deftabs[tableindex];
            for (coefindex = 0; coefindex < 64; coefindex++) {
              if (entrysize == 1) {
                if (!get(f, &v1)) return(NOT_OK);
                length--;
                if (!get(f, &v2)) return(NOT_OK);
                length--;
                val = (int) v1 * 256 + (int) v2;
              }
              else if (entrysize == 0) {
                if (!get(f, &v1)) return(NOT_OK);
                val = (int) v1;
                length--;
              }
              else
                return(NOT_OK);
              /* scaling factor in percent */
              x = 100.0 * (double) val / (double) reftable[coefindex];
              cumsf += x;
              /* separate check for all-ones table (Q = 100) */
              if (val != 0x01)
                allones = 0;
            }
            cumsf /= 64.0;  /* mean scale factor */
            if (allones)  /* special case for all-ones table */
              qual[tableindex] = 100.0;
            else if (cumsf <= 100.0)
              qual[tableindex] = (200.0 - cumsf) / 2.0;
            else
              qual[tableindex] = 5000.0 / cumsf;
          }
          else
            if (fseek(f, length, 1)) return(NOT_OK);
        }
        done = done | 0x02;
        if (done == 3) return(OK);
        break;
      default:
        if (fseek(f, l - 2, 1)) return(NOT_OK);
        break;
    }
  }
  return(failure("imagsize error: JPEG size not found"));
}

static int xbm(FILE *f, int *widthP, int *heightP)
{
  char line[500], name_and_type[500];

  if (fgets(line, (int) sizeof(line), f) == NULL) return(failure("imagsize error: XBM width not found"));
  if (sscanf(line, "efine %s %d", name_and_type, widthP) != 2) return(failure("imagsize error: XBM bogus width"));
  if (fgets(line, (int) sizeof(line), f) == NULL) return(failure("Ximagsize error: BM height not found"));
  if (sscanf(line, "#define %s %d", name_and_type, heightP) != 2) return(failure("imagsize error: XBM bogus height"));
  /* indicate successful finish */
  return(OK);
}

static int xpm(FILE *f, int *widthP, int *heightP, int *depth)
{
  char line[500];
  int colors, done, dummy, bits;

  /* Read rest of signature. */
  if (!mustbe(f, ' ')) return(NOT_OK);
  if (!mustbe(f, 'X')) return(NOT_OK);
  if (!mustbe(f, 'P')) return(NOT_OK);
  if (!mustbe(f, 'M')) return(NOT_OK);
  if (!mustbe(f, ' ')) return(NOT_OK);
  if (!mustbe(f, '*')) return(NOT_OK);
  if (!mustbe(f, '/')) return(NOT_OK);
  /* look for imagsize variable */
  done = 0;
  bits = 0;
  while ((done == 0) && (fgets(line, (int) sizeof(line), f) != NULL))
    if (sscanf(line, "\"%d %d %d %d\"", widthP, heightP, &colors, &dummy) == 4) {
      colors--;
      while (colors > 0) {
        bits++;
        colors /= 2;
      }
      *depth = bits;
      done = 1;
    }
  /* complain if not found */
  if (done == 0) {
    *widthP = *heightP = *depth = 0;
    return(failure("imagsize error: XPM width and height not found"));
  }
  /* indicate successful finish */
  return(OK);
}

static int ppm(FILE *f, int *widthP, int *heightP)
{
  char line[500];
  int done;

  /* look for imagsize variable */
  done = 0;
  while ((done == 0) && (fgets(line, (int) sizeof(line), f) != NULL))
    if (sscanf(line, "%d %d", widthP, heightP) == 2) done = 1;
  /* complain if not found */
  if (done == 0) {
    *widthP = *heightP = 0;
    return(failure("imagsize error: PPM width and height not found"));
  }
  /* indicate successful finish */
  return(OK);
}

static int tiff_be(FILE *f, int *widthP, int *heightP, int *depth)
{
  int bpp, count, done, i, num_dirent, offset, planes, tag, type, val;
  unsigned char w1, w2, w3, w4;

  /* Read rest of signature. */
  if (!mustbe(f, (unsigned char) 0x00)) return(NOT_OK);
  if (!mustbe(f, (unsigned char) 0x2a)) return(NOT_OK);
  /* Determine offset of IFD and go there */
  if (!get(f, &w1)) return(NOT_OK);
  if (!get(f, &w2)) return(NOT_OK);
  if (!get(f, &w3)) return(NOT_OK);
  if (!get(f, &w4)) return(NOT_OK);
  offset = (int) w1 * 16777216 + (int) w2 * 65536 + (int) w3 * 256 + (int) w4;
  if (fseek(f, offset, 0)) return(NOT_OK);
  /* Determine number of directory entries */
  if (!get(f, &w1)) return(NOT_OK);
  if (!get(f, &w2)) return(NOT_OK);
  num_dirent = (int) w1 * 256 + (int) w2;
  /* Parse through directory entries */
  done = 0;
  count = 0;
  bpp = 1;
  planes = 1;
  while ((done != 15) && (count < num_dirent)) {
    if (!get(f, &w1)) return(NOT_OK);
    if (!get(f, &w2)) return(NOT_OK);
    tag = (int) w1 * 256 + (int) w2;
    if (!get(f, &w1)) return(NOT_OK);
    if (!get(f, &w2)) return(NOT_OK);
    type = (int) w1 * 256 + (int) w2;
    for (i = 0; i < 4; i++) if (!get(f, &w1)) return(NOT_OK);
    if ((type == 1) || (type == 6)) {
      if (!get(f, &w1)) return(NOT_OK);
      if (!get(f, &w2)) return(NOT_OK);
      if (!get(f, &w3)) return(NOT_OK);
      if (!get(f, &w4)) return(NOT_OK);
      val = (int) w1;
    }
    else if ((type == 3) || (type == 8)) {
      if (!get(f, &w1)) return(NOT_OK);
      if (!get(f, &w2)) return(NOT_OK);
      if (!get(f, &w3)) return(NOT_OK);
      if (!get(f, &w4)) return(NOT_OK);
      val = (int) w1 * 256 + (int) w2;
    }
    else if ((type == 4) || (type == 9)) {
      if (!get(f, &w1)) return(NOT_OK);
      if (!get(f, &w2)) return(NOT_OK);
      if (!get(f, &w3)) return(NOT_OK);
      if (!get(f, &w4)) return(NOT_OK);
      val = (int) w1 * 16777216 + (int) w2 * 65536 + (int) w3 * 256 + (int) w4;
    }
    else
      val = 0;
    if (tag == 0x0100) {
      *widthP = val;
      done = done | 0x01;
    }
    else if (tag == 0x0101) {
      *heightP = val;
      done = done | 0x02;
    }
    else if (tag == 0x0102) {
      bpp = val;
      done = done | 0x04;
    }
    else if (tag == 0x0115) {
      planes = val;
      done = done | 0x08;
    }
    count++;
  }
  *depth = bpp * planes;
  /* indicate successful finish */
  return(OK);
}

static int tiff_le(FILE *f, int *widthP, int *heightP, int *depth)
{
  int bpp, count, done, i, num_dirent, offset, planes, tag, type, val;
  unsigned char w1, w2, w3, w4;

  /* Read rest of signature. */
  if (!mustbe(f, (unsigned char) 0x2a)) return(NOT_OK);
  if (!mustbe(f, (unsigned char) 0x00)) return(NOT_OK);
  /* Determine offset of IFD and go there */
  if (!get(f, &w1)) return(NOT_OK);
  if (!get(f, &w2)) return(NOT_OK);
  if (!get(f, &w3)) return(NOT_OK);
  if (!get(f, &w4)) return(NOT_OK);
  offset = (int) w4 * 16777216 + (int) w3 * 65536 + (int) w2 * 256 + (int) w1;
  if (fseek(f, offset, 0)) return(NOT_OK);
  /* Determine number of directory entries */
  if (!get(f, &w1)) return(NOT_OK);
  if (!get(f, &w2)) return(NOT_OK);
  num_dirent = (int) w2 * 256 + (int) w1;
  /* Parse through directory entries */
  done = 0;
  count = 0;
  bpp = 1;
  planes = 1;
  while ((done != 15) && (count < num_dirent)) {
    if (!get(f, &w1)) return(NOT_OK);
    if (!get(f, &w2)) return(NOT_OK);
    tag = (int) w2 * 256 + (int) w1;
    if (!get(f, &w1)) return(NOT_OK);
    if (!get(f, &w2)) return(NOT_OK);
    type = (int) w2 * 256 + (int) w1;
    for (i = 0; i < 4; i++) if (!get(f, &w1)) return(NOT_OK);
    if ((type == 1) || (type == 6)) {
      if (!get(f, &w1)) return(NOT_OK);
      if (!get(f, &w2)) return(NOT_OK);
      if (!get(f, &w3)) return(NOT_OK);
      if (!get(f, &w4)) return(NOT_OK);
      val = (int) w1;
    }
    else if ((type == 3) || (type == 8)) {
      if (!get(f, &w1)) return(NOT_OK);
      if (!get(f, &w2)) return(NOT_OK);
      if (!get(f, &w3)) return(NOT_OK);
      if (!get(f, &w4)) return(NOT_OK);
      val = (int) w2 * 256 + (int) w1;
    }
    else if ((type == 4) || (type == 9)) {
      if (!get(f, &w1)) return(NOT_OK);
      if (!get(f, &w2)) return(NOT_OK);
      if (!get(f, &w3)) return(NOT_OK);
      if (!get(f, &w4)) return(NOT_OK);
      val = (int) w4 * 16777216 + (int) w3 * 65536 + (int) w2 * 256 + (int) w1;
    }
    else
      val = 0;
    if (tag == 0x0100) {
      *widthP = val;
      done = done | 0x01;
    }
    else if (tag == 0x0101) {
      *heightP = val;
      done = done | 0x02;
    }
    else if (tag == 0x0102) {
      bpp = val;
      done = done | 0x04;
    }
    else if (tag == 0x0115) {
      planes = val;
      done = done | 0x08;
    }
    count++;
  }
  *depth = bpp * planes;
  /* indicate successful finish */
  return(OK);
}

static int png (FILE *f, int *widthP, int *heightP, int *bpp, int *typenum)
{
  unsigned char ch1, ch2, ch3, ch4, l1, l2, l3, l4, w1, w2, w3, w4, h1, h2, h3, h4, b1, t1;
  long l;

  /* Read rest of signature. */
  if (!mustbe(f, (unsigned char) 78)) return(NOT_OK);
  if (!mustbe(f, (unsigned char) 71)) return(NOT_OK);
  if (!mustbe(f, (unsigned char) 13)) return(NOT_OK);
  if (!mustbe(f, (unsigned char) 10)) return(NOT_OK);
  if (!mustbe(f, (unsigned char) 26)) return(NOT_OK);
  if (!mustbe(f, (unsigned char) 10)) return(NOT_OK);
  /* PNG chunks consist of a length, a chunk type, chunk data, and a CRC.  We read chunks until we get an IHDR chunk,
   * and then extract the width and height from that.  Actually, the IHDR chunk is required to come first, but we
   * might as well allow for broken encoders that don't obey that.
   */
  while (!feof(f)) {
    if (!get(f, &l1)) return(NOT_OK);
    if (!get(f, &l2)) return(NOT_OK);
    if (!get(f, &l3)) return(NOT_OK);
    if (!get(f, &l4)) return(NOT_OK);
    l = (long) l1 * 16777216 + (long) l2 * 65536 + (long) l3 * 256 + (long) l4;
    if (!get(f, &ch1)) return(NOT_OK);
    if (!get(f, &ch2)) return(NOT_OK);
    if (!get(f, &ch3)) return(NOT_OK);
    if (!get(f, &ch4)) return(NOT_OK);
    /* Is it the chunk we're looking for? */
    if (ch1 == (unsigned char) 'I' && ch2 == (unsigned char) 'H' && ch3 == (unsigned char) 'D' &&
        ch4 == (unsigned char) 'R') {
      /* Read the height, width, bits per plane, and colortype. */
      if (!get(f, &w1)) return(NOT_OK);
      if (!get(f, &w2)) return(NOT_OK);
      if (!get(f, &w3)) return(NOT_OK);
      if (!get(f, &w4)) return(NOT_OK);
      *widthP = (int) w1 * 16777216 + (int) w2 * 65536 + (int) w3 * 256 + (int) w4;
      if (!get(f, &h1)) return(NOT_OK);
      if (!get(f, &h2)) return(NOT_OK);
      if (!get(f, &h3)) return(NOT_OK);
      if (!get(f, &h4)) return(NOT_OK);
      *heightP = (int) h1 * 16777216 + (int) h2 * 65536 + (int) h3 * 256 + (int) h4;
      if (!get(f, &b1)) return(NOT_OK);
      *bpp = (int) b1;
      if (!get(f, &t1)) return(NOT_OK);
      *typenum = (int) t1;
      return(OK);
    }
    if (fseek (f, l + 4, 1)) return(NOT_OK);  /* Not the block we want.  Skip it. */
  }
  return(failure("imagsize error: PNG size not found"));
}

static int webp (FILE *f, int *widthP, int *heightP, int *depth, int *typenum)
{
  unsigned char ch1, ch2, ch3, ch4, l1, l2, l3, l4, h1, h2, h3, r1, r2, r3, r4, w1, w2, w3;
  long l, val;
  char line[500], name_and_type[500];

  /* Read rest of signature. */
  if (!mustbe(f, 'F')) return(NOT_OK);
  if (!mustbe(f, 'F')) return(NOT_OK);
  if (fseek(f, 4, 1)) return(NOT_OK);
  if (!mustbe(f, 'W')) return(NOT_OK);
  if (!mustbe(f, 'E')) return(NOT_OK);
  if (!mustbe(f, 'B')) return(NOT_OK);
  if (!mustbe(f, 'P')) return(NOT_OK);
  /* scan through files for chunks */
  while (!feof(f)) {
    /* Read chunk header. */
    if (!get(f, &ch1)) return(NOT_OK);
    if (!get(f, &ch2)) return(NOT_OK);
    if (!get(f, &ch3)) return(NOT_OK);
    if (!get(f, &ch4)) return(NOT_OK);
    /* Read chunk size. */
    if (!get(f, &l1)) return(NOT_OK);
    if (!get(f, &l2)) return(NOT_OK);
    if (!get(f, &l3)) return(NOT_OK);
    if (!get(f, &l4)) return(NOT_OK);
    l = (long) l4 * 16777216 + (long) l3 * 65536 + (long) l2 * 256 + (long) l1;
    /* Is this a VP8 chunk? */
    if (ch1 == (unsigned char) 'V' && ch2 == (unsigned char) 'P' && ch3 == (unsigned char) '8') {
      if (ch4 == (unsigned char) ' ') {
        *typenum = 1;
        if (fseek(f, 6, 1)) return(NOT_OK);
        if (!get(f, &w1)) return(NOT_OK);
        if (!get(f, &w2)) return(NOT_OK);
        *widthP = (int) w2 * 256 + (int) w1;
        if (!get(f, &h1)) return(NOT_OK);
        if (!get(f, &h2)) return(NOT_OK);
        *heightP = (int) h2 * 256 + (int) h1;
        *depth = 24;
        return(OK);
      }
      else if (ch4 == (unsigned char) 'L') {
        *typenum = 2;
        if (fseek(f, 1, 1)) return(NOT_OK);
        if (!get(f, &r1)) return(NOT_OK);
        if (!get(f, &r2)) return(NOT_OK);
        if (!get(f, &r3)) return(NOT_OK);
        if (!get(f, &r4)) return(NOT_OK);
        l = (long) r4 * 16777216 + (long) r3 * 65536 + (long) r2 * 256 + (long) r1;
        *widthP = (l & 16383) + 1;
        *heightP = ((l >> 14) & 16383) + 1;
        *depth = 24;
        return(OK);
      }
    }
    if (fseek(f, l, 1)) return(NOT_OK);  /* Not the block we want.  Skip it. */
  }
  return(failure("imagsize error: WEBP size not found"));
}

static int image_size (FILE *f, int *widthP, int *heightP, double *qual, int *depth, char *type, char *sampling)
{
  unsigned char ch1, ch2;
  int bpp, rc, typenum;

  *widthP = 0;
  *heightP = 0;
  qual[0] = 0.0;
  qual[1] = 0.0;
  *depth = 0;
  strcpy(type, "unknown");
  strcpy(sampling, "none");

  if (!get(f, &ch1)) return(NOT_OK);
  if (!get(f, &ch2)) return(NOT_OK);
  if (ch1 == (unsigned char) 'G' && ch2 == (unsigned char) 'I') {
    rc = gif(f, widthP, heightP, depth);
    strcpy(type, "gif");
    return(rc);
  }
  else if (ch1 == (unsigned char) 'B' && ch2 == (unsigned char) 'M') {
    rc = bmp(f, widthP, heightP, depth);
    if (*depth <= 16)
      strcpy(type, "bmp-pal");
    else
      strcpy(type, "bmp-rgb");
    return(rc);
  }
  else if (ch1 == (unsigned char) 0xff && ch2 == (unsigned char) M_SOI) {
    rc = jpeg(f, widthP, heightP, qual, depth, &typenum, sampling);
    if (typenum == 0)
      strcpy(type, "jpg-base");
    else if (typenum == 1)
      strcpy(type, "jpg-sequ");
    else if (typenum == 2)
      strcpy(type, "jpg-prog");
    else if (typenum == 3)
      strcpy(type, "jpg-lossless");
    else if (typenum == 5)
      strcpy(type, "jpg-diff-sequ");
    else if (typenum == 6)
      strcpy(type, "jpg-diff-prog");
    else if (typenum == 7)
      strcpy(type, "jpg-diff-lossless");
    else if (typenum == 9)
      strcpy(type, "jpg-sequ-arith");
    else if (typenum == 10)
      strcpy(type, "jpg-prog-arith");
    else if (typenum == 11)
      strcpy(type, "jpg-lossless-arith");
    else if (typenum == 13)
      strcpy(type, "jpg-diff-sequ-arith");
    else if (typenum == 14)
      strcpy(type, "jpg-diff-prog-arith");
    else if (typenum == 15)
      strcpy(type, "jpg-diff-lossless-arith");
    else
      strcpy(type, "jpg-unknown");
    return(rc);
  }
  else if (ch1 == (unsigned char) 'R' && ch2 == (unsigned char) 'I') {
    rc = webp(f, widthP, heightP, depth, &typenum);
    if (typenum == 1)
      strcpy(type, "webp-vp8");
    else if (typenum == 2)
      strcpy(type, "webp-lossless");
    else
      strcpy(type, "webp-unknown");
    return(rc);
  }
  else if (ch1 == (unsigned char) '#' && ch2 == (unsigned char) 'd') {
    rc = xbm(f, widthP, heightP);
    strcpy(type, "xbm");
    return(rc);
  }
  else if (ch1 == (unsigned char) '/' && ch2 == (unsigned char) '*') {
    rc = xpm(f, widthP, heightP, depth);
    strcpy(type, "xpm");
    return(rc);
  }
  else if (ch1 == (unsigned char) 137 && ch2 == (unsigned char) 80) {
    *depth = 0;
    rc = png(f, widthP, heightP, &bpp, &typenum);
    if (typenum == 0) {
      *depth = bpp;
      strcpy(type, "png-gray");
    }
    else if (typenum == 2) {
      *depth = 3 * bpp;
      strcpy(type, "png-rgb");
    }
    else if (typenum == 3) {
      *depth = bpp;
      strcpy(type, "png-pal");
    }
    else if (typenum == 4) {
      *depth = bpp;
      strcpy(type, "png-gray-alpha");
    }
    else if (typenum == 6) {
      *depth = 3 * bpp;
      strcpy(type, "png-rgb-alpha");
    }
    else
      strcpy(type, "png-unknown");
    return(rc);
  }
  else if ((ch1 == (unsigned char) 'P') && (ch2 >= (unsigned char) '1') && (ch2 <= (unsigned char) '6')) {
    rc = ppm(f, widthP, heightP);
    if (ch2 == (unsigned char) '1')
      strcpy(type, "pbm-text");
    else if (ch2 == (unsigned char) '2')
      strcpy(type, "pgm-text");
    else if (ch2 == (unsigned char) '3')
      strcpy(type, "ppm-text");
    else if (ch2 == (unsigned char) '4')
      strcpy(type, "pbm-raw");
    else if (ch2 == (unsigned char) '5')
      strcpy(type, "pgm-raw");
    else if (ch2 == (unsigned char) '6')
      strcpy(type, "ppm-raw");
    return(rc);
  }
  else if (ch1 == (unsigned char) 'M' && ch2 == (unsigned char) 'M') {
    rc = tiff_be(f, widthP, heightP, depth);
    strcpy(type, "tiff-be");
    return(rc);
  }
  else if (ch1 == (unsigned char) 'I' && ch2 == (unsigned char) 'I') {
    rc = tiff_le(f, widthP, heightP, depth);
    strcpy(type, "tiff-le");
    return(rc);
  }
  else {
    strcpy(type, "unknown");
    return(failure("imagsize warning: unknown image type"));
  }
}

static int print_size(char *filename, int format, int verbose)
{
  FILE *f;
  char sampling[16], type[200];
  int ok, width, height, depth;
  double qual[2];
  off_t size;
  struct stat file_stats;

  if (stat(filename, &file_stats)) {
    fprintf(stderr, "imagsize error: can't determine size of %s\n", filename);
    return(NOT_OK);
  }
  if (((file_stats.st_mode & S_IFDIR) == S_IFDIR) || ((file_stats.st_mode & S_IFREG) != S_IFREG)) {
    size = (off_t) 0;
    return(OK);
  }
  size = file_stats.st_size;
  f = fopen (filename, "rb");
  if (f == NULL) {
    perror (filename);
    return(NOT_OK);
  }
#ifdef HAVE_POSIX_FADVISE
  posix_fadvise(fileno(f), 0, 0, POSIX_FADV_RANDOM);
#endif
  ok = image_size (f, &width, &height, qual, &depth, type, sampling);
  (void) fclose (f);
  if (ok) {
    if (depth != 24)
      qual[1] = qual[0];
    if (verbose || (width > 0)) {
      if (format == 0)
        fprintf(stdout, fmt0, filename, size, width, height, depth, qual[0], qual[1], sampling, type);
      else if (format == 1)
        fprintf(stdout, fmt1, filename, size, width, height, depth, qual[0], qual[1], sampling, type);
      else if (format == 2)
        fprintf(stdout, fmt2, filename, filename, width, height);
    }
  }
  else if (verbose) {
    if (format == 0)
      fprintf(stdout, fmt0a, filename, size, errstr);
    else if (format == 1)
      fprintf(stdout, fmt1a, filename, size, errstr);
    else if (format == 2)
      fprintf(stdout, fmt2a, filename, filename);
  }
  return(ok);
}

int main (int argc, char **argv)
{
  FILE *infile;
  char filename[PATH_MAX], *listname, *newline, *rc;
  int allok, argn, format, ok, opt, use_file, use_pipe, verbose;

  /* parse options */
  format = 0;  /* 0 = full, 1 = X-window style, 2 = Web-page style */
  listname = "";
  verbose = 0;
  use_file = 0;
  use_pipe = 0;
  while ((opt = getopt(argc, argv, "f:hnpvwx")) != OPTEND) switch (opt) {
    case 'f':
      use_file = 1;
      listname = optarg;
      break;
    case 'h':
      help(stdout);
      return(0);
    case 'n':
      format = 0;
      break;
    case 'p':
      use_pipe = 1;
      break;
    case 'v':
      verbose = 1;
      break;
    case 'w':
      format = 2;
      break;
    case 'x':
      format = 1;
      break;
    case '?':
      help(stderr);
      return(1);
  }
  if ((optind == argc) && (use_file == 0) && (use_pipe == 0)) {
    help(stdout);
    return(0);
  }
  allok = 1;
  /* process files in listed in file specified by -f option */
  if (use_file) {
    infile = fopen (listname, "r");
    if (infile == NULL)
      fprintf(stderr, "imagsize error: can't open %s!\n", listname);
    else {
      while (!feof(infile)) {
        rc = fgets(filename, PATH_MAX - 1, infile);
        if (rc != NULL) {
          newline = strchr(filename, '\n');
          if (newline != NULL) *newline = '\0';
          if (strlen (filename) != 0) {
            ok = print_size (filename, format, verbose);
            allok &= ok;
          }
        }
      }
      (void) fclose (infile);
    }
  }
  /* process files listed on stdin (i.e., the -p option) */
  if (use_pipe) while (!feof(stdin)) {
    rc = fgets(filename, PATH_MAX - 1, stdin);
    if (rc != NULL) {
      newline = strchr(filename, '\n');
      if (newline != NULL) *newline = '\0';
      if (strlen (filename) != 0) {
        ok = print_size (filename, format, verbose);
        allok &= ok;
      }
    }
  }
  /* process files given in the argument list */
  for (argn = optind; argn < argc; argn++) {
    ok = print_size (argv[argn], format, verbose);
    allok &= ok;
  }
  /* return final exit code */
  if (!allok) return(1);
  return(0);
}
