#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"

#include "ppport.h"

#include "headers.h"

#include <stdio.h>

static const size_t BUF_SIZE = 102400;

typedef enum { 
	UNKNOWN, 
	IMAGE_BMP, 
	IMAGE_GIF, 
	IMAGE_PNG, 
	IMAGE_PSD, 
	IMAGE_JPEG, 
	IMAGE_TIFF,
	IMAGE_ICO
} ImageType;

static const unsigned char  bmp_sig[2]    = {'B', 'M'};

static const unsigned char  gif_sig[3]    = {'G', 'I', 'F'};
static const unsigned char  jpg_sig[3]    = { 0xFF, 0xD8, 0xFF };

static const unsigned char  psd_sig[4]    = {'8', 'B', 'P', 'S'};
static const unsigned char  iitiff_sig[4] = {'I', 'I', 0x2A, 0x00};
static const unsigned char  mmtiff_sig[4] = {'M', 'M', 0x00, 0x2A};
static const unsigned char  ico_sig[8]    = { 0x00, 0x00, 0x01, 0x00};

static const unsigned char  png_sig[8]    = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A };


static const char *bmp_compression[7] = {"None",
                                         "RLE8",
                                         "RLE4",
                                         "BITFIELDS",
                                         "JPEG",
                                         "PNG"
                                        };

static const char *psd_color[10] = {"Mono",
                                    "Grayscale",
                                    "Indexed",
                                    "RGB",
                                    "CMYK",
                                    NULL,
                                    NULL,
                                    "Multichannel"
                                    "Duotone",
                                    "LAB"
                                   };
                                   
static const char *png_color[10] = {"Gray",
                                    NULL,
                                    "RGB",
                                    "Indexed",
                                    "GrayA",
                                    NULL,
                                    "RGBA"
                                   };

static const char *png_filter[5] = {"None",
                                    "Sub",
                                    "Up",
                                    "Average",
                                    "Paeth"
                                   };

static const char *tiff_color[11] = {"WhiteIsZero",
                                     "BlackIsZero",
                                     "RGB",
                                     "Indexed",
                                     "Transparency Mask",
                                     "CMYK",
                                     "YCbCr",
                                     "CIE L*a*b*",
                                     "ICC L*a*b*",
                                     "ITU L*a*b*"
                                    };
                                     



static uint16_t get_uint16(const unsigned char * data, const unsigned short rev_order)
{
	if (rev_order) 
		return (data[0] << 8) | data[1];
	else
		return (data[1] << 8) | data[0];

}

static uint32_t get_uint32(const unsigned char * data, const unsigned short rev_order)
{
	if (rev_order)
		return (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3];
	else
		return (data[3] << 24) | (data[2] << 16) | (data[1] << 8) | data[0];
}


ImageType get_image_type(const unsigned char *data, const size_t size)
{
	// 2 bytes
	if (size >= 2)
	{
		if (memcmp(data, bmp_sig, 2) == 0) return IMAGE_BMP; 
	}

	if (size >= 3)
	{
		if (memcmp(data, gif_sig, 3) == 0) return IMAGE_GIF;

		if (memcmp(data, jpg_sig, 3) == 0) return IMAGE_JPEG; 
	}

	if (size >= 4)
	{
		if (memcmp(data, psd_sig, 4) == 0) return IMAGE_PSD;

		if (memcmp(data, iitiff_sig, 4) == 0) return IMAGE_TIFF;

		if (memcmp(data, mmtiff_sig, 4) == 0) return IMAGE_TIFF;

		if (memcmp(data, ico_sig, 4) == 0) return IMAGE_ICO;
	}

	if (size >= 8)
	{
		if (memcmp(data, png_sig, 8) == 0) { return IMAGE_PNG; }
	}

}


