
#include "imgloader.h"
#include "dr_rdpng.h"
#include <iostream>
#include <fstream>
#include "../simio.h"
#include "../simdebug.h"
#include <string.h>

img_loader::img_loader(const char *filename):m_bitmap_data(0),m_bitmap_size(0),m_greyscale(0) {
    dispose();
    if (filename!=NULL) {
        load_image(filename);
    }
}

img_loader::~img_loader() {
    dispose();
}

void img_loader::dispose(void) {
    if (m_bitmap_data) {
        delete[] m_bitmap_data;
        m_bitmap_data=NULL;
    }
    memset(&m_bitmap_file_header,0,sizeof(m_bitmap_file_header));
    memset(&m_bitmap_header,0,sizeof(m_bitmap_header));
}

ImgResult img_loader::load_image(const char * filename) {
    if (filename==NULL) {
        return IMGR_NULL;
    }
    std::ifstream file(filename, std::ios::binary | std::ios::in);

    if (file.bad()) {
        return IMGR_FILE_DOES_NOT_EXIST;
    }

    if (!file.is_open()) {
        return IMGR_UNABLE_TO_OPEN;
    }

    dispose();
    char signature[3]={0,0,0};
    file.read(signature,2);
    if (strcmp(signature,"BM")==0) {
        file.close();
        return this->load_bmp(filename);
    } else if ((signature[0]=='P')&&(signature[1]>='1')&&(signature[1]<='6')) {
        file.close();
        return this->load_pxm(filename);
    } else if (strcmp(signature,"‰P")==0) {
        file.read(signature,2);
        if (strcmp(signature,"NG")==0) {
            file.close();
            return this->load_png(filename);
        } else {
            file.close();
            return IMGR_UNSUPPORTED_FORMAT;
        }
    }
    file.close();
    return IMGR_UNSUPPORTED_FORMAT;
}


void * img_loader::get_bits()
{
	return (void*)m_bitmap_data;
}


ImgResult img_loader::get_bits(void * buffer, uint32 & buffer_size, uint32 red_mask, uint32 green_mask, uint32 blue_mask, uint32 alpha_mask, bool include_padding)
{
	ImgResult result = IMGR_OK;
	uint32 BitCountRed = color::bit_count_by_mask(red_mask);
	uint32 BitCountGreen = color::bit_count_by_mask(green_mask);
	uint32 BitCountBlue = color::bit_count_by_mask(blue_mask);
	uint32 BitCountAlpha = color::bit_count_by_mask(alpha_mask);

	unsigned int BitCount = (BitCountRed + BitCountGreen + BitCountBlue + BitCountAlpha + 7) & ~7;

	if (BitCount > 32) {
		return IMGR_INVALID_FORMAT;
	}

	unsigned int w = get_width();
	unsigned int dataBytesPerLine = (w * BitCount + 7) / 8;
	unsigned int LineWidth = (dataBytesPerLine + 3) & ~3;

	if (buffer_size == 0 || buffer == 0) {
		buffer_size = (get_width() * get_height() * BitCount) / 8 + sizeof(unsigned int);
		return IMGR_OK;
	}

	uint8* BufferPtr = (uint8*)buffer;

	uint32 BitPosRed = color::bit_position_by_mask(red_mask);
	uint32 BitPosGreen = color::bit_position_by_mask(green_mask);
	uint32 BitPosBlue = color::bit_position_by_mask(blue_mask);
	uint32 BitPosAlpha = color::bit_position_by_mask(alpha_mask);

	unsigned int j = 0;

	for (uint32 i = 0; i < (uint32)m_bitmap_size; i++) {
		*(uint32*)BufferPtr =
			(color::convert(m_bitmap_data[i].Blue, 8, BitCountBlue) << BitPosBlue) |
			(color::convert(m_bitmap_data[i].Green, 8, BitCountGreen) << BitPosGreen) |
			(color::convert(m_bitmap_data[i].Red, 8, BitCountRed) << BitPosRed) |
			(color::convert(m_bitmap_data[i].Alpha, 8, BitCountAlpha) << BitPosAlpha);

		if (include_padding) {
			j++;
			if (j >= w) {
				for (uint32 k = 0; k < LineWidth - dataBytesPerLine; k++) {
					BufferPtr += (BitCount >> 3);
				}
				j = 0;
			}

		}

		BufferPtr += (BitCount >> 3);
	}

	buffer_size -= sizeof(uint32);

	return result;
}


