using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.util;
using iTextSharp.text;
using iTextSharp.text.error_messages;
/*
 * This file is part of the iText project.
 * Copyright (c) 1998-2012 1T3XT BVBA
 * Authors: Bruno Lowagie, Paulo Soares, et al.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License version 3
 * as published by the Free Software Foundation with the addition of the
 * following permission added to Section 15 as permitted in Section 7(a):
 * FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY 1T3XT,
 * 1T3XT DISCLAIMS THE WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Affero General Public License for more details.
 * You should have received a copy of the GNU Affero General Public License
 * along with this program; if not, see http://www.gnu.org/licenses or write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA, 02110-1301 USA, or download the license from the following URL:
 * http://itextpdf.com/terms-of-use/
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU Affero General Public License.
 *
 * In accordance with Section 7(b) of the GNU Affero General Public License,
 * you must retain the producer line in every PDF that is created or manipulated
 * using iText.
 *
 * You can be released from the requirements of the license by purchasing
 * a commercial license. Buying such a license is mandatory as soon as you
 * develop commercial activities involving the iText software without
 * disclosing the source code of your own applications.
 * These activities include: offering paid services to customers as an ASP,
 * serving PDFs on the fly in a web application, shipping iText with a closed
 * source product.
 *
 * For more information, please contact iText Software Corp. at this
 * address: sales@itextpdf.com
 * 
 * This code was originally released in 2001 by SUN (see class
 * com.sun.media.imageioimpl.plugins.bmp.BMPImageReader.java)
 * using the BSD license in a specific wording. In a mail dating from
 * January 23, 2008, Brian Burkhalter (@sun.com) gave us permission
 * to use the code under the following version of the BSD license:
 *
 * Copyright (c) 2005 Sun Microsystems, Inc. All  Rights Reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met: 
 * 
 * - Redistribution of source code must retain the above copyright 
 *   notice, this  list of conditions and the following disclaimer.
 * 
 * - Redistribution 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.
 * 
 * Neither the name of Sun Microsystems, Inc. or the names of 
 * contributors may be used to endorse or promote products derived 
 * from this software without specific prior written permission.
 * 
 * This software is provided "AS IS," without a warranty of any 
 * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND 
 * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, 
 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
 * EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL 
 * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF 
 * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
 * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR 
 * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
 * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
 * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
 * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGES. 
 * 
 * You acknowledge that this software is not designed or intended for 
 * use in the design, construction, operation or maintenance of any 
 * nuclear facility.
 */

namespace iTextSharp.text.pdf.codec {
    /** Reads a BMP image. All types of BMP can be read.
    * <p>
    * It is based in the JAI codec.
    *
    * @author  Paulo Soares
    */
    public class BmpImage {
        
        // BMP variables
        private Stream inputStream;
        private long bitmapFileSize;
        private long bitmapOffset;
        private long compression;
        private long imageSize;
        private byte[] palette;
        private int imageType;
        private int numBands;
        private bool isBottomUp;
        private int bitsPerPixel;
        private int redMask, greenMask, blueMask, alphaMask;
        public Dictionary<string,object> properties = new Dictionary<string,object>();    
        private long xPelsPerMeter;
        private long yPelsPerMeter;
        // BMP Image types
        private const int VERSION_2_1_BIT = 0;
        private const int VERSION_2_4_BIT = 1;
        private const int VERSION_2_8_BIT = 2;
        private const int VERSION_2_24_BIT = 3;
        
        private const int VERSION_3_1_BIT = 4;
        private const int VERSION_3_4_BIT = 5;
        private const int VERSION_3_8_BIT = 6;
        private const int VERSION_3_24_BIT = 7;
        
        private const int VERSION_3_NT_16_BIT = 8;
        private const int VERSION_3_NT_32_BIT = 9;
        
        private const int VERSION_4_1_BIT = 10;
        private const int VERSION_4_4_BIT = 11;
        private const int VERSION_4_8_BIT = 12;
        private const int VERSION_4_16_BIT = 13;
        private const int VERSION_4_24_BIT = 14;
        private const int VERSION_4_32_BIT = 15;
        
        // Color space types
        private const int LCS_CALIBRATED_RGB = 0;
        private const int LCS_sRGB = 1;
        private const int LCS_CMYK = 2;
        
        // Compression Types
        private const int BI_RGB = 0;
        private const int BI_RLE8 = 1;
        private const int BI_RLE4 = 2;
        private const int BI_BITFIELDS = 3;
        
        int width;
        int height;
        
        internal BmpImage(Stream isp, bool noHeader, int size) {
            bitmapFileSize = size;
            bitmapOffset = 0;
            Process(isp, noHeader);
        }
        
        /** Reads a BMP from an url.
        * @param url the url
        * @throws IOException on error
        * @return the image
        */    
        public static Image GetImage(Uri url) {
            Stream isp = null;
            try {
                WebRequest wr = WebRequest.Create(url);
                wr.Credentials = CredentialCache.DefaultCredentials;
                isp = wr.GetResponse().GetResponseStream();
                Image img = GetImage(isp);
                img.Url = url;
                return img;
            }
            finally {
                if (isp != null) {
                    isp.Close();
                }
            }
        }
        
