
#region ================== Copyright (c) 2007 Pascal vd Heiden

/*
 * Copyright (c) 2007 Pascal vd Heiden, www.codeimp.com
 * This program is released under GNU General Public License
 * 
 * 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 General Public License for more details.
 * 
 */

#endregion

#region ================== Namespaces

using System;
using System.Runtime.InteropServices;
using System.IO;
using System.Drawing;
using CodeImp.DoomBuilder.Rendering;
using System.Drawing.Imaging;
using CodeImp.DoomBuilder.Data;

#endregion

namespace CodeImp.DoomBuilder.IO
{
    class PcxImageReader : IImageReader
    {
        public Bitmap ReadAsBitmap(Stream stream, out int offsetx, out int offsety)
        {
            offsetx = int.MinValue;
            offsety = int.MinValue;

            using (BinaryReader reader = new BinaryReader(stream, System.Text.Encoding.UTF8, true))
            {
                int manufacturer = reader.ReadByte(); // 10=ZSoft
                int version = reader.ReadByte();
                // 0=PC Paintbrush v2.5
                // 2=PC Paintbrush v2.8 w palette information
                // 3=PC Paintbrush v2.8 w/o palette information
                // 4=PC Paintbrush/Windows
                // 5=PC Paintbrush v3.0+
                int encoding = reader.ReadByte(); // 1 = RLE, none other known
                int bitsPerComponent = reader.ReadByte();
                int leftMargin = reader.ReadUInt16();
                int topMargin = reader.ReadUInt16();
                int rightMargin = reader.ReadUInt16();
                int bottomMargin = reader.ReadUInt16();
                int dpiX = reader.ReadUInt16();
                int dpiY = reader.ReadUInt16();
                byte[] egaPalette = reader.ReadBytes(48); // 16 RGB triplets
                reader.ReadByte(); // reserved
                int numColorPlanes = reader.ReadByte();
                int planePitch = reader.ReadUInt16(); // always even
                int paletteInfo = reader.ReadUInt16(); // 1=color/bw palette, 2=grayscale image
                int screenwidth = reader.ReadUInt16();
                int screenheight = reader.ReadUInt16();
                reader.ReadBytes(54); // reserved

                int width = rightMargin - leftMargin + 1;
                int height = bottomMargin - topMargin + 1;

                if (width == 0 || height == 0)
                    throw new InvalidDataException("Invalid pcx image file");

                int vgaPaletteID = 0;
                byte[] vgaPalette = null;

                int srcpitch = numColorPlanes * planePitch;
                byte[] scanlines = new byte[srcpitch * height];

                int pos = 0;
                while (pos < scanlines.Length)
                {
                    byte value = reader.ReadByte();
                    if ((value & 0xc0) == 0xc0) // two last bits
                    {
                        byte length = (byte)(value & 0x3f);
                        value = reader.ReadByte();
                        while (length > 0)
                        {
                            scanlines[pos++] = value;
                            length--;
                        }
                    }
                    else
                    {
                        scanlines[pos++] = value;
                    }
                }

                byte[] imageData = new byte[width * height * 4];
                int destpitch = width * 4;

                if (bitsPerComponent == 4 && numColorPlanes == 1 && paletteInfo < 2) // 16 colors from a palette
                {
                    for (int y = 0; y < height; y++)
                    {
                        for (int x = 0; x < width; x++)
                        {
                            int srcshift = ((x & 1) << 2);
                            int srcoffset = (x >> 1) + y * srcpitch;
                            int palentry = (scanlines[srcoffset] >> srcshift) & 15;
                            int offset = x * 4 + y * destpitch;
                            imageData[offset + 2] = egaPalette[palentry * 3];
                            imageData[offset + 1] = egaPalette[palentry * 3 + 1];
                            imageData[offset + 0] = egaPalette[palentry * 3 + 2];
                            imageData[offset + 3] = 255;
                        }
                    }
                }
                else if (bitsPerComponent == 4 && numColorPlanes == 4 && paletteInfo == 2) // 4096 colors with 16 levels of transparency
                {
                    for (int y = 0; y < height; y++)
                    {
                        for (int x = 0; x < width; x++)
                        {
                            int srcshift = ((x & 1) << 2);
                            int srcoffset = (x >> 1) + y * srcpitch;
                            int red = (scanlines[srcoffset] >> srcshift) & 15;
                            int green = (scanlines[srcoffset + planePitch] >> srcshift) & 15;
                            int blue = (scanlines[srcoffset + planePitch * 2] >> srcshift) & 15;
                            int alpha = (scanlines[srcoffset + planePitch * 3] >> srcshift) & 15;

                            int offset = x * 4 + y * destpitch;
                            imageData[offset + 2] = (byte)((red * 255 + 7) / 15);
                            imageData[offset + 1] = (byte)((green * 255 + 7) / 15);
                            imageData[offset + 0] = (byte)((blue * 255 + 7) / 15);
                            imageData[offset + 3] = (byte)((alpha * 255 + 7) / 15);
                        }
                    }
                }
                else if (bitsPerComponent == 8 && numColorPlanes == 1 && paletteInfo < 2) // 256 colors from a palette
                {
                    if (version == 5)
                    {
                        vgaPaletteID = reader.ReadByte(); // 0x0c
                        vgaPalette = reader.ReadBytes(768); // 256 RGB triplets
                    }
                    else
                    {
                        throw new InvalidDataException("No vga palette in pcx");
                    }

                    for (int y = 0; y < height; y++)
                    {
                        for (int x = 0; x < width; x++)
                        {
                            int palentry = scanlines[x + y * srcpitch];
                            int offset = x * 4 + y * destpitch;
                            imageData[offset + 2] = vgaPalette[palentry * 3];
                            imageData[offset + 1] = vgaPalette[palentry * 3 + 1];
                            imageData[offset + 0] = vgaPalette[palentry * 3 + 2];
                            imageData[offset + 3] = 255;
                        }
                    }
                }
                else if (bitsPerComponent == 8 && numColorPlanes == 1 && paletteInfo == 2) // 256 shades of gray
                {
                    for (int y = 0; y < height; y++)
                    {
                        for (int x = 0; x < width; x++)
                        {
                            byte gray = scanlines[x + y * srcpitch];
                            int offset = x * 4 + y * destpitch;
                            imageData[offset + 2] = gray;
                            imageData[offset + 1] = gray;
                            imageData[offset + 0] = gray;
                            imageData[offset + 3] = 255;
                        }
                    }
                }
                else if (bitsPerComponent == 8 && numColorPlanes == 3) // 24 true color
                {
                    for (int y = 0; y < height; y++)
                    {
                        for (int x = 0; x < width; x++)
                        {
                            int srcoffset = x + y * srcpitch;
                            int offset = x * 4 + y * destpitch;
                            imageData[offset + 2] = scanlines[srcoffset];
                            imageData[offset + 1] = scanlines[srcoffset + planePitch];
                            imageData[offset + 0] = scanlines[srcoffset + planePitch * 2];
                            imageData[offset + 3] = 255;
                        }
                    }
                }
                else if (bitsPerComponent == 8 && numColorPlanes == 4) // 24 true color with alpha channel
                {
                    for (int y = 0; y < height; y++)
                    {
                        for (int x = 0; x < width; x++)
                        {
                            int srcoffset = x + y * srcpitch;
                            int offset = x * 4 + y * destpitch;
                            imageData[offset + 2] = scanlines[srcoffset];
                            imageData[offset + 1] = scanlines[srcoffset + planePitch];
                            imageData[offset + 0] = scanlines[srcoffset + planePitch * 2];
                            imageData[offset + 3] = scanlines[srcoffset + planePitch * 3];
                        }
                    }
                }
                else
                {
                    throw new InvalidDataException(string.Format("Unsupported pcx subformat (bits={0}, planes={1})", bitsPerComponent, numColorPlanes));
                }

                return new Bitmap(width, height, destpitch, PixelFormat.Format32bppArgb, Marshal.UnsafeAddrOfPinnedArrayElement(imageData, 0));
            }
        }
    }