ImgResult img_loader::load_bmp(const char *filename)
{
    std::ifstream file(filename, std::ios::binary | std::ios::in);

    if (file.bad()) {
        return IMGR_FILE_DOES_NOT_EXIST;
    }

    if (file.is_open() == false) {
        return IMGR_UNABLE_TO_OPEN;
    }
    dispose();

    file.read((char*) &m_bitmap_file_header, sizeof(BITMAP_FILEHEADER_SIZE));
    if (m_bitmap_file_header.Signature!=BITMAP_SIGNATURE) {
        return IMGR_INVALID_FORMAT;
    }

    file.read((char*) &m_bitmap_header,sizeof(BITMAP_HEADER));

    // Load Color Table
    file.seekg(BITMAP_FILEHEADER_SIZE+m_bitmap_header.HeaderSize,std::ios::beg);

    uint32 color_table_size=0;
    if (m_bitmap_header.BitCount==1) {
        color_table_size=2;
    } else if (m_bitmap_header.BitCount==4) {
        color_table_size=16;
    } else if (m_bitmap_header.BitCount==8) {
        color_table_size=256;
    }

    // Always allocate full sized color table (and make sure it is not zero or MSVC will crash!)
    BGRA* color_table = new BGRA[color_table_size+1];

    file.read((char *)color_table,sizeof(BGRA)*m_bitmap_header.ClrUsed);
    m_bitmap_size=get_width()*get_height();
    m_bitmap_data=new RGBA[m_bitmap_size];

    uint32 line_width=((get_width()*get_bit_count()/8)+3)&~3;
    uint8* line=new uint8[line_width];

    file.seekg(m_bitmap_file_header.BitsOffset,std::ios::beg);

    sint32 index=0;

    ImgResult result=IMGR_OK;

    if (m_bitmap_header.Compression==0) {
        for (uint32 y=0;y<get_height();y++) {
            file.read((char *)line,line_width);

            uint8 * line_ptr=line;

            for (uint32 x=0;x<get_width();x++) {
                if (m_bitmap_header.BitCount==1) {
                    uint32 Color=*((uint8*)line_ptr);
                    for (int k=0;k<8;k++) {
                        m_bitmap_data[index].Red=color_table[Color&0x80?1:0].Red;
                        m_bitmap_data[index].Green=color_table[Color&0x80?1:0].Green;
                        m_bitmap_data[index].Blue=color_table[Color&0x80?1:0].Blue;
                        m_bitmap_data[index].Alpha=color_table[Color&0x80?1:0].Alpha;
                        index++;
                        Color<<=1;
                    }
                    line_ptr++;
                    x+=7;
                } else if (m_bitmap_header.BitCount==4) {
                    uint32 Color=*((uint8*)line_ptr);
                    m_bitmap_data[index].Red=color_table[(Color>>4)&0x0f].Red;
                    m_bitmap_data[index].Green=color_table[(Color>>4)&0x0f].Green;
                    m_bitmap_data[index].Blue=color_table[(Color>>4)&0x0f].Blue;
                    m_bitmap_data[index].Alpha=color_table[(Color>>4)&0x0f].Alpha;
                    index++;
                    m_bitmap_data[index].Red=color_table[Color & 0x0f].Red;
                    m_bitmap_data[index].Green=color_table[Color & 0x0f].Green;
                    m_bitmap_data[index].Blue=color_table[Color & 0x0f].Blue;
                    m_bitmap_data[index].Alpha=color_table[Color & 0x0f].Alpha;
                    index++;
                    line_ptr++;
                    x++;
                } else if (m_bitmap_header.BitCount==8) {
                    uint32 Color=*((uint8*)line_ptr);
                    m_bitmap_data[index].Red=color_table[Color].Red;
                    m_bitmap_data[index].Green=color_table[Color].Green;
                    m_bitmap_data[index].Blue=color_table[Color].Blue;
                    m_bitmap_data[index].Alpha=color_table[Color].Alpha;
                    index++;
                    line_ptr++;
                } else if (m_bitmap_header.BitCount==16) {
                    uint32 Color = *((uint16*) line_ptr);
                    m_bitmap_data[index].Red=((Color >> 10) & 0x1f) << 3;
                    m_bitmap_data[index].Green=((Color >> 5) & 0x1f) << 3;
                    m_bitmap_data[index].Blue=(Color & 0x1f) << 3;
                    m_bitmap_data[index].Alpha=255;
                    index++;
                    line_ptr+=2;
                } else if (m_bitmap_header.BitCount==24) {
                    uint32 Color=*((uint32*)line_ptr);
                    m_bitmap_data[index].Red=(Color>>16)&0xff;
                    m_bitmap_data[index].Green=(Color>>8)&0xff;
                    m_bitmap_data[index].Blue=Color&0xff;
                    m_bitmap_data[index].Alpha=255;
                    index++;
                    line_ptr+=3;
                } else if (m_bitmap_header.BitCount==32) {
                    uint32 Color=*((uint32*)line_ptr);
                    m_bitmap_data[index].Alpha=(Color>>24)&0xff;
                    m_bitmap_data[index].Red=(Color>>16)&0xff;
                    m_bitmap_data[index].Green=(Color>>8)&0xff;
                    m_bitmap_data[index].Blue=Color&0xff;
                    index++;
                    line_ptr+=4;
                }
            }
        }
    } else if (m_bitmap_header.Compression==1) {
        // RLE Compression
        uint8 Count=0;
        uint8 color_index=0;
        int x=0,y=0;
        while (file.eof()==false) {
            file.read((char *)&Count,sizeof(uint8));
            file.read((char *)&color_index,sizeof(uint8));

            if (Count>0) {
                index=x+y*get_width();
                for (int k=0;k<Count;k++) {
                    m_bitmap_data[index+k].Red=color_table[color_index].Red;
                    m_bitmap_data[index+k].Green=color_table[color_index].Green;
                    m_bitmap_data[index+k].Blue=color_table[color_index].Blue;
                    m_bitmap_data[index+k].Alpha=color_table[color_index].Alpha;
                }
                x+=Count;
            } else if (Count==0) {
                int flag=color_index;
                if (flag==0) {
                    x=0;
                    y++;
                } else if (flag==1) {
                    break;
                } else if (flag==2) {
                    char rx=file.get();
                    char ry=file.get();
                    x+=rx;
                    y+=ry;
                } else {
                    Count=flag;
                    index=x+y*get_width();
                    for (int k=0;k<Count;k++) {
                        color_index=file.get();
                        m_bitmap_data[index+k].Red=color_table[color_index].Red;
                        m_bitmap_data[index+k].Green=color_table[color_index].Green;
                        m_bitmap_data[index+k].Blue=color_table[color_index].Blue;
                        m_bitmap_data[index+k].Alpha=color_table[color_index].Alpha;
                    }
                    x+=Count;
                    // Attention: Current Microsoft STL implementation seems to be buggy, tellg() always returns 0.
                    if (file.tellg()&1) {
                        file.seekg(1,std::ios::cur);
                    }
                }
            }
        }
    } else if (m_bitmap_header.Compression==2) {
        // RLE 4 - unsupported
        result=IMGR_UNSUPPORTED_FORMAT;
    } else if (m_bitmap_header.Compression==3) {
        // BITFIELDS
        // We assumes that mask of each color component can be in any order
        uint32 bit_count_red = color::bit_count_by_mask(m_bitmap_header.RedMask);
        uint32 bit_count_green = color::bit_count_by_mask(m_bitmap_header.GreenMask);
        uint32 bit_count_blue = color::bit_count_by_mask(m_bitmap_header.BlueMask);
        uint32 bit_count_alpha = color::bit_count_by_mask(m_bitmap_header.AlphaMask);

        for (uint32 y=0;y<get_height();y++) {
            file.read((char *)line,line_width);
            uint8* line_ptr=line;

            for (unsigned int x=0;x<get_width();x++) {
                uint32 Color=0;
                if (m_bitmap_header.BitCount==16) {
                    Color=*((uint16*)line_ptr);
                    line_ptr+=2;
                } else if (m_bitmap_header.BitCount==32) {
                    Color=*((uint32*)line_ptr);
                    line_ptr+=4;
                } else {
                    // Other bitcounts are unsupported
                    result=IMGR_UNSUPPORTED_FORMAT;
                }
                m_bitmap_data[index].Red=color::convert(color::component_by_mask(Color,m_bitmap_header.RedMask),bit_count_red,8);
                m_bitmap_data[index].Green=color::convert(color::component_by_mask(Color,m_bitmap_header.GreenMask),bit_count_green,8);
                m_bitmap_data[index].Blue=color::convert(color::component_by_mask(Color,m_bitmap_header.BlueMask),bit_count_blue,8);
                m_bitmap_data[index].Alpha=color::convert(color::component_by_mask(Color,m_bitmap_header.AlphaMask),bit_count_alpha,8);
                index++;
            }
        }
    }
    delete color_table;
    delete line;
    file.close();
    if (m_bitmap_header.Height>0){
        // Bitmaps are inverted by default, uninvert the bitmap
        for (int yi=0;yi<m_bitmap_header.Height/2;yi++) {
            for (int xi=0;xi<m_bitmap_header.Width;xi++) {
                int ui=yi*m_bitmap_header.Width+xi;
                int vi=(m_bitmap_header.Height-yi-1)*m_bitmap_header.Width+xi;
                RGBA val=m_bitmap_data[ui];
                m_bitmap_data[ui]=m_bitmap_data[vi];
                m_bitmap_data[vi]=val;
            }
        }
    }
    return result;
}