IV get_png_info(const unsigned char *data, const size_t size, HV *hash)
{
	if (size < sizeof(ImageInfoPNG) + 16) return 0;

	ImageInfoPNG *png = (ImageInfoPNG*) (data + 16);
	
	uint32_t width  = get_uint32(png->width, 1);
	uint32_t height = get_uint32(png->height, 1);

	hv_store(hash, "file_media_type", 15, newSVpv("image/png", 0), 0);
	hv_store(hash, "file_ext", 8, newSVpv("png", 0), 0);
	
	
	hv_store(hash, "width", 5, newSViv(width), 0);
	hv_store(hash, "height", 6, newSViv(height), 0);
	hv_store(hash, "bits", 4, newSViv(png->depth), 0);
	
	if (png->compression == 0)
		hv_store(hash, "compression", 11, newSVpv("Deflate", 0), 0);
	else
		hv_store(hash, "compression", 11, newSViv(png->compression), 0);
	
	hv_store(hash, "interlace", 9, newSVpv(png->interlace == 1 ? "Adam7" : "None", 0), 0);
	
	if (png->filter < sizeof(png_filter) / sizeof(png_filter[0]))
		hv_store(hash, "filter", 6, newSVpv(png_filter[png->filter], 0), 0);

	if (png->color_type < sizeof(png_color) / sizeof(png_color[0]))
		hv_store(hash, "color_type", 10, newSVpv(png_color[png->color_type], 0), 0);

	return 1;
}

IV get_jpeg_info(const unsigned char *data, const size_t size, HV *hash) 
{
	unsigned long pos = 4;

	uint16_t block_length = get_uint16(data + pos, 1);

	hv_store(hash, "file_media_type", 15, newSVpv("image/jpeg", 0), 0);
	hv_store(hash, "file_ext", 8, newSVpv("jpg", 0), 0);
	
	
	short unsigned int jpeg_type = 0;
	
	while(pos < size)
	{
		pos += block_length;

		if (pos >= size) return 0; 
		if (data[pos] != 0xFF) return 0;
		
		jpeg_type = data[pos + 1];
		
		switch(jpeg_type)
		{
			case JPEG_TYPE_BASELINE:  
				hv_store(hash, "jpeg_type", 9, newSVpv("Baseline", 0), 0); break;
			case JPEG_TYPE_EXTENDED_SEQUENTIAL:  
				hv_store(hash, "jpeg_type", 9, newSVpv("Extended sequential", 0), 0); break;
			case JPEG_TYPE_PROGRESSIVE:  
				hv_store(hash, "jpeg_type", 9, newSVpv("Progressive", 0), 0); break;
			case JPEG_TYPE_LOSSLESS:  
				hv_store(hash, "jpeg_type", 9, newSVpv("Lossless", 0), 0); break;
			case JPEG_TYPE_DIFFERENTIAL_SEQUENTIAL:  
				hv_store(hash, "jpeg_type", 9, newSVpv("Differential sequential", 0), 0); break;
			case JPEG_TYPE_DIFFERENTIAL_PROGRESSIVE:  
				hv_store(hash, "jpeg_type", 9, newSVpv("Differential progressive", 0), 0); break;
			case JPEG_TYPE_DIFFERENTIAL_LOSSLESS:  
				hv_store(hash, "jpeg_type", 9, newSVpv("Differential lossless", 0), 0); break;
			case JPEG_TYPE_EXTENDED_SEQUENTIAL_AC:  
				hv_store(hash, "jpeg_type", 9, newSVpv("Extended sequential, arithmetic coding", 0), 0); break;
			case JPEG_TYPE_PROGRESSIVE_AC:  
				hv_store(hash, "jpeg_type", 9, newSVpv("Progressive, arithmetic coding", 0), 0); break;
			case JPEG_TYPE_LOSSLESS_AC:  
				hv_store(hash, "jpeg_type", 9, newSVpv("Lossless, arithmetic coding", 0), 0); break;
			case JPEG_TYPE_DIFFERENTIAL_SEQUENTIAL_AC:  
				hv_store(hash, "jpeg_type", 9, newSVpv("Differential sequential, arithmetic coding", 0), 0); break;
			case JPEG_TYPE_DIFFERENTIAL_PROGRESSIVE_AC:  
				hv_store(hash, "jpeg_type", 9, newSVpv("Differential progressive, arithmetic coding", 0), 0); break;
			case JPEG_TYPE_DIFFERENTIAL_LOSSLESS_AC:  
				hv_store(hash, "jpeg_type", 9, newSVpv("Differential lossless, arithmetic coding", 0), 0); break;
			default:
				jpeg_type = 0;
		}

		if (jpeg_type > 0)
		{
			// 0xFFC? block: [0xFFC?][ushort length][uchar precision][ushort x][ushort y]
			uint8_t  bits     = data[pos + 4];
			uint16_t height   = get_uint16(data + pos + 5, 1); 
			uint16_t width    = get_uint16(data + pos + 7, 1); 
			uint8_t  num_comp = data[pos + 9];
			
			hv_store(hash, "width", 5, newSViv(width), 0);
			hv_store(hash, "height", 6, newSViv(height), 0);
			hv_store(hash, "bits", 4, newSViv(bits), 0);
			hv_store(hash, "samples_per_pixel", 17, newSViv(num_comp), 0);
			
			
			switch(num_comp)
			{
				case 1:
					hv_store(hash, "color_type", 10, newSVpv("Gray", 0), 0); break;
				case 3:
					hv_store(hash, "color_type", 10, newSVpv("YCbCr", 0), 0); break; //or RGB ?
				case 4:
					hv_store(hash, "color_type", 10, newSVpv("CMYK", 0), 0); break; //or YCCK ?
			}
			
			return 1;
		}
		else
		{
			pos += 2;
			// Next block
			block_length = get_uint16(data + pos, 1);
		}
	}

	return 1;
}

