/* image_size - figure out the image size of GIF, JPEG, XBM, or PNG files
**
** Copyright (C)1997,1998 by Jef Poskanzer <jef@mail.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.
*/


#include <stdlib.h>
#include <stdio.h>
#include <string.h>

/* Define a few JPEG markers. */
#define M_SOF0 0xc0
#define M_SOF3 0xc3
#define M_SOI 0xd8
#define M_EOI 0xd9
#define M_SOS 0xda


/* Forwards. */
int image_size( FILE* f, int* widthP, int* heightP );
const char* image_size_errstr( void );
static int failure( const char* reason );
static int gif( FILE* f, int* widthP, int* heightP );
static int jpeg( FILE* f, int* widthP, int* heightP );
static int xbm( FILE* f, int* widthP, int* heightP );
static int png( FILE* f, int* widthP, int* heightP );
static int get( FILE* f, unsigned char* chP );
static int mustbe( FILE* f, unsigned char ch );


#ifndef nomain

int
main( int argc, const char** argv )
    {
    int allok, ok;
    int argn;
    int width, height;
    FILE* f;

    allok = 1;
    if ( argc == 1 )
	{
	ok = image_size( stdin, &width, &height );
	if ( ok )
	    (void) printf( "stdin: %d %d\n", width, height );
	else
	    (void) fprintf( stderr, "stdin: %s\n", image_size_errstr() );
	allok &= ok;
	}
    else
        {
	for ( argn = 1; argn < argc; ++argn )
	    {
	    f = fopen( argv[argn], "r" );
	    if ( f == (FILE*) 0 )
		perror( argv[argn] );
	    else
		{
		ok = image_size( f, &width, &height );
		(void) fclose( f );
		if ( ok )
		    (void) printf( "%s: %d %d\n", argv[argn], width, height );
		else
		    (void) fprintf( stderr, "%s: %s\n", argv[argn], image_size_errstr() );
		allok &= ok;
		}
	    }
        }

    if ( ! allok )
	exit( 1 );
    exit( 0 );
    }

#endif /* ! nomain */


int
image_size( FILE* f, int* widthP, int* heightP )
    {
    unsigned char ch1, ch2;

    if ( ! get( f, &ch1 ) ) return 0;
    if ( ! get( f, &ch2 ) ) return 0;
    if ( ch1 == 'G' && ch2 == 'I' )
	return gif( f, widthP, heightP );
    else if ( ch1 == 0xff && ch2 == M_SOI )
	return jpeg( f, widthP, heightP );
    else if ( ch1 == '#' && ch2 == 'd' )
	return xbm( f, widthP, heightP );
    else if ( ch1 == 137 && ch2 == 80 )
	return png( f, widthP, heightP );
    else
	return failure( "can't figure out image type" );
    }


static char errstr[200];

const char*
image_size_errstr( void )
    {
    return errstr;
    }

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


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

    /* Read rest of signature. */
    if ( ! mustbe( f, 'F' ) ) return 0;
    if ( ! mustbe( f, '8' ) ) return 0;
    if ( ! get( f, &ch ) ) return 0;
    if ( ch != '7' && ch != '9' )
	return failure( "corrupted image file" );
    if ( ! mustbe( f, 'a' ) ) return 0;

    /* Width and height are the next things in the file. */
    if ( ! get( f, &w1 ) ) return 0;
    if ( ! get( f, &w2 ) ) return 0;
    if ( ! get( f, &h1 ) ) return 0;
    if ( ! get( f, &h2 ) ) return 0;
    *widthP = (int) w2 * 256 + (int) w1;
    *heightP = (int) h2 * 256 + (int) h1;
    return 1;
    }