bool img_loader::get_bits_with_palette(void* Buffer, uint32 &Size, uint32 BitCount, BGRA* &Palette, uint32 &PaletteSize, bool OptimalPalette, bool IncludePadding) {
	bool Result = false;

	if (BitCount > 16) {
		return false;
	}

	unsigned int w = get_width();
	unsigned int dataBytesPerLine = (w * BitCount + 7) / 8;
	unsigned int LineWidth = (dataBytesPerLine + 3) & ~3;

	if (Size == 0 || Buffer == 0) {
		Size = (LineWidth * get_height() * BitCount) / 8;
		return true;
	}


	if (OptimalPalette) {
		PaletteSize = 0;
		// Not implemented
	} else {
		if (BitCount == 1) {
			PaletteSize = 2;
			// Not implemented: Who need that?
		} else if (BitCount == 4) { // 2:2:1
			PaletteSize = 16;
			Palette = new BGRA[PaletteSize];
			for (int r = 0; r < 4; r++) {
				for (int g = 0; g < 2; g++) {
					for (int b = 0; b < 2; b++) {
						Palette[r | g << 2 | b << 3].Red = r ? (r << 6) | 0x3f : 0;
						Palette[r | g << 2 | b << 3].Green = g ? (g << 7) | 0x7f : 0;
						Palette[r | g << 2 | b << 3].Blue = b ? (b << 7) | 0x7f : 0;
						Palette[r | g << 2 | b << 3].Alpha = 0xff;
					}
				}
			}
		} else if (BitCount == 8) { // 3:3:2
			PaletteSize = 256;
			Palette = new BGRA[PaletteSize];
			for (int r = 0; r < 8; r++) {
				for (int g = 0; g < 8; g++) {
					for (int b = 0; b < 4; b++) {
						Palette[r | g << 3 | b << 6].Red = r ? (r << 5) | 0x1f : 0;
						Palette[r | g << 3 | b << 6].Green = g ? (g << 5) | 0x1f : 0;
						Palette[r | g << 3 | b << 6].Blue = b ? (b << 6) | 0x3f : 0;
						Palette[r | g << 3 | b << 6].Alpha = 0xff;
					}
				}
			}
		} else if (BitCount == 16) { // 5:5:5
			// Not implemented
		}
	}

	unsigned int j = 0;
	uint8* BufferPtr = (uint8*) Buffer;

	for (uint32 i = 0; i < (uint32)m_bitmap_size; i++) {
		if (BitCount == 1) {
			// Not implemented: Who needs that?
		} else if (BitCount == 4) {
			*BufferPtr = ((m_bitmap_data[i].Red >> 6) | (m_bitmap_data[i].Green >> 7) << 2 | (m_bitmap_data[i].Blue >> 7) << 3) << 4;
			i++;
			*BufferPtr |= (m_bitmap_data[i].Red >> 6) | (m_bitmap_data[i].Green >> 7) << 2 | (m_bitmap_data[i].Blue >> 7) << 3;
		} else if (BitCount == 8) {
			*BufferPtr = (m_bitmap_data[i].Red >> 5) | (m_bitmap_data[i].Green >> 5) << 3 | (m_bitmap_data[i].Blue >> 5) << 6;
		} else if (BitCount == 16) {
			// Not implemented
		}

		if (IncludePadding) {
			j++;
			if (j >= w) {
				for (unsigned int k = 0; k < (LineWidth - dataBytesPerLine); k++) {
					BufferPtr += BitCount / 8;
				}
				j = 0;
			}
		}

		BufferPtr++;
	}

	Result = true;

	return Result;
}