IV get_bmp_info(const unsigned char *data, const size_t size, HV *hash) 
{
	if (size < (14 + sizeof(ImageInfoBMP))) return 0;
	
	ImageInfoBMP *bmp = (ImageInfoBMP*) (data + 14);
	
	hv_store(hash, "file_media_type", 15, newSVpv("image/bmp", 0), 0);
	hv_store(hash, "file_ext", 8, newSVpv("bmp", 0), 0);
	
	hv_store(hash, "width", 5, newSViv(bmp->biWidth), 0);
	hv_store(hash, "height", 6, newSViv(bmp->biHeight), 0);
	hv_store(hash, "bits", 4, newSViv(bmp->biBitCount), 0);
	hv_store(hash, "x_pixels_per_meter", 18, newSViv(bmp->biXPelsPerMeter), 0);
	hv_store(hash, "y_pixels_per_meter", 18, newSViv(bmp->biYPelsPerMeter), 0);
	hv_store(hash, "colors_used", 11, newSViv(bmp->biClrUsed), 0);
	hv_store(hash, "colors_important", 16, newSViv(bmp->biClrUsed), 0);
	
	if (bmp->biCompression <= sizeof(bmp_compression)/sizeof(bmp_compression[0]))
		hv_store(hash, "compression", 11, newSVpv(bmp_compression[bmp->biCompression], 0), 0);

	if (bmp->biBitCount < 24) 
		hv_store(hash, "color_type", 10, newSVpv("Indexed", 0), 0);
	else
		hv_store(hash, "color_type", 10, newSVpv("RGB", 0), 0);

	return 1;
}

IV get_gif_info(const unsigned char *data, const size_t size, HV *hash) 
{

	if (size < sizeof(ImageInfoGIF)) return 0;

	ImageInfoGIF *gif = (ImageInfoGIF*) (data);
	
	hv_store(hash, "file_media_type", 15, newSVpv("image/gif", 0), 0);
	hv_store(hash, "file_ext", 8, newSVpv("gif", 0), 0);
	
	hv_store(hash, "width", 5, newSViv(gif->ScreenWidth), 0);
	hv_store(hash, "height", 6, newSViv(gif->ScreenHeight), 0);
	hv_store(hash, "color_type", 10, newSVpv("Indexed", 0), 0);
	
	hv_store(hash, "sorted_colors", 13, newSViv((gif->Packed & 0x08) ? 1 : 0), 0);
	
	hv_store(hash, "bits", 4, newSViv(((gif->Packed & 0x70) >> 4) + 1), 0);
	
	unsigned short color_table_size = 1 << ((gif->Packed & 0x07) + 1);
	hv_store(hash, "color_table_size", 16, newSViv(color_table_size), 0);
	
	hv_store(hash, "background_color", 16, newSViv(gif->BackgroundColor), 0);
	hv_store(hash, "version", 7, newSVpv(gif->Version, 3), 0);
	
	
	if (gif->AspectRatio != 0)
	{
		double aspect_ratio = ((double)(gif->AspectRatio + 15) / 64); 
		hv_store(hash, "aspect_ratio", 12, newSVnv(aspect_ratio), 0);
	}
	
	int pos = sizeof(ImageInfoGIF) + (color_table_size * 3);
	if (size > pos)
	{
		if (memcmp("89a", gif->Version, 3) == 0 && data[pos] == 0xF9)
			hv_store(hash, "animated", 8, newSViv(1), 0);
		else 
			hv_store(hash, "animated", 8, newSViv(0), 0);
	}
	else return 0;
	
	return 1;
}