static int
jpeg( FILE* f, int* widthP, int* heightP )
    {
    unsigned char ch, l1, l2, w1, w2, h1, h2;
    int l, i;

    /* 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.
    */
    for (;;)
	{
	if ( ! mustbe( f, 0xff ) ) return 0;
	if ( ! get( f, &ch ) ) return 0;
	if ( ! get( f, &l1 ) ) return 0;
	if ( ! get( f, &l2 ) ) return 0;
	l = (int) l1 * 256 + (int) l2;
	/* Is it the block we're looking for? */
	if ( ch >= M_SOF0 && ch <= M_SOF3 )
	    {
	    /* Toss the sample precision. */
	    if ( ! get( f, &ch ) ) return 0;
	    /* Read the height and width. */
	    if ( ! get( f, &h1 ) ) return 0;
	    if ( ! get( f, &h2 ) ) return 0;
	    if ( ! get( f, &w1 ) ) return 0;
	    if ( ! get( f, &w2 ) ) return 0;
	    *widthP = (int) w1 * 256 + (int) w2;
	    *heightP = (int) h1 * 256 + (int) h2;
	    return 1;
	    }
	if ( ch == M_SOS || ch == M_EOI )
	    return failure( "JPEG size not found" );
	/* Not the block we want.  Read and toss. */
	for ( i = 2; i < l; ++i )
	    if ( ! get( f, &ch ) ) return 0;
	}
    }


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

    if ( fgets( line, sizeof(line), f ) == (char*) 0 )
	return failure( "XBM width not found" );
    if ( sscanf( line, "efine %s %d", name_and_type, widthP ) != 2 )
	return failure( "XBM bogus width" );
    if ( fgets( line, sizeof(line), f ) == (char*) 0 )
	return failure( "XBM height not found" );
    if ( sscanf( line, "#define %s %d", name_and_type, heightP ) != 2 )
	return failure( "XBM bogus height" );
    return 1;
    }


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

    /* Read rest of signature. */
    if ( ! mustbe( f, 78 ) ) return 0;
    if ( ! mustbe( f, 71 ) ) return 0;
    if ( ! mustbe( f, 13 ) ) return 0;
    if ( ! mustbe( f, 10 ) ) return 0;
    if ( ! mustbe( f, 26 ) ) return 0;
    if ( ! mustbe( f, 10 ) ) return 0;

    /* 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.
    */
    for (;;)
	{
	if ( ! get( f, &l1 ) ) return 0;
	if ( ! get( f, &l2 ) ) return 0;
	if ( ! get( f, &l3 ) ) return 0;
	if ( ! get( f, &l4 ) ) return 0;
	l = (long) l1 * 16777216 + (long) l2 * 65536 + (long) l3 * 256 + (long) l4;
	if ( ! get( f, &ch1 ) ) return 0;
	if ( ! get( f, &ch2 ) ) return 0;
	if ( ! get( f, &ch3 ) ) return 0;
	if ( ! get( f, &ch4 ) ) return 0;
	/* Is it the chunk we're looking for? */
	if ( ch1 == 'I' && ch2 == 'H' && ch3 == 'D' && ch4 == 'R' )
	    {
	    /* Read the height and width. */
	    if ( ! get( f, &w1 ) ) return 0;
	    if ( ! get( f, &w2 ) ) return 0;
	    if ( ! get( f, &w3 ) ) return 0;
	    if ( ! get( f, &w4 ) ) return 0;
	    if ( ! get( f, &h1 ) ) return 0;
	    if ( ! get( f, &h2 ) ) return 0;
	    if ( ! get( f, &h3 ) ) return 0;
	    if ( ! get( f, &h4 ) ) return 0;
	    *widthP = (long) w1 * 16777216 + (long) w2 * 65536 + (long) w3 * 256 + (long) w4;
	    *heightP = (long) h1 * 16777216 + (long) h2 * 65536 + (long) h3 * 256 + (long) h4;
	    return 1;
	    }
	/* Not the block we want.  Read and toss. */
	for ( i = 0; i < l + 4; ++i )
	    if ( ! get( f, &ch1 ) ) return 0;
	}
    /* Should never get here. */
    /* return failure( "PNG size not found" ); */
    }


static int
get( FILE* f, unsigned char* chP )
    {
    int ich = getc( f );
    if ( ich == EOF )
	return failure( "premature EOF" );
    *chP = (unsigned char) ich;
    return 1;
    }


static int
mustbe( FILE* f, unsigned char ch )
    {
    unsigned char ch2;

    if ( ! get( f, &ch2 ) ) return 0;
    if ( ch2 != ch )
	return failure( "corrupted image file" );
    return 1;
    }