bool img_loader::save_BMP(char * filename,uint32 BitCount) {
    bool result=true;
    std::ofstream file(filename, std::ios::out | std::ios::binary);

    if (file.is_open()==false) {
        return false;
    }
    BITMAP_FILEHEADER bfh;
    BITMAP_HEADER bh;
    memset(&bfh,0,sizeof(bfh));
    memset(&bh,0,sizeof(bh));

    bfh.Signature=BITMAP_SIGNATURE;
    bfh.BitsOffset=BITMAP_FILEHEADER_SIZE+sizeof(BITMAP_HEADER);
    bfh.Size=(get_width()*get_height()*BitCount)/8+bfh.BitsOffset;

    bh.HeaderSize=sizeof(BITMAP_HEADER);
    bh.BitCount=BitCount;

    if (BitCount==32) {
        bh.Compression=3;
        bh.AlphaMask    = 0xff000000;
        bh.RedMask      = 0x00ff0000;
        bh.GreenMask    = 0x0000ff00;
        bh.BlueMask     = 0x000000ff;
    } else if (BitCount==16) {
        bh.Compression=3;
        bh.AlphaMask    = 0x00000000;
        bh.RedMask      = 0x0000001f;
        bh.GreenMask    = 0x000007E0;
        bh.BlueMask     = 0x0000F800;
    } else {
        bh.Compression=0;
    }

    unsigned int line_width=(get_width()+3)&~3;

    bh.Planes=1;
    bh.Height=get_height();
    bh.Width=get_width();
    bh.SizeImage=(line_width*BitCount*get_height())/8;
    bh.PelsPerMeterX=3780;
    bh.PelsPerMeterY=3780;

    if (BitCount==32) {
        file.write((char*) &bfh, sizeof(BITMAP_FILEHEADER));
        file.write((char*) &bh, sizeof(BITMAP_HEADER));
        file.write((char*) m_bitmap_data, bh.SizeImage);
    } else if (BitCount==16) {
        uint8* Bitmap = new uint8[bh.SizeImage];
        BGRA *Palette = 0;
        uint32 PaletteSize = 0;

        if (get_bits_with_palette(Bitmap, bh.SizeImage, BitCount, Palette, PaletteSize)) {
            bfh.BitsOffset += PaletteSize * sizeof(BGRA);
            file.write((char*) &bfh, BITMAP_FILEHEADER_SIZE);
            file.write((char*) &bh, sizeof(BITMAP_HEADER));
            file.write((char*) Palette, PaletteSize * sizeof(BGRA));
            file.write((char*) Bitmap, bh.SizeImage);
        }
        delete [] Bitmap;
        delete [] Palette;
    } else {
        uint32 RedMask = 0;
        uint32 GreenMask = 0;
        uint32 BlueMask = 0;
        uint32 AlphaMask = 0;

        if (BitCount == 16) {
            RedMask     = 0x0000F800;
            GreenMask   = 0x000007E0;
            BlueMask    = 0x0000001F;
            AlphaMask   = 0x00000000;
        } else if (BitCount==24) {
            RedMask = 0x00FF0000;
            GreenMask = 0x0000FF00;
            BlueMask = 0x000000FF;
        } else {
            result=false;
        }
        if (result) {
            if (get_bits(NULL, bh.SizeImage, RedMask, GreenMask, BlueMask, AlphaMask)) {
                uint8* Bitmap = new uint8[bh.SizeImage];
                if (get_bits(Bitmap, bh.SizeImage, RedMask, GreenMask, BlueMask, AlphaMask)) {
					file.write((char*) &bfh, sizeof(BITMAP_FILEHEADER));
					file.write((char*) &bh, sizeof(BITMAP_HEADER));
					file.write((char*) Bitmap, bh.SizeImage);
				}
				delete [] Bitmap;
            }
        }
    }
    file.close();
    return result;
}