IV get_psd_info(const unsigned char *data, const size_t size, HV *hash) 
{
	if (size < sizeof(ImageInfoPSD) + 4) return 0;

	ImageInfoPSD *psd = (ImageInfoPSD*) (data + 4);
	
	uint32_t width  = get_uint32(psd->width, 1);
	uint32_t height = get_uint32(psd->height, 1);
	uint16_t depth  = get_uint16(psd->depth, 1);
	uint16_t color_type = get_uint16(psd->mode, 1);
	
	hv_store(hash, "file_media_type", 15, newSVpv(" image/photoshop", 0), 0);
	hv_store(hash, "file_ext", 8, newSVpv("psd", 0), 0);
	
	hv_store(hash, "width", 5, newSViv(width), 0);
	hv_store(hash, "height", 6, newSViv(height), 0);
	hv_store(hash, "bits", 4, newSViv(depth), 0);
	
	if (color_type < 10)
		hv_store(hash, "color_type", 10, newSVpv(psd_color[color_type], 0), 0);

	return 1;
}

IV get_tiff_info(const unsigned char *data, const size_t size, HV *hash) 
{
	if (size > 8)
	{
		unsigned short rev_order;
		if      (memcmp(data, iitiff_sig, 4) == 0) { rev_order = 0; }
		else if (memcmp(data, mmtiff_sig, 4) == 0) { rev_order = 1; }
		else { return 0; }

		// Get offset to first IFD
		const uint32_t IFDOffset = get_uint32(data + 4, rev_order);
		if (IFDOffset > size) { return 0; }

		// Get number of tags in IFD
		const uint16_t tags = get_uint16(data + IFDOffset, rev_order);
		const uint32_t directory_size = 2 + tags * 12 + 4;
		if (IFDOffset + directory_size > size) { return 0; }

		// Iterate through directory
		unsigned long tag_num = 0;
		for(tag_num = 0; tag_num < tags; ++tag_num)
		{
			unsigned long tag_value = 0;
			const unsigned long entry_offset = IFDOffset + 2 + tag_num * 12;

			const unsigned char *entry   = data + entry_offset;
			const uint16_t tag      = get_uint16(entry + 0, rev_order);
			const uint16_t tag_type = get_uint16(entry + 2, rev_order);
			
			switch(tag_type)
			{
				case TIFF_TAG_FMT_BYTE:
				case TIFF_TAG_FMT_SBYTE:
					tag_value = entry[8];
					break;
				case TIFF_TAG_FMT_USHORT:
					tag_value = get_uint16(entry + 8, rev_order);
					break;
				case TIFF_TAG_FMT_SSHORT:
					tag_value = get_uint16(entry + 8, rev_order);
					break;
				case TIFF_TAG_FMT_ULONG:
					tag_value = get_uint32(entry + 8, rev_order);
					break;
				case TIFF_TAG_FMT_SLONG:
					tag_value = get_uint32(entry + 8, rev_order);
					break;
				default:
					continue;
			}

			switch(tag)
			{
				case TIFF_TAG_IMAGEWIDTH:
					hv_store(hash, "width", 5, newSViv(tag_value), 0);
					break;
				case TIFF_TAG_COMP_IMAGEWIDTH:
					hv_store(hash, "exif_width", 10, newSViv(tag_value), 0);
					break;
				case TIFF_TAG_IMAGEHEIGHT:
					hv_store(hash, "height", 6, newSViv(tag_value), 0);
					break;
				case TIFF_TAG_COMP_IMAGEHEIGHT:
					hv_store(hash, "exif_height", 11, newSViv(tag_value), 0);
					break;
				case TIFF_TAG_BITS:
//					hv_store(hash, "bits", 4, newSViv(tag_value), 0);
					break;
				case TIFF_TAG_COMPRESSION:
					switch(tag_value)
					{
						case TIFF_COMPRESSION_NONE:
							hv_store(hash, "compression", 11, newSVpv("None",0), 0); 
							break;
						case TIFF_COMPRESSION_CCITTRLE:
							hv_store(hash, "compression", 11, newSVpv("CCITT modified Huffman RLE",0), 0); 
							break;
						case TIFF_COMPRESSION_CCITTFAX3:
							hv_store(hash, "compression", 11, newSVpv("CCITT Group 3 fax encoding",0), 0); 
							break;
						case TIFF_COMPRESSION_CCITTFAX4:
							hv_store(hash, "compression", 11, newSVpv("CCITT Group 4 fax encoding",0), 0); 
							break;
						case TIFF_COMPRESSION_LZW:
							hv_store(hash, "compression", 11, newSVpv("LZW",0), 0); 
							break;
						case TIFF_COMPRESSION_OJPEG:
							hv_store(hash, "compression", 11, newSVpv("Original JPEG / Old-style JPEG (6.0)",0), 0); 
							break;
						case TIFF_COMPRESSION_JPEG:
							hv_store(hash, "compression", 11, newSVpv("JPEG",0), 0); 
							break;
						case TIFF_COMPRESSION_NEXT:
							hv_store(hash, "compression", 11, newSVpv("NeXT 2-bit RLE",0), 0); 
							break;
						case TIFF_COMPRESSION_CCITTRLEW:
							hv_store(hash, "compression", 11, newSVpv("CCITT RLE",0), 0); 
							break;
						case TIFF_COMPRESSION_PACKBITS:
							hv_store(hash, "compression", 11, newSVpv("Macintosh RLE",0), 0); 
							break;
						case TIFF_COMPRESSION_THUNDERSCAN:
							hv_store(hash, "compression", 11, newSVpv("ThunderScan RLE",0), 0); 
							break;
						case TIFF_COMPRESSION_IT8CTPAD:
							hv_store(hash, "compression", 11, newSVpv("IT8 CT w/padding",0), 0); 
							break;
						case TIFF_COMPRESSION_IT8LW:
							hv_store(hash, "compression", 11, newSVpv("IT8 Linework RLE",0), 0); 
							break;
						case TIFF_COMPRESSION_IT8MP:
							hv_store(hash, "compression", 11, newSVpv("IT8 Monochrome picture",0), 0); 
							break;
						case TIFF_COMPRESSION_IT8BL:
							hv_store(hash, "compression", 11, newSVpv("IT8 Binary line art",0), 0); 
							break;
						case TIFF_COMPRESSION_PIXARFILM:
							hv_store(hash, "compression", 11, newSVpv("Pixar companded 10bit LZW",0), 0); 
							break;
						case TIFF_COMPRESSION_PIXARLOG:
							hv_store(hash, "compression", 11, newSVpv("Pixar companded 11bit ZIP",0), 0); 
							break;
						case TIFF_COMPRESSION_DEFLATE:
							hv_store(hash, "compression", 11, newSVpv("Deflate",0), 0); 
							break;
						case TIFF_COMPRESSION_ADOBE_DEFLATE:
							hv_store(hash, "compression", 11, newSVpv("Adobe deflate",0), 0); 
							break;
						case TIFF_COMPRESSION_DCS:
							hv_store(hash, "compression", 11, newSVpv("Kodak DCS",0), 0); 
							break;
						case TIFF_COMPRESSION_JBIG:
							hv_store(hash, "compression", 11, newSVpv("ISO JBIG",0), 0); 
							break;
						case TIFF_COMPRESSION_SGILOG:
							hv_store(hash, "compression", 11, newSVpv("SGI Log Luminance RLE",0), 0); 
							break;
						case TIFF_COMPRESSION_SGILOG24:
							hv_store(hash, "compression", 11, newSVpv("SGI Log 24-bit",0), 0); 
							break;
						case TIFF_COMPRESSION_JP2000:
							hv_store(hash, "compression", 11, newSVpv("JPEG2000",0), 0); 
							break;
					}
					break;
				case TIFF_TAG_COLORTYPE:
					if (tag_value <= sizeof(tiff_color) / sizeof(tiff_color[0]))
						hv_store(hash, "color_type", 10, newSVpv(tiff_color[tag_value], 0), 0);
					break;
			}
		}
	}
	
	hv_store(hash, "file_media_type", 15, newSVpv("image/tiff", 0), 0);
	hv_store(hash, "file_ext", 8, newSVpv("tif", 0), 0);
	
	return 1;
}