        /** Reads a BMP from a stream. The stream is not closed.
        * @param is the stream
        * @throws IOException on error
        * @return the image
        */    
        public static Image GetImage(Stream isp) {
            return GetImage(isp, false, 0);
        }
        
        /** Reads a BMP from a stream. The stream is not closed.
        * The BMP may not have a header and be considered as a plain DIB.
        * @param is the stream
        * @param noHeader true to process a plain DIB
        * @param size the size of the DIB. Not used for a BMP
        * @throws IOException on error
        * @return the image
        */    
        public static Image GetImage(Stream isp, bool noHeader, int size) {
            BmpImage bmp = new BmpImage(isp, noHeader, size);
            Image img = bmp.GetImage();
            img.SetDpi((int)((double)bmp.xPelsPerMeter * 0.0254 + 0.5), (int)((double)bmp.yPelsPerMeter * 0.0254 + 0.5));
            img.OriginalType = Image.ORIGINAL_BMP;
            return img;
        }
        
        /** Reads a BMP from a file.
        * @param file the file
        * @throws IOException on error
        * @return the image
        */    
        public static Image GetImage(String file) {
            return GetImage(Utilities.ToURL(file));
        }
        
        /** Reads a BMP from a byte array.
        * @param data the byte array
        * @throws IOException on error
        * @return the image
        */    
        public static Image GetImage(byte[] data) {
            Stream isp = new MemoryStream(data);
            Image img = GetImage(isp);
            img.OriginalData = data;
            return img;
        }

        
        protected void Process(Stream stream, bool noHeader) {
            if (noHeader || stream is BufferedStream) {
                inputStream = stream;
            } else {
                inputStream = new BufferedStream(stream);
            }
            if (!noHeader) {
                // Start File Header
                if (!(ReadUnsignedByte(inputStream) == 'B' &&
                ReadUnsignedByte(inputStream) == 'M')) {
                    throw new Exception(MessageLocalization.GetComposedMessage("invalid.magic.value.for.bmp.file"));
                }

                // Read file size
                bitmapFileSize = ReadDWord(inputStream);

                // Read the two reserved fields
                ReadWord(inputStream);
                ReadWord(inputStream);

                // Offset to the bitmap from the beginning
                bitmapOffset = ReadDWord(inputStream);

                // End File Header
            }
            // Start BitmapCoreHeader
            long size = ReadDWord(inputStream);
            
            if (size == 12) {
                width = ReadWord(inputStream);
                height = ReadWord(inputStream);
            } else {
                width = ReadLong(inputStream);
                height = ReadLong(inputStream);
            }
            
            int planes = ReadWord(inputStream);
            bitsPerPixel = ReadWord(inputStream);
            
            properties["color_planes"] = planes;
            properties["bits_per_pixel"] = bitsPerPixel;
            
            // As BMP always has 3 rgb bands, except for Version 5,
            // which is bgra
            numBands = 3;
            if (bitmapOffset == 0)
                bitmapOffset = size;
            if (size == 12) {
                // Windows 2.x and OS/2 1.x
                properties["bmp_version"] = "BMP v. 2.x";
                
                // Classify the image type
                if (bitsPerPixel == 1) {
                    imageType = VERSION_2_1_BIT;
                } else if (bitsPerPixel == 4) {
                    imageType = VERSION_2_4_BIT;
                } else if (bitsPerPixel == 8) {
                    imageType = VERSION_2_8_BIT;
                } else if (bitsPerPixel == 24) {
                    imageType = VERSION_2_24_BIT;
                }
                
                // Read in the palette
                int numberOfEntries = (int)((bitmapOffset-14-size) / 3);
                int sizeOfPalette = numberOfEntries*3;
                if (bitmapOffset == size) {
                    switch (imageType) {
                        case VERSION_2_1_BIT:
                            sizeOfPalette = 2 * 3;
                            break;
                        case VERSION_2_4_BIT:
                            sizeOfPalette = 16 * 3;
                            break;
                        case VERSION_2_8_BIT:
                            sizeOfPalette = 256 * 3;
                            break;
                        case VERSION_2_24_BIT:
                            sizeOfPalette = 0;
                            break;
                    }
                    bitmapOffset = size + sizeOfPalette;
                }
                ReadPalette(sizeOfPalette);
            } else {
                
                compression = ReadDWord(inputStream);
                imageSize = ReadDWord(inputStream);
                xPelsPerMeter = ReadLong(inputStream);
                yPelsPerMeter = ReadLong(inputStream);
                long colorsUsed = ReadDWord(inputStream);
                long colorsImportant = ReadDWord(inputStream);
                
                switch ((int)compression) {
                    case BI_RGB:
                        properties["compression"] = "BI_RGB";
                        break;
                        
                    case BI_RLE8:
                        properties["compression"] = "BI_RLE8";
                        break;
                        
                    case BI_RLE4:
                        properties["compression"] = "BI_RLE4";
                        break;
                        
                    case BI_BITFIELDS:
                        properties["compression"] = "BI_BITFIELDS";
                        break;
                }
                
                properties["x_pixels_per_meter"] = xPelsPerMeter;
                properties["y_pixels_per_meter"] = yPelsPerMeter;
                properties["colors_used"] = colorsUsed;
                properties["colors_important"] = colorsImportant;
                
                if (size == 40) {
                    // Windows 3.x and Windows NT
                    switch ((int)compression) {
                        
                        case BI_RGB:  // No compression
                        case BI_RLE8:  // 8-bit RLE compression
                        case BI_RLE4:  // 4-bit RLE compression
                            
                            if (bitsPerPixel == 1) {
                                imageType = VERSION_3_1_BIT;
                            } else if (bitsPerPixel == 4) {
                                imageType = VERSION_3_4_BIT;
                            } else if (bitsPerPixel == 8) {
                                imageType = VERSION_3_8_BIT;
                            } else if (bitsPerPixel == 24) {
                                imageType = VERSION_3_24_BIT;
                            } else if (bitsPerPixel == 16) {
                                imageType = VERSION_3_NT_16_BIT;
                                redMask = 0x7C00;
                                greenMask = 0x3E0;
                                blueMask = 0x1F;
                                properties["red_mask"] = redMask;
                                properties["green_mask"] = greenMask;
                                properties["blue_mask"] = blueMask;
                            } else if (bitsPerPixel == 32) {
                                imageType = VERSION_3_NT_32_BIT;
                                redMask   = 0x00FF0000;
                                greenMask = 0x0000FF00;
                                blueMask  = 0x000000FF;
                                properties["red_mask"] = redMask;
                                properties["green_mask"] = greenMask;
                                properties["blue_mask"] = blueMask;
                            }

                            // Read in the palette
                            int numberOfEntries = (int)((bitmapOffset-14-size) / 4);
                            int sizeOfPalette = numberOfEntries*4;
                            if (bitmapOffset == size) {
                                switch (imageType) {
                                    case VERSION_3_1_BIT:
                                        sizeOfPalette = (int)(colorsUsed == 0 ? 2 : colorsUsed) * 4;
                                        break;
                                    case VERSION_3_4_BIT:
                                        sizeOfPalette = (int)(colorsUsed == 0 ? 16 : colorsUsed) * 4;
                                        break;
                                    case VERSION_3_8_BIT:
                                        sizeOfPalette = (int)(colorsUsed == 0 ? 256 : colorsUsed) * 4;
                                        break;
                                    default:
                                        sizeOfPalette = 0;
                                        break;
                                }
                                bitmapOffset = size + sizeOfPalette;
                            }
                            ReadPalette(sizeOfPalette);
                            properties["bmp_version"] = "BMP v. 3.x";
                            break;
                            
                        case BI_BITFIELDS:
                            
                            if (bitsPerPixel == 16) {
                                imageType = VERSION_3_NT_16_BIT;
                            } else if (bitsPerPixel == 32) {
                                imageType = VERSION_3_NT_32_BIT;
                            }
                            
                            // BitsField encoding
                            redMask = (int)ReadDWord(inputStream);
                            greenMask = (int)ReadDWord(inputStream);
                            blueMask = (int)ReadDWord(inputStream);
                            
                            properties["red_mask"] = redMask;
                            properties["green_mask"] = greenMask;
                            properties["blue_mask"] = blueMask;
                            
                            if (colorsUsed != 0) {
                                // there is a palette
                                sizeOfPalette = (int)colorsUsed*4;
                                ReadPalette(sizeOfPalette);
                            }
                            
                            properties["bmp_version"] = "BMP v. 3.x NT";
                            break;
                            
                        default:
                            throw new
                            Exception("Invalid compression specified in BMP file.");
                    }
                } else if (size == 108) {
                    // Windows 4.x BMP
                    
                    properties["bmp_version"] = "BMP v. 4.x";
                    
                    // rgb masks, valid only if comp is BI_BITFIELDS
                    redMask = (int)ReadDWord(inputStream);
                    greenMask = (int)ReadDWord(inputStream);
                    blueMask = (int)ReadDWord(inputStream);
                    // Only supported for 32bpp BI_RGB argb
                    alphaMask = (int)ReadDWord(inputStream);
                    long csType = ReadDWord(inputStream);
                    int redX = ReadLong(inputStream);
                    int redY = ReadLong(inputStream);
                    int redZ = ReadLong(inputStream);
                    int greenX = ReadLong(inputStream);
                    int greenY = ReadLong(inputStream);
                    int greenZ = ReadLong(inputStream);
                    int blueX = ReadLong(inputStream);
                    int blueY = ReadLong(inputStream);
                    int blueZ = ReadLong(inputStream);
                    long gammaRed = ReadDWord(inputStream);
                    long gammaGreen = ReadDWord(inputStream);
                    long gammaBlue = ReadDWord(inputStream);
                    
                    if (bitsPerPixel == 1) {
                        imageType = VERSION_4_1_BIT;
                    } else if (bitsPerPixel == 4) {
                        imageType = VERSION_4_4_BIT;
                    } else if (bitsPerPixel == 8) {
                        imageType = VERSION_4_8_BIT;
                    } else if (bitsPerPixel == 16) {
                        imageType = VERSION_4_16_BIT;
                        if ((int)compression == BI_RGB) {
                            redMask = 0x7C00;
                            greenMask = 0x3E0;
                            blueMask = 0x1F;
                        }
                    } else if (bitsPerPixel == 24) {
                        imageType = VERSION_4_24_BIT;
                    } else if (bitsPerPixel == 32) {
                        imageType = VERSION_4_32_BIT;
                        if ((int)compression == BI_RGB) {
                            redMask   = 0x00FF0000;
                            greenMask = 0x0000FF00;
                            blueMask  = 0x000000FF;
                        }
                    }
                    
                    properties["red_mask"] = redMask;
                    properties["green_mask"] = greenMask;
                    properties["blue_mask"] = blueMask;
                    properties["alpha_mask"] = alphaMask;

                    // Read in the palette
                    int numberOfEntries = (int)((bitmapOffset-14-size) / 4);
                    int sizeOfPalette = numberOfEntries*4;
                    if (bitmapOffset == size) {
                        switch (imageType) {
                            case VERSION_4_1_BIT:
                                sizeOfPalette = (int)(colorsUsed == 0 ? 2 : colorsUsed) * 4;
                                break;
                            case VERSION_4_4_BIT:
                                sizeOfPalette = (int)(colorsUsed == 0 ? 16 : colorsUsed) * 4;
                                break;
                            case VERSION_4_8_BIT:
                                sizeOfPalette = (int)(colorsUsed == 0 ? 256 : colorsUsed) * 4;
                                break;
                            default:
                                sizeOfPalette = 0;
                                break;
                        }
                        bitmapOffset = size + sizeOfPalette;
                    }
                    ReadPalette(sizeOfPalette);
                    
                    switch ((int)csType) {
                        case LCS_CALIBRATED_RGB:
                            // All the new fields are valid only for this case
                            properties["color_space"] = "LCS_CALIBRATED_RGB";
                            properties["redX"] = redX;
                            properties["redY"] = redY;
                            properties["redZ"] = redZ;
                            properties["greenX"] = greenX;
                            properties["greenY"] = greenY;
                            properties["greenZ"] = greenZ;
                            properties["blueX"] = blueX;
                            properties["blueY"] = blueY;
                            properties["blueZ"] = blueZ;
                            properties["gamma_red"] = gammaRed;
                            properties["gamma_green"] = gammaGreen;
                            properties["gamma_blue"] = gammaBlue;
                            
                            // break;
                            throw new
                            Exception("Not implemented yet.");
                            
                        case LCS_sRGB:
                            // Default Windows color space
                            properties["color_space"] = "LCS_sRGB";
                            break;
                            
                        case LCS_CMYK:
                            properties["color_space"] = "LCS_CMYK";
                            //		    break;
                            throw new
                            Exception("Not implemented yet.");
                    }
                    
                } else {
                    properties["bmp_version"] = "BMP v. 5.x";
                    throw new
                    Exception("BMP version 5 not implemented yet.");
                }
            }
            
            if (height > 0) {
                // bottom up image
                isBottomUp = true;
            } else {
                // top down image
                isBottomUp = false;
                height = Math.Abs(height);
            }
            // When number of bitsPerPixel is <= 8, we use IndexColorModel.
            if (bitsPerPixel == 1 || bitsPerPixel == 4 || bitsPerPixel == 8) {
                
                numBands = 1;
                
                
                // Create IndexColorModel from the palette.
                byte[] r;
                byte[] g;
                byte[] b;
                int sizep;
                if (imageType == VERSION_2_1_BIT ||
                imageType == VERSION_2_4_BIT ||
                imageType == VERSION_2_8_BIT) {
                    
                    sizep = palette.Length/3;
                    
                    if (sizep > 256) {
                        sizep = 256;
                    }
                    
                    int off;
                    r = new byte[sizep];
                    g = new byte[sizep];
                    b = new byte[sizep];
                    for (int i=0; i<sizep; i++) {
                        off = 3 * i;
                        b[i] = palette[off];
                        g[i] = palette[off+1];
                        r[i] = palette[off+2];
                    }
                } else {
                    sizep = palette.Length/4;
                    
                    if (sizep > 256) {
                        sizep = 256;
                    }
                    
                    int off;
                    r = new byte[sizep];
                    g = new byte[sizep];
                    b = new byte[sizep];
                    for (int i=0; i<sizep; i++) {
                        off = 4 * i;
                        b[i] = palette[off];
                        g[i] = palette[off+1];
                        r[i] = palette[off+2];
                    }
                }
                
            } else if (bitsPerPixel == 16) {
                numBands = 3;
            } else if (bitsPerPixel == 32) {
                numBands = alphaMask == 0 ? 3 : 4;
            } else {
                numBands = 3;
            }
        }
        