bool img_loader::save_PNG(char * filename)
{
	return (bool)write_png(filename, (uint8*)m_bitmap_data, get_width(), get_height(), get_bit_count());

}


bool img_loader::save_PXM(char * filename, int level)
{
	FILE *f=fopen(filename,"wb");
	if (f==NULL) {
        return false;
	}
	fprintf(f,"P%d\n#Simutrans PxM Writer\n%d\n%d\n",level,get_width(),get_height());
	if (level!=1&&level!=4) {
        fprintf(f,"255\n");
	}
	if (level<=6) {
        for (uint32 y=0;y<get_height();y++) {
            for (uint32 x=0;x<get_width();x++) {
                uint32 location=y*get_width()+x;
                if (level==1) {
                    int v=m_bitmap_data[location].Red+m_bitmap_data[location].Green+m_bitmap_data[location].Blue;
                    if (v>384) {
                        fprintf(f,"1 ");
                    } else {
                        fprintf(f,"0 ");
                    }
                } else if (level==2) {
                    int v=(m_bitmap_data[location].Red+m_bitmap_data[location].Green+m_bitmap_data[location].Blue)/3;
                    if (v>255) {
                        v=255;
                    }
                    fprintf(f,"%d ",v);
                } else if (level==3) {
                    fprintf(f,"%d %d %d\t",m_bitmap_data[location].Red,m_bitmap_data[location].Green,m_bitmap_data[location].Blue);
                } else if (level==4) {
                    uint8 val=0;
                    for (int x2=0;x2<8;x2++) {
                        val=val<<1;
                        int v=m_bitmap_data[location+x2].Red+m_bitmap_data[location+x2].Green+m_bitmap_data[location+x2].Blue;
                        if (v>384) {
                            val+=1;
                        }

                    }
                    fputc(val,f);
                    x+=7;
                } else if (level==5) {
                    int v=(m_bitmap_data[location].Red+m_bitmap_data[location].Green+m_bitmap_data[location].Blue)/3;
                    if (v>255) {
                        v=255;
                    }
                    fputc(v,f);
                } else if (level==6) {
                    fputc(m_bitmap_data[location].Red,f);
                    fputc(m_bitmap_data[location].Green,f);
                    fputc(m_bitmap_data[location].Blue,f);
                }
            }
            if (level<4) {
                fprintf(f,"\n");
            }
        }
	}
	return false;
}