    class TgaImageReader : IImageReader
    {
        public Bitmap ReadAsBitmap(Stream stream, out int offsetx, out int offsety)
        {
            offsetx = int.MinValue;
            offsety = int.MinValue;

            using (BinaryReader reader = new BinaryReader(stream, System.Text.Encoding.UTF8, true))
            {
                read_header(reader);
                read_image_id(reader);
                read_color_map(reader);
                read_image_data(reader);
                decode_palette();
                byte[] image = decode_image();
                return new Bitmap(image_width, image_height, image_width * 4, PixelFormat.Format32bppArgb, Marshal.UnsafeAddrOfPinnedArrayElement(image, 0));
            }
        }

        void read_header(BinaryReader reader)
        {
            id_length = reader.ReadByte();
            colormap_type = reader.ReadByte();
            image_type = reader.ReadByte();

            colormap_orig = reader.ReadUInt16();
            colormap_length = reader.ReadUInt16();
            colormap_entry_size = reader.ReadByte();

            image_x_orig = reader.ReadInt16();
            image_y_orig = reader.ReadInt16();
            image_width = reader.ReadUInt16();
            image_height = reader.ReadUInt16();
            image_pixel_size = reader.ReadByte();
            image_descriptor = reader.ReadByte();

            if (colormap_type > 1)
                throw new InvalidDataException("Invalid or unsupported targa image file");

            if (image_type != 1 && image_type != 2 && image_type != 3 && image_type != 9 && image_type != 10 && image_type != 11)
                throw new InvalidDataException("Invalid or unsupported targa image type");

            if (image_width == 0 || image_height == 0)
                throw new InvalidDataException("Invalid targa image file");

            if (colormap_type == 0)
                colormap_length = 0;

            bytes_per_colormap_entry = (colormap_entry_size + 7) / 8;
            bytes_per_pixel_entry = (image_pixel_size + 7) / 8;

            if (bytes_per_pixel_entry > 4)
                throw new InvalidDataException("Unsupported targa image file");

            num_alpha_bits = image_descriptor & 0x0f;
            right_to_left = (image_descriptor & 0x10) != 0;
            top_down = (image_descriptor & 0x20) != 0;
        }