        private byte[] GetPalette(int group) {
            if (palette == null)
                return null;
            byte[] np = new byte[palette.Length / group * 3];
            int e = palette.Length / group;
            for (int k = 0; k < e; ++k) {
                int src = k * group;
                int dest = k * 3;
                np[dest + 2] = palette[src++];
                np[dest + 1] = palette[src++];
                np[dest] = palette[src];
            }
            return np;
        }
        
        private Image GetImage() {
            byte[] bdata = null; // buffer for byte data
            //short[] sdata = null; // buffer for short data
            //int[] idata = null; // buffer for int data
            
            //	if (sampleModel.GetDataType() == DataBuffer.TYPE_BYTE)
            //	    bdata = (byte[])((DataBufferByte)tile.GetDataBuffer()).GetData();
            //	else if (sampleModel.GetDataType() == DataBuffer.TYPE_USHORT)
            //	    sdata = (short[])((DataBufferUShort)tile.GetDataBuffer()).GetData();
            //	else if (sampleModel.GetDataType() == DataBuffer.TYPE_INT)
            //	    idata = (int[])((DataBufferInt)tile.GetDataBuffer()).GetData();
            
            // There should only be one tile.
            switch (imageType) {
                
                case VERSION_2_1_BIT:
                    // no compression
                    return Read1Bit(3);
                    
                case VERSION_2_4_BIT:
                    // no compression
                    return Read4Bit(3);
                    
                case VERSION_2_8_BIT:
                    // no compression
                    return Read8Bit(3);
                    
                case VERSION_2_24_BIT:
                    // no compression
                    bdata = new byte[width * height * 3];
                    Read24Bit(bdata);
                    return new ImgRaw(width, height, 3, 8, bdata);
                    
                case VERSION_3_1_BIT:
                    // 1-bit images cannot be compressed.
                    return Read1Bit(4);
                    
                case VERSION_3_4_BIT:
                    switch ((int)compression) {
                        case BI_RGB:
                            return Read4Bit(4);
                            
                        case BI_RLE4:
                            return ReadRLE4();
                            
                        default:
                            throw new
                            Exception("Invalid compression specified for BMP file.");
                    }
                    
                case VERSION_3_8_BIT:
                    switch ((int)compression) {
                        case BI_RGB:
                            return Read8Bit(4);
                            
                        case BI_RLE8:
                            return ReadRLE8();
                            
                        default:
                            throw new
                            Exception("Invalid compression specified for BMP file.");
                    }
                    
                case VERSION_3_24_BIT:
                    // 24-bit images are not compressed
                    bdata = new byte[width * height * 3];
                    Read24Bit(bdata);
                    return new ImgRaw(width, height, 3, 8, bdata);
                    
                case VERSION_3_NT_16_BIT:
                    return Read1632Bit(false);
                    
                case VERSION_3_NT_32_BIT:
                    return Read1632Bit(true);
                    
                case VERSION_4_1_BIT:
                    return Read1Bit(4);
                    
                case VERSION_4_4_BIT:
                    switch ((int)compression) {
                        
                        case BI_RGB:
                            return Read4Bit(4);
                            
                        case BI_RLE4:
                            return ReadRLE4();
                            
                        default:
                            throw new
                            Exception("Invalid compression specified for BMP file.");
                    }
                    
                case VERSION_4_8_BIT:
                    switch ((int)compression) {
                        
                        case BI_RGB:
                            return Read8Bit(4);
                            
                        case BI_RLE8:
                            return ReadRLE8();
                            
                        default:
                            throw new
                            Exception("Invalid compression specified for BMP file.");
                    }
                    
                case VERSION_4_16_BIT:
                    return Read1632Bit(false);
                    
                case VERSION_4_24_BIT:
                    bdata = new byte[width * height * 3];
                    Read24Bit(bdata);
                    return new ImgRaw(width, height, 3, 8, bdata);
                    
                case VERSION_4_32_BIT:
                    return Read1632Bit(true);
            }
            return null;
        }
        