bool img_loader::is_greyscale(void)
{
	return m_greyscale;
}


ImgResult img_loader::load_png(const char *filename) {
    unsigned char **block=new unsigned char * [1];
    block[0]=(unsigned char *)malloc(1);
    uint32 width=0;
    uint32 height=0;
    uint32 base_img_size=1;
    if (!load_block(block,&width,&height,filename,base_img_size)) {
        return IMGR_UNKNOWN_ERROR;
    }
    this->set_bits(block[0],width,height,0x0000FF00,0x00FF0000,0xFF000000,0x000000FF);
    return IMGR_OK;
}


uint32 img_loader::read_next_number_pxm(std::ifstream& file) {
    char c=file.get();
    while ((c<'0')||(c>'9')) {
        // If there is a # then the line is a comment - read to end of line
        if (c=='#') {
            while (c!='\n') {
                c=file.get();
                if (file.fail()) {
                    return -1;
                }
            }
            if (file.fail()) {
                return -1;
            }
        }
        // get next character
        c=file.get();
    }
    uint32 ret_val=0;
    while ((c>='0')&&(c<='9')) {
        ret_val*=10;
        ret_val+=(c-'0');
        if (file.fail()) {
            return ret_val;
        }
        c=file.get();
    }
    file.unget();
    return ret_val;
}


inline sint32 img_loader::standardize_number_pxm(sint32 val, sint32 maxval) {
    if (maxval==255) {
        return val;
    } else if (maxval<255) {
        return val*255/maxval;
    } else {
        int dif=maxval/256;
        int ret=val/dif;
        if (ret>255) {
            return 255;
        }
        return ret;
    }
}