        void read_image_id(BinaryReader reader)
        {
            image_id = reader.ReadBytes(id_length);
        }

        void read_color_map(BinaryReader reader)
        {
            colormap_data = reader.ReadBytes(bytes_per_colormap_entry * colormap_length);
        }

        void read_image_data(BinaryReader reader)
        {
            if (image_type == 9 || image_type == 10 || image_type == 11) // RLE compressed
            {
                image_data = new byte[bytes_per_pixel_entry * image_width * image_height];

                byte[] rle_data = reader.ReadBytes((int)(reader.BaseStream.Length - reader.BaseStream.Position));

                int input = 0;
                int output = 0;
                int pixels_left = image_width * image_height;
                int input_available = rle_data.Length;
                while (pixels_left > 0 && input_available > 0)
                {
                    int code = rle_data[input];
                    int count = (code & 0x7f) + 1;
                    bool rle_packet = (code & 0x80) != 0;

                    input++;
                    input_available--;

                    if (rle_packet)
                    {
                        if (bytes_per_pixel_entry > input_available && pixels_left >= count) // Check for buffer overruns
                            break;

                        for (int i = 0; i < count; i++)
                        {
                            for (int j = 0; j < bytes_per_pixel_entry; j++)
                                image_data[output + i * bytes_per_pixel_entry + j] = rle_data[input + j];
                        }

                        input += bytes_per_pixel_entry;
                    }
                    else
                    {
                        if (count * bytes_per_pixel_entry >= input_available && pixels_left >= count) // Check for buffer overruns
                            break;

                        for (int j = 0; j < count * bytes_per_pixel_entry; j++)
                            image_data[output + j] = rle_data[input + j];

                        input += count * bytes_per_pixel_entry;
                    }

                    output += bytes_per_pixel_entry * count;
                    pixels_left -= count;
                }
            }
            else
            {
                image_data = reader.ReadBytes(bytes_per_pixel_entry * image_width * image_height);
            }
        }

        void decode_palette()
        {
            palette = new byte[colormap_length * 4];
            if (image_type == 1 || image_type == 9)
            {
                if (colormap_entry_size == 32)
                {
                    for (int i = 0; i < colormap_length * 4; i++)
                        palette[i] = colormap_data[i];

                    if (num_alpha_bits == 0)
                    {
                        for (int i = 0; i < colormap_length; i++)
                            palette[i * 4 + 3] = 255;
                    }
                }
                else if (colormap_entry_size == 24)
                {
                    for (int i = 0; i < colormap_length; i++)
                    {
                        palette[i * 4] = colormap_data[i * 3];
                        palette[i * 4 + 1] = colormap_data[i * 3 + 1];
                        palette[i * 4 + 2] = colormap_data[i * 3 + 2];
                        palette[i * 4 + 3] = 255;
                    }
                }
                else if (colormap_entry_size == 16) // 5,5,5,1
                {
                    for (int i = 0; i < colormap_length; i++)
                    {
                        int color = colormap_data[i * 2] | (((int)colormap_data[i * 2 + 1]) << 8);
                        int alpha_bit = (num_alpha_bits != 0) ? ((color >> 15) & 0x1) : 1;

                        palette[i * 4] = (byte)(((color >> 10) & 0x1f) << 3);
                        palette[i * 4 + 1] = (byte)(((color >> 5) & 0x1f) << 3);
                        palette[i * 4 + 2] = (byte)((color & 0x1f) << 3);
                        palette[i * 4 + 3] = (byte)((alpha_bit == 1) ? 255 : 0);
                    }
                }
                else
                {
                    throw new InvalidDataException("Unsupported targa image file");
                }
            }
        }