        private Image IndexedModel(byte[] bdata, int bpc, int paletteEntries) {
            Image img = new ImgRaw(width, height, 1, bpc, bdata);
            PdfArray colorspace = new PdfArray();
            colorspace.Add(PdfName.INDEXED);
            colorspace.Add(PdfName.DEVICERGB);
            byte[] np = GetPalette(paletteEntries);
            int len = np.Length;
            colorspace.Add(new PdfNumber(len / 3 - 1));
            colorspace.Add(new PdfString(np));
            PdfDictionary ad = new PdfDictionary();
            ad.Put(PdfName.COLORSPACE, colorspace);
            img.Additional = ad;
            return img;
        }
        
        private void ReadPalette(int sizeOfPalette) {
            if (sizeOfPalette == 0) {
                return;
            }
            palette = new byte[sizeOfPalette];
            int bytesRead = 0;
            while (bytesRead < sizeOfPalette) {
                int r = inputStream.Read(palette, bytesRead, sizeOfPalette - bytesRead);
                if (r <= 0) {
                    throw new IOException(MessageLocalization.GetComposedMessage("incomplete.palette"));
                }
                bytesRead += r;
            }
            properties["palette"] = palette;
        }

        // Deal with 1 Bit images using IndexColorModels
        private Image Read1Bit(int paletteEntries) {
            byte[] bdata = new byte[((width + 7) / 8) * height];
            int padding = 0;
            int bytesPerScanline = (int)Math.Ceiling((double)width/8.0);
            
            int remainder = bytesPerScanline % 4;
            if (remainder != 0) {
                padding = 4 - remainder;
            }
            
            int imSize = (bytesPerScanline + padding) * height;
            
            // Read till we have the whole image
            byte[] values = new byte[imSize];
            int bytesRead = 0;
            while (bytesRead < imSize) {
                bytesRead += inputStream.Read(values, bytesRead,
                imSize - bytesRead);
            }
            
            if (isBottomUp) {
                
                // Convert the bottom up image to a top down format by copying
                // one scanline from the bottom to the top at a time.
                
                for (int i=0; i<height; i++) {
                    Array.Copy(values,
                    imSize - (i+1)*(bytesPerScanline + padding),
                    bdata,
                    i*bytesPerScanline, bytesPerScanline);
                }
            } else {
                
                for (int i=0; i<height; i++) {
                    Array.Copy(values,
                    i * (bytesPerScanline + padding),
                    bdata,
                    i * bytesPerScanline,
                    bytesPerScanline);
                }
            }
            return IndexedModel(bdata, 1, paletteEntries);
        }
        