ImgResult img_loader::load_pxm(const char* filename) {
    std::ifstream file(filename, std::ios::binary | std::ios::in);
    char signature[3]={0,0,0};
    file.read(signature,2);
    // convert second character of signature to level
    uint8 level=signature[1]-'0';
    if ((level<1)||(level>6)) {
        // should never get here, as the value is checked before entering the function.
        return IMGR_INVALID_FORMAT;
    }

    //char buffer[256];
    bool binary_data=(level>=4);

    // Read in the width and height of the image
    int width=read_next_number_pxm(file);
    int height=read_next_number_pxm(file);

    // If width or height are negative an error has occured.
    if ((width<0)||(height<0)) {
        return IMGR_INVALID_FORMAT;
    }

    int maxval=1;
    // If image is not Black and White read in the max value (can be up to 65535)
    if ((level!=1)&&(level!=4)) {
        maxval=read_next_number_pxm(file);
    }
    // Read in and check that we have a whitespace character
    char c=file.get();
    if ((c!=' ')&&(c!='\t')&&(c!='\n')&&(c!='\r')) {
        return IMGR_INVALID_FORMAT;
    }
    m_bitmap_header.Width=width;
    m_bitmap_header.Height=height;
    m_bitmap_header.BitCount=32;
    m_bitmap_header.Compression=3;
    m_bitmap_size=width*height;

    // Alocate space for bitmap data
    m_bitmap_data=new RGBA[m_bitmap_size];

    if (binary_data) {
        // Read in Binary Data
        if (level==4) {
            // Black and white image encoded to individual bits, padded to a full byte for each line
            int line_len=(width+7)/8;
            char *buffer=new char [line_len];
            for (int y=0;y<height;y++) {
                file.read(buffer,line_len);
                int x1=0;
                for (int x=0;x<line_len;x++) {
                    int location=y*width+x1;
                    for (int i=7;i>=0;i--) {
                        if (x1<width) {
                            // Black and white so options are 255 or 0
                            if ((buffer[x]&(1<<i))==1) {
                                m_bitmap_data[location].Red=255;
                                m_bitmap_data[location].Green=255;
                                m_bitmap_data[location].Blue=255;
                            } else {
                                m_bitmap_data[location].Red=0;
                                m_bitmap_data[location].Green=0;
                                m_bitmap_data[location].Blue=0;
                            }
                            m_bitmap_data[location].Alpha=0;
                            x1++;
                        }
                    }
                }
            }
        } else {
            // Read in a color or greyscale image
            for (int y=0;y<height;y++) {
                for (int x=0;x<width;x++) {
                    int r=file.get();
                    int g=0;
                    int b=0;
                    if (maxval>255) {
                        r*=256;
                        r+=file.get();
                    }
                    r=standardize_number_pxm(r,maxval);
                    if (level==5) {
                        // If greyscale green and blue are the same value as red
                        g=r;
                        b=r;
                    } else {
                        // If color, read in green and blue values
                        g=file.get();
                        if (maxval>255) {
                            g*=256;
                            g+=file.get();
                        }
                        b=file.get();
                        if (maxval>255) {
                            b*=256;
                            b+=file.get();
                        }
                        g=standardize_number_pxm(g,maxval);
                        b=standardize_number_pxm(b,maxval);
                    }
                    int location=y*width+x;
                    m_bitmap_data[location].Red=r;
                    m_bitmap_data[location].Green=g;
                    m_bitmap_data[location].Blue=b;
                    m_bitmap_data[location].Alpha=0;
                }
            }
        }
    } else {
        // Read in ASCII Data
        for (int y=0;y<height;y++) {
            for (int x=0;x<width;x++) {
                int location=y*width+x;
                if (level==3) {
                    // If color, read in Red, Green and Blue
                    int R=standardize_number_pxm(read_next_number_pxm(file),maxval);
                    int G=standardize_number_pxm(read_next_number_pxm(file),maxval);
                    int B=standardize_number_pxm(read_next_number_pxm(file),maxval);
                    m_bitmap_data[location].Red=R;
                    m_bitmap_data[location].Green=G;
                    m_bitmap_data[location].Blue=B;
                    m_bitmap_data[location].Alpha=0;
                } else {
                    // Otherwise Red Green and Blue are the same value, so only read in 1 value
                    int R=standardize_number_pxm(read_next_number_pxm(file),maxval);
                    m_bitmap_data[location].Red=R;
                    m_bitmap_data[location].Green=R;
                    m_bitmap_data[location].Blue=R;
                    m_bitmap_data[location].Alpha=0;
                }
            }
        }
    }
    return IMGR_OK;
}