        byte[] decode_image()
        {
            // single color-map index for Pseudo-Color
            // Attribute, Red, Green and Blue ordered data for True-Color
            // independent color-map indices for Direct-Color

            if (image_type == 0) // no image data
            {
                return new byte[image_width * image_height * 4];
            }
            else if (image_type == 1 || image_type == 9) // color-mapped
            {
                return decode_color_mapped();
            }
            else if (image_type == 2 || image_type == 10) // true-color
            {
                return decode_true_color();
            }
            else if (image_type == 3 || image_type == 11) // black-and-white
            {
                return decode_grayscale();
            }
            else
            {
                throw new InvalidDataException("Invalid or unsupported targa image file");
            }
        }

        byte[] decode_color_mapped()
        {
            var image = new byte[image_width * image_height * 4];

            for (int y = 0; y < image_height; y++)
            {
                int output_line = (top_down ? y : image_height - y - 1) * image_width * 4;
                for (int x = 0; x < image_width; x++)
                {
                    int inx = (x + y * image_width) * bytes_per_pixel_entry;
                    int outx = output_line + (right_to_left ? image_width - 1 - x : x) * 4;

                    int index = 0;
                    for (int i = 0; i < bytes_per_pixel_entry; i++)
                        index |= ((int)image_data[inx + i]) << (i * 8);
                    index = Math.Min(Math.Max(index - colormap_orig, 0), (int)colormap_length - 1);
                    index *= 4;

                    image[outx] = palette[index];
                    image[outx + 1] = palette[index + 1];
                    image[outx + 2] = palette[index + 2];
                    image[outx + 3] = palette[index + 3];
                }
            }

            return image;
        }

        byte[] decode_true_color()
        {
            var image = new byte[image_width * image_height * 4];

            if (image_pixel_size == 32)
            {
                for (int y = 0; y < image_height; y++)
                {
                    int input_line = y * image_width * 4;
                    int output_line = (top_down ? y : image_height - y - 1) * image_width * 4;
                    for (int x = 0; x < image_width; x++)
                    {
                        int inx = input_line + x * 4;
                        int outx = output_line + (right_to_left ? image_width - 1 - x : x) * 4;
                        image[outx] = image_data[inx];
                        image[outx + 1] = image_data[inx + 1];
                        image[outx + 2] = image_data[inx + 2];
                        image[outx + 3] = (num_alpha_bits != 0) ? image_data[inx + 3] : (byte)255;
                    }
                }
            }
            else if (image_pixel_size == 24)
            {
                for (int y = 0; y < image_height; y++)
                {
                    int input_line = y * image_width * 3;
                    int output_line = (top_down ? y : image_height - y - 1) * image_width * 4;
                    for (int x = 0; x < image_width; x++)
                    {
                        int inx = input_line + x * 3;
                        int outx = output_line + (right_to_left ? image_width - 1 - x : x) * 4;
                        image[outx] = image_data[inx];
                        image[outx + 1] = image_data[inx + 1];
                        image[outx + 2] = image_data[inx + 2];
                        image[outx + 3] = 255;
                    }
                }
            }
            else if (image_pixel_size == 16)
            {
                for (int y = 0; y < image_height; y++)
                {
                    int input_line = y * image_width * 2;
                    int output_line = (top_down ? y : image_height - y - 1) * image_width * 4;
                    for (int x = 0; x < image_width; x++)
                    {
                        int inx = input_line + x * 2;
                        int outx = output_line + (right_to_left ? image_width - 1 - x : x) * 4;

                        int color = image_data[inx] | (((int)image_data[inx + 1]) << 8);
                        int alpha_bit = (num_alpha_bits != 0) ? ((color >> 15) & 0x1) : 1;

                        image[outx] = (byte)(((color >> 10) & 0x1f) << 3);
                        image[outx + 1] = (byte)(((color >> 5) & 0x1f) << 3);
                        image[outx + 2] = (byte)((color & 0x1f) << 3);
                        image[outx + 3] = (byte)((alpha_bit == 1) ? 255 : 0);
                    }
                }
            }
            else
            {
                throw new InvalidDataException("Unsupported targa image file");
            }

            return image;
        }