        // Method to read a 4 bit BMP image data
        private Image Read4Bit(int paletteEntries) {
            byte[] bdata = new byte[((width + 1) / 2) * height];
            
            // Padding bytes at the end of each scanline
            int padding = 0;
            
            int bytesPerScanline = (int)Math.Ceiling((double)width/2.0);
            int remainder = bytesPerScanline % 4;
            if (remainder != 0) {
                padding = 4 - remainder;
            }
            
            int imSize = (bytesPerScanline + padding) * height;
            
            // Read till we have the whole image
            byte[] values = new byte[imSize];
            int bytesRead = 0;
            while (bytesRead < imSize) {
                bytesRead += inputStream.Read(values, bytesRead,
                imSize - bytesRead);
            }
            
            if (isBottomUp) {
                
                // Convert the bottom up image to a top down format by copying
                // one scanline from the bottom to the top at a time.
                for (int i=0; i<height; i++) {
                    Array.Copy(values,
                    imSize - (i+1)*(bytesPerScanline + padding),
                    bdata,
                    i*bytesPerScanline,
                    bytesPerScanline);
                }
            } else {
                for (int i=0; i<height; i++) {
                    Array.Copy(values,
                    i * (bytesPerScanline + padding),
                    bdata,
                    i * bytesPerScanline,
                    bytesPerScanline);
                }
            }
            return IndexedModel(bdata, 4, paletteEntries);
        }
        