IV get_ico_info(const unsigned char *data, const size_t size, HV *hash) 
{
	if (size > 8)
	{
		unsigned long pos = 4;


		hv_store(hash, "file_media_type", 15, newSVpv("image/x-icon", 0), 0);
		hv_store(hash, "file_ext", 8, newSVpv("ico", 0), 0);

		uint16_t icon_count = (data[pos + 1] << 8) | data[pos];

		hv_store(hash, "icon_count", 10, newSViv(icon_count), 0);

		unsigned long offset = (icon_count - 1) * sizeof(ImageInfoICO);
		
		pos += offset + 2;
		
		if (size < (4 + offset + sizeof(ImageInfoICO))) return 0;
		
		ImageInfoICO *ico = (ImageInfoICO*) (data + pos);
		
		hv_store(hash, "width", 5, newSViv(ico->width), 0);
		hv_store(hash, "height", 6, newSViv(ico->height), 0);
		hv_store(hash, "bits", 4, newSViv(ico->bitCount), 0);
		
		if (ico->nColors == 0)
		{
			hv_store(hash, "color_type", 10, newSVpv("RGB", 0), 0);
			hv_store(hash, "colors", 6, newSViv(256), 0);
		}
		else
		{
			hv_store(hash, "color_type", 10, newSVpv("Indexed", 0), 0);
			hv_store(hash, "colors", 6, newSViv(ico->nColors), 0);
		}
	}
	
	return 1;
}