        byte[] decode_grayscale()
        {
            var image = new byte[image_width * image_height * 4];

            if (image_pixel_size == 8)
            {
                for (int y = 0; y < image_height; y++)
                {
                    int input_line = y * image_width;
                    int output_line = (top_down ? y : image_height - y - 1) * image_width * 4;
                    for (int x = 0; x < image_width; x++)
                    {
                        int inx = input_line + x;
                        int outx = output_line + (right_to_left ? image_width - 1 - x : x) * 4;
                        image[outx] = image_data[inx];
                        image[outx + 1] = image_data[inx];
                        image[outx + 2] = image_data[inx];
                        image[outx + 3] = 255;
                    }
                }
            }
            else
            {
                throw new InvalidDataException("Unsupported targa image file");
            }

            return image;
        }

        byte id_length;
        byte colormap_type;
        byte image_type;

        ushort colormap_orig;
        ushort colormap_length;
        ushort colormap_entry_size;

        short image_x_orig;
        short image_y_orig;
        ushort image_width;
        ushort image_height;
        byte image_pixel_size;
        byte image_descriptor;

        int bytes_per_colormap_entry;
        int bytes_per_pixel_entry;

        int num_alpha_bits;
        bool right_to_left;
        bool top_down;

        byte[] image_id;
        byte[] colormap_data;
        byte[] palette;
        byte[] image_data;
    }

    class FrameworkImageReader : IImageReader
    {
        bool isPng;

        public FrameworkImageReader(bool isPng)
        {
            this.isPng = isPng;
        }

        public Bitmap ReadAsBitmap(Stream stream, out int offsetx, out int offsety)
        {
            using (var image = Image.FromStream(new NoCloseStream(stream)))
            {
                ReadPngOffsets(stream, out offsetx, out offsety);
                return new Bitmap(image);
            }
        }

        void ReadPngOffsets(Stream stream, out int offsetx, out int offsety)
        {
            offsetx = int.MinValue;
            offsety = int.MinValue;

            if (isPng)
            {
                stream.Position = 8;
                using (BinaryReader reader = new BinaryReader(stream, System.Text.Encoding.UTF8, true))
                {
                    // Read chunks untill we encounter either "grAb" or "IDAT"
                    while (reader.BaseStream.Position < reader.BaseStream.Length)
                    {
                        // Big Endian!
                        int chunklength = ToInt32BigEndian(reader.ReadBytes(4));
                        string chunkname = new string(reader.ReadChars(4));

                        if (chunkname == "grAb")
                        {
                            offsetx = ToInt32BigEndian(reader.ReadBytes(4));
                            offsety = ToInt32BigEndian(reader.ReadBytes(4));
                            break;
                        }
                        else if (chunkname == "IDAT")
                        {
                            break;
                        }
                        else
                        {
                            // Skip the rest of the chunk
                            reader.BaseStream.Position += chunklength + 4;
                        }
                    }
                }
            }
        }

        static int ToInt32BigEndian(byte[] buffer)
        {
            return (buffer[0] << 24) | (buffer[1] << 16) | (buffer[2] << 8) | buffer[3];
        }

        // This wraps a stream but makes Close/Dispose do nothing. That prevents.net's Image.FromStream from closing it as we want to fall back to other image loaders.
        class NoCloseStream : Stream
        {
            Stream stream;
            public NoCloseStream(Stream s) { stream = s; }
            public override bool CanRead { get { return stream.CanRead; } }
            public override bool CanSeek { get { return stream.CanSeek; } }
            public override bool CanWrite { get { return stream.CanWrite; } }
            public override bool CanTimeout { get { return stream.CanTimeout; } }
            public override int ReadTimeout { get { return stream.ReadTimeout; } }
            public override int WriteTimeout { get { return stream.WriteTimeout; } }
            public override int EndRead(IAsyncResult asyncResult) { return stream.EndRead(asyncResult); }
            public override void EndWrite(IAsyncResult asyncResult) { stream.EndWrite(asyncResult); }
            public override int ReadByte() { return stream.ReadByte(); }
            public override long Length { get { return stream.Length; } }
            public override void WriteByte(byte value) { stream.WriteByte(value); }
            public override long Position { get { return stream.Position; } set { stream.Position = value; } }
            public override void Flush() { stream.Flush(); }
            public override int Read(byte[] buffer, int offset, int count) { return stream.Read(buffer, offset, count); }
            public override long Seek(long offset, SeekOrigin origin) { return stream.Seek(offset, origin); }
            public override void SetLength(long value) { stream.SetLength(value); }
            public override void Write(byte[] buffer, int offset, int count) { stream.Write(buffer, offset, count); }
        }
    }
}