        // Method to read 8 bit BMP image data
        private Image Read8Bit(int paletteEntries) {
            byte[] bdata = new byte[width * height];
            // Padding bytes at the end of each scanline
            int padding = 0;
            
            // width * bitsPerPixel should be divisible by 32
            int bitsPerScanline = width * 8;
            if ( bitsPerScanline%32 != 0) {
                padding = (bitsPerScanline/32 + 1)*32 - bitsPerScanline;
                padding = (int)Math.Ceiling(padding/8.0);
            }
            
            int imSize = (width + padding) * height;
            
            // Read till we have the whole image
            byte[] values = new byte[imSize];
            int bytesRead = 0;
            while (bytesRead < imSize) {
                bytesRead += inputStream.Read(values, bytesRead, imSize - bytesRead);
            }
            
            if (isBottomUp) {
                
                // Convert the bottom up image to a top down format by copying
                // one scanline from the bottom to the top at a time.
                for (int i=0; i<height; i++) {
                    Array.Copy(values,
                    imSize - (i+1) * (width + padding),
                    bdata,
                    i * width,
                    width);
                }
            } else {
                for (int i=0; i<height; i++) {
                    Array.Copy(values,
                    i * (width + padding),
                    bdata,
                    i * width,
                    width);
                }
            }
            return IndexedModel(bdata, 8, paletteEntries);
        }
        
        // Method to read 24 bit BMP image data
        private void Read24Bit(byte[] bdata) {
            // Padding bytes at the end of each scanline
            int padding = 0;
            
            // width * bitsPerPixel should be divisible by 32
            int bitsPerScanline = width * 24;
            if ( bitsPerScanline%32 != 0) {
                padding = (bitsPerScanline/32 + 1)*32 - bitsPerScanline;
                padding = (int)Math.Ceiling(padding/8.0);
            }
            
            
            int imSize = ((width * 3 + 3) / 4 * 4) * height;
            // Read till we have the whole image
            byte[] values = new byte[imSize];
            int bytesRead = 0;
            while (bytesRead < imSize) {
                int r = inputStream.Read(values, bytesRead,
                imSize - bytesRead);
                if (r < 0)
                    break;
                bytesRead += r;
            }
            
            int l=0, count;
            
            if (isBottomUp) {
                int max = width*height*3-1;
                
                count = -padding;
                for (int i=0; i<height; i++) {
                    l = max - (i+1)*width*3 + 1;
                    count += padding;
                    for (int j=0; j<width; j++) {
                        bdata[l + 2] = values[count++];
                        bdata[l + 1] = values[count++];
                        bdata[l] = values[count++];
                        l += 3;
                    }
                }
            } else {
                count = -padding;
                for (int i=0; i<height; i++) {
                    count += padding;
                    for (int j=0; j<width; j++) {
                        bdata[l + 2] = values[count++];
                        bdata[l + 1] = values[count++];
                        bdata[l] = values[count++];
                        l += 3;
                    }
                }
            }
        }
        