MODULE = Image::Info::XS		PACKAGE = Image::Info::XS		

SV* image_info(source)
	SV *source;
	PREINIT:
	size_t image_data_size;
	unsigned char *image_data;
	unsigned short from_file = 0;
	
	CODE:
		if (SvTYPE(source) == SVt_PV)
		{
			FILE *f = fopen(SvPV_nolen(source), "r");
			if (!f) 
			{
				warn("File open error");
				XSRETURN_UNDEF;
			}
			
			image_data = malloc(BUF_SIZE);
			image_data_size = fread(image_data, 1, BUF_SIZE, f);
			
			fclose(f);
			
			from_file = 1;
		}
		else if (SvROK(source) && SvTYPE(SvRV(source)) == SVt_PV)
		{
			image_data_size = SvCUR(SvRV(source));
			image_data = SvPV_nolen(SvRV(source));
			
		}
		else XSRETURN_UNDEF;
		
		ImageType image_type = get_image_type(image_data, image_data_size);
		
		HV *hash = newHV();
		
		unsigned short result = 0;
		
		switch(image_type)
		{
			case IMAGE_BMP: result = get_bmp_info(image_data, image_data_size, hash); break;

			case IMAGE_GIF: result = get_gif_info(image_data, image_data_size, hash);break;

			case IMAGE_PNG: result = get_png_info(image_data, image_data_size, hash); break;

			case IMAGE_PSD: result = get_psd_info(image_data, image_data_size, hash); break;

			case IMAGE_JPEG: result = get_jpeg_info(image_data, image_data_size, hash); break;

			case IMAGE_TIFF: result = get_tiff_info(image_data, image_data_size, hash); break;

			case IMAGE_ICO: result = get_ico_info(image_data, image_data_size, hash); break;

		}
		
		if (from_file) free(image_data);
		
		if (result == 1) 
			RETVAL = newRV_noinc((SV*) hash);
		else 
		{
			SvREFCNT_dec((SV*) hash);
			XSRETURN_UNDEF;
		}
	OUTPUT:
		RETVAL


SV* image_type(source)
	SV *source;
	PREINIT:
	size_t image_data_size;
	unsigned char *image_data;
	unsigned short from_file = 0;
	
	CODE:
		if (SvTYPE(source) == SVt_PV)
		{

			FILE *f = fopen(SvPV_nolen(source), "r");
			if (!f) 
			{
				warn("File open error");
				XSRETURN_UNDEF;
			}
			
			image_data = malloc(BUF_SIZE);
			image_data_size = fread(image_data, 1, BUF_SIZE, f);
			
			fclose(f);
			
			from_file = 1;
		}
		if (SvROK(source) && SvTYPE(SvRV(source)) == SVt_PV)
		{
			image_data_size = SvCUR(SvRV(source));
			image_data = SvPV_nolen(SvRV(source));
			
		}
		
		ImageType image_type = get_image_type(image_data, image_data_size);
		
		if (from_file) free(image_data);
		
		RETVAL = NULL;
		
		switch(image_type)
		{
			case IMAGE_BMP: RETVAL = newSVpv("BMP", 0); break;

			case IMAGE_GIF: RETVAL = newSVpv("GIF", 0); break;

			case IMAGE_PNG: RETVAL = newSVpv("PNG", 0); break;

			case IMAGE_PSD: RETVAL = newSVpv("PSD", 0); break;

			case IMAGE_JPEG: RETVAL = newSVpv("JPEG", 0); break;

			case IMAGE_TIFF: RETVAL = newSVpv("TIFF", 0); break;

			case IMAGE_ICO: RETVAL = newSVpv("ICO", 0); break;
		}
		
	OUTPUT:
		RETVAL