bool img_loader::set_bits(void *buffer, uint32 width, uint32 height, uint32 red_mask, uint32 green_mask, uint32 blue_mask,uint32 alpha_mask) {
    if (buffer==NULL) {
        return false;
    }

    uint8 * buffer_ptr=(uint8*)buffer;

    dispose();

    m_bitmap_header.Width=width;
    m_bitmap_header.Height=height;
    m_bitmap_header.BitCount=32;
    m_bitmap_header.Compression=3;

    m_bitmap_size=get_width()*get_height();
    m_bitmap_data=new RGBA[m_bitmap_size];

    unsigned int bit_count = (color::bit_count_by_mask(red_mask | green_mask | blue_mask | alpha_mask) + 7) & ~7;

    uint32 bit_count_red = color::bit_count_by_mask(red_mask);
    uint32 bit_count_green = color::bit_count_by_mask(green_mask);
    uint32 bit_count_blue = color::bit_count_by_mask(blue_mask);
    uint32 bit_count_alpha = color::bit_count_by_mask(alpha_mask);

    for (uint32 i=0;i<(uint32)m_bitmap_size;i++) {
        uint32 color=0;
        if (bit_count<=8) {
            color=*((uint8*)buffer_ptr);
            buffer_ptr++;
        } else if (bit_count<=16) {
            color=*((uint16*)buffer_ptr);
            buffer_ptr+=2;
        } else if (bit_count<=24) {
            color=*((uint32*)buffer_ptr);
            buffer_ptr+=3;
        } else if (bit_count<=32) {
            color=*((uint32*)buffer_ptr);
            buffer_ptr+=4;
        } else {
            // Unsupported
            buffer_ptr++;
        }
        m_bitmap_data[i].Alpha=color::convert(color::component_by_mask(color,alpha_mask),bit_count_alpha,8);
        m_bitmap_data[i].Red=color::convert(color::component_by_mask(color,red_mask),bit_count_red,8);
        m_bitmap_data[i].Green=color::convert(color::component_by_mask(color,green_mask),bit_count_green,8);
        m_bitmap_data[i].Blue=color::convert(color::component_by_mask(color,blue_mask),bit_count_blue,8);
    }
    return true;
}


uint32 img_loader::get_width(void) {
    if (abs(m_bitmap_header.Width) > 8192) {
        m_bitmap_header.Width=8192;
    }
    return m_bitmap_header.Width<0?-m_bitmap_header.Width:m_bitmap_header.Width;
}


uint32 img_loader::get_height(void) {
    if (abs(m_bitmap_header.Height) > 8192) {
        m_bitmap_header.Height=8192;
    }
    return m_bitmap_header.Height<0?-m_bitmap_header.Height:m_bitmap_header.Height;
}


inline uint32 img_loader::get_bit_count() {
    return m_bitmap_header.BitCount;
}

inline uint32 img_loader::color::bit_count_by_mask(uint32 mask) {
    uint32 bit_count=0;
    while (mask) {
        mask&=mask-1;
        bit_count++;
    }
    return bit_count;
}

inline uint32 img_loader::color::bit_position_by_mask(uint32 mask) {
    return bit_count_by_mask((mask & (~mask + 1)) - 1);
}

inline uint32 img_loader::color::component_by_mask(uint32 color, uint32 mask) {
    uint32 component=color&mask;
    return component>>bit_position_by_mask(mask);
}

inline uint32 img_loader::color::bit_count_to_mask(uint32 bit_count) {
    return (bit_count == 32)?0xFFFFFFFF:(1<<bit_count)-1;
}

uint32 img_loader::color::convert(uint32 color,uint32 from_bit_count,uint32 to_bit_count) {
    if (to_bit_count<from_bit_count) {
        color >>= (from_bit_count-to_bit_count);
    } else {
        color <<= (to_bit_count-from_bit_count);
        if (color>0) {
            color|=bit_count_to_mask(to_bit_count-from_bit_count);
        }
    }
    return color;
}