        private int FindMask(int mask) {
            int k = 0;
            for (; k < 32; ++k) {
                if ((mask & 1) == 1)
                    break;
                mask = Util.USR(mask, 1);
            }
            return mask;
        }
        
        private int FindShift(int mask) {
            int k = 0;
            for (; k < 32; ++k) {
                if ((mask & 1) == 1)
                    break;
                mask = Util.USR(mask, 1);
            }
            return k;
        }
        
        private Image Read1632Bit(bool is32) {
            
            int red_mask = FindMask(redMask);
            int red_shift = FindShift(redMask);
            int red_factor = red_mask + 1;
            int green_mask = FindMask(greenMask);
            int green_shift = FindShift(greenMask);
            int green_factor = green_mask + 1;
            int blue_mask = FindMask(blueMask);
            int blue_shift = FindShift(blueMask);
            int blue_factor = blue_mask + 1;
            byte[] bdata = new byte[width * height * 3];
            // Padding bytes at the end of each scanline
            int padding = 0;
            
            if (!is32) {
            // width * bitsPerPixel should be divisible by 32
                int bitsPerScanline = width * 16;
                if ( bitsPerScanline%32 != 0) {
                    padding = (bitsPerScanline/32 + 1)*32 - bitsPerScanline;
                    padding = (int)Math.Ceiling(padding/8.0);
                }
            }
            
            int imSize = (int)imageSize;
            if (imSize == 0) {
                imSize = (int)(bitmapFileSize - bitmapOffset);
            }
            
            int l=0;
            int v;
            if (isBottomUp) {
                for (int i=height - 1; i >= 0; --i) {
                    l = width * 3 * i;
                    for (int j=0; j<width; j++) {
                        if (is32)
                            v = (int)ReadDWord(inputStream);
                        else
                            v = ReadWord(inputStream);
                        bdata[l++] = (byte)((Util.USR(v, red_shift) & red_mask) * 256 / red_factor);
                        bdata[l++] = (byte)((Util.USR(v, green_shift) & green_mask) * 256 / green_factor);
                        bdata[l++] = (byte)((Util.USR(v, blue_shift) & blue_mask) * 256 / blue_factor);
                    }
                    for (int m=0; m<padding; m++) {
                        inputStream.ReadByte();
                    }
                }
            } else {
                for (int i=0; i<height; i++) {
                    for (int j=0; j<width; j++) {
                        if (is32)
                            v = (int)ReadDWord(inputStream);
                        else
                            v = ReadWord(inputStream);
                        bdata[l++] = (byte)((Util.USR(v, red_shift) & red_mask) * 256 / red_factor);
                        bdata[l++] = (byte)((Util.USR(v, green_shift) & green_mask) * 256 / green_factor);
                        bdata[l++] = (byte)((Util.USR(v, blue_shift) & blue_mask) * 256 / blue_factor);
                    }
                    for (int m=0; m<padding; m++) {
                        inputStream.ReadByte();
                    }
                }
            }
            return new ImgRaw(width, height, 3, 8, bdata);
        }
        
        private Image ReadRLE8() {
            
            // If imageSize field is not provided, calculate it.
            int imSize = (int)imageSize;
            if (imSize == 0) {
                imSize = (int)(bitmapFileSize - bitmapOffset);
            }
                        
            // Read till we have the whole image
            byte[] values = new byte[imSize];
            int bytesRead = 0;
            while (bytesRead < imSize) {
                bytesRead += inputStream.Read(values, bytesRead,
                imSize - bytesRead);
            }
            
            // Since data is compressed, decompress it
            byte[] val = DecodeRLE(true, values);
            
            // Uncompressed data does not have any padding
            imSize = width * height;
            
            if (isBottomUp) {
                
                // Convert the bottom up image to a top down format by copying
                // one scanline from the bottom to the top at a time.
                // int bytesPerScanline = (int)Math.Ceil((double)width/8.0);
                byte[] temp = new byte[val.Length];
                int bytesPerScanline = width;
                for (int i=0; i<height; i++) {
                    Array.Copy(val,
                    imSize - (i+1)*(bytesPerScanline),
                    temp,
                    i*bytesPerScanline, bytesPerScanline);
                }
                val = temp;
            }
            return IndexedModel(val, 8, 4);
        }
        
        private Image ReadRLE4() {
            
            // If imageSize field is not specified, calculate it.
            int imSize = (int)imageSize;
            if (imSize == 0) {
                imSize = (int)(bitmapFileSize - bitmapOffset);
            }
            
            // Read till we have the whole image
            byte[] values = new byte[imSize];
            int bytesRead = 0;
            while (bytesRead < imSize) {
                bytesRead += inputStream.Read(values, bytesRead,
                imSize - bytesRead);
            }
            
            // Decompress the RLE4 compressed data.
            byte[] val = DecodeRLE(false, values);
            
            // Invert it as it is bottom up format.
            if (isBottomUp) {
                
                byte[] inverted = val;
                val = new byte[width * height];
                int l = 0, index, lineEnd;
                
                for (int i = height-1; i >= 0; i--) {
                    index = i * width;
                    lineEnd = l + width;
                    while (l != lineEnd) {
                        val[l++] = inverted[index++];
                    }
                }
            }
            int stride = ((width + 1) / 2);
            byte[] bdata = new byte[stride * height];
            int ptr = 0;
            int sh = 0;
            for (int h = 0; h < height; ++h) {
                for (int w = 0; w < width; ++w) {
                    if ((w & 1) == 0)
                        bdata[sh + w / 2] = (byte)(val[ptr++] << 4);
                    else
                        bdata[sh + w / 2] |= (byte)(val[ptr++] & 0x0f);
                }
                sh += stride;
            }
            return IndexedModel(bdata, 4, 4);
        }
        
        private byte[] DecodeRLE(bool is8, byte[] values) {
            byte[] val = new byte[width * height];
            try {
                int ptr = 0;
                int x = 0;
                int q = 0;
                for (int y = 0; y < height && ptr < values.Length;) {
                    int count = values[ptr++] & 0xff;
                    if (count != 0) {
                        // encoded mode
                        int bt = values[ptr++] & 0xff;
                        if (is8) {
                            for (int i = count; i != 0; --i) {
                                val[q++] = (byte)bt;
                            }
                        }
                        else {
                            for (int i = 0; i < count; ++i) {
                                val[q++] = (byte)((i & 1) == 1 ? (bt & 0x0f) : ((bt >> 4) & 0x0f));
                            }
                        }
                        x += count;
                    }
                    else {
                        // escape mode
                        count = values[ptr++] & 0xff;
                        if (count == 1)
                            break;
                        switch (count) {
                            case 0:
                                x = 0;
                                ++y;
                                q = y * width;
                                break;
                            case 2:
                                // delta mode
                                x += values[ptr++] & 0xff;
                                y += values[ptr++] & 0xff;
                                q = y * width + x;
                                break;
                            default:
                                // absolute mode
                                if (is8) {
                                    for (int i = count; i != 0; --i)
                                        val[q++] = (byte)(values[ptr++] & 0xff);
                                }
                                else {
                                    int bt = 0;
                                    for (int i = 0; i < count; ++i) {
                                        if ((i & 1) == 0)
                                            bt = values[ptr++] & 0xff;
                                        val[q++] = (byte)((i & 1) == 1 ? (bt & 0x0f) : ((bt >> 4) & 0x0f));
                                    }
                                }
                                x += count;
                                // read pad byte
                                if (is8) {
                                    if ((count & 1) == 1)
                                        ++ptr;
                                }
                                else {
                                    if ((count & 3) == 1 || (count & 3) == 2)
                                        ++ptr;
                                }
                                break;
                        }
                    }
                }
            }
            catch {
                //empty on purpose
            }
            
            return val;
        }
        
        // Windows defined data type reading methods - everything is little endian
        
        // Unsigned 8 bits
        private int ReadUnsignedByte(Stream stream) {
            return (stream.ReadByte() & 0xff);
        }
        
        // Unsigned 2 bytes
        private int ReadUnsignedShort(Stream stream) {
            int b1 = ReadUnsignedByte(stream);
            int b2 = ReadUnsignedByte(stream);
            return ((b2 << 8) | b1) & 0xffff;
        }
        
        // Signed 16 bits
        private int ReadShort(Stream stream) {
            int b1 = ReadUnsignedByte(stream);
            int b2 = ReadUnsignedByte(stream);
            return (b2 << 8) | b1;
        }
        
        // Unsigned 16 bits
        private int ReadWord(Stream stream) {
            return ReadUnsignedShort(stream);
        }
        
        // Unsigned 4 bytes
        private long ReadUnsignedInt(Stream stream) {
            int b1 = ReadUnsignedByte(stream);
            int b2 = ReadUnsignedByte(stream);
            int b3 = ReadUnsignedByte(stream);
            int b4 = ReadUnsignedByte(stream);
            long l = (long)((b4 << 24) | (b3 << 16) | (b2 << 8) | b1);
            return l & 0xffffffff;
        }
        
        // Signed 4 bytes
        private int ReadInt(Stream stream) {
            int b1 = ReadUnsignedByte(stream);
            int b2 = ReadUnsignedByte(stream);
            int b3 = ReadUnsignedByte(stream);
            int b4 = ReadUnsignedByte(stream);
            return (b4 << 24) | (b3 << 16) | (b2 << 8) | b1;
        }
        
        // Unsigned 4 bytes
        private long ReadDWord(Stream stream) {
            return ReadUnsignedInt(stream);
        }
        
        // 32 bit signed value
        private int ReadLong(Stream stream) {
            return ReadInt(stream);
        }
    }
}
