corrade-vassal – Rev 1

Subversion Repositories:
Rev:
/*
 * Copyright (c) 2006-2014, openmetaverse.org
 * All rights reserved.
 *
 * - Redistribution and use in source and binary forms, with or without
 *   modification, are permitted provided that the following conditions are met:
 *
 * - Redistributions of source code must retain the above copyright notice, this
 *   list of conditions and the following disclaimer.
 * - Neither the name of the openmetaverse.org nor the names
 *   of its contributors may be used to endorse or promote products derived from
 *   this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

using System;

namespace OpenMetaverse.Imaging
{
#if !NO_UNSAFE

    /// <summary>
    /// Capability to load TGAs to Bitmap 
    /// </summary>
    public class LoadTGAClass
    {
        struct tgaColorMap
        {
            public ushort FirstEntryIndex;
            public ushort Length;
            public byte EntrySize;

            public void Read(System.IO.BinaryReader br)
            {
                FirstEntryIndex = br.ReadUInt16();
                Length = br.ReadUInt16();
                EntrySize = br.ReadByte();
            }
        }

        struct tgaImageSpec
        {
            public ushort XOrigin;
            public ushort YOrigin;
            public ushort Width;
            public ushort Height;
            public byte PixelDepth;
            public byte Descriptor;

            public void Read(System.IO.BinaryReader br)
            {
                XOrigin = br.ReadUInt16();
                YOrigin = br.ReadUInt16();
                Width = br.ReadUInt16();
                Height = br.ReadUInt16();
                PixelDepth = br.ReadByte();
                Descriptor = br.ReadByte();
            }

            public byte AlphaBits
            {
                get
                {
                    return (byte)(Descriptor & 0xF);
                }
                set
                {
                    Descriptor = (byte)((Descriptor & ~0xF) | (value & 0xF));
                }
            }

            public bool BottomUp
            {
                get
                {
                    return (Descriptor & 0x20) == 0x20;
                }
                set
                {
                    Descriptor = (byte)((Descriptor & ~0x20) | (value ? 0x20 : 0));
                }
            }
        }

        struct tgaHeader
        {
            public byte IdLength;
            public byte ColorMapType;
            public byte ImageType;

            public tgaColorMap ColorMap;
            public tgaImageSpec ImageSpec;

            public void Read(System.IO.BinaryReader br)
            {
                this.IdLength = br.ReadByte();
                this.ColorMapType = br.ReadByte();
                this.ImageType = br.ReadByte();
                this.ColorMap = new tgaColorMap();
                this.ImageSpec = new tgaImageSpec();
                this.ColorMap.Read(br);
                this.ImageSpec.Read(br);
            }

            public bool RleEncoded
            {
                get
                {
                    return ImageType >= 9;
                }
            }
        }

        struct tgaCD
        {
            public uint RMask, GMask, BMask, AMask;
            public byte RShift, GShift, BShift, AShift;
            public uint FinalOr;
            public bool NeedNoConvert;
        }

        static uint UnpackColor(
            uint sourceColor, ref tgaCD cd)
        {
            if (cd.RMask == 0xFF && cd.GMask == 0xFF && cd.BMask == 0xFF)
            {
                // Special case to deal with 8-bit TGA files that we treat as alpha masks
                return sourceColor << 24;
            }
            else
            {
                uint rpermute = (sourceColor << cd.RShift) | (sourceColor >> (32 - cd.RShift));
                uint gpermute = (sourceColor << cd.GShift) | (sourceColor >> (32 - cd.GShift));
                uint bpermute = (sourceColor << cd.BShift) | (sourceColor >> (32 - cd.BShift));
                uint apermute = (sourceColor << cd.AShift) | (sourceColor >> (32 - cd.AShift));
                uint result =
                    (rpermute & cd.RMask) | (gpermute & cd.GMask)
                    | (bpermute & cd.BMask) | (apermute & cd.AMask) | cd.FinalOr;

                return result;
            }
        }

        static unsafe void decodeLine(
            System.Drawing.Imaging.BitmapData b,
            int line,
            int byp,
            byte[] data,
            ref tgaCD cd)
        {
            if (cd.NeedNoConvert)
            {
                // fast copy
                uint* linep = (uint*)((byte*)b.Scan0.ToPointer() + line * b.Stride);
                fixed (byte* ptr = data)
                {
                    uint* sptr = (uint*)ptr;
                    for (int i = 0; i < b.Width; ++i)
                    {
                        linep[i] = sptr[i];
                    }
                }
            }
            else
            {
                byte* linep = (byte*)b.Scan0.ToPointer() + line * b.Stride;

                uint* up = (uint*)linep;

                int rdi = 0;

                fixed (byte* ptr = data)
                {
                    for (int i = 0; i < b.Width; ++i)
                    {
                        uint x = 0;
                        for (int j = 0; j < byp; ++j)
                        {
                            x |= ((uint)ptr[rdi]) << (j << 3);
                            ++rdi;
                        }
                        up[i] = UnpackColor(x, ref cd);
                    }
                }
            }
        }

        static void decodeRle(
            System.Drawing.Imaging.BitmapData b,
            int byp, tgaCD cd, System.IO.BinaryReader br, bool bottomUp)
        {
            try
            {
                int w = b.Width;
                // make buffer larger, so in case of emergency I can decode 
                // over line ends.
                byte[] linebuffer = new byte[(w + 128) * byp];
                int maxindex = w * byp;
                int index = 0;

                for (int j = 0; j < b.Height; ++j)
                {
                    while (index < maxindex)
                    {
                        byte blocktype = br.ReadByte();

                        int bytestoread;
                        int bytestocopy;

                        if (blocktype >= 0x80)
                        {
                            bytestoread = byp;
                            bytestocopy = byp * (blocktype - 0x80);
                        }
                        else
                        {
                            bytestoread = byp * (blocktype + 1);
                            bytestocopy = 0;
                        }

                        //if (index + bytestoread > maxindex)
                        //      throw new System.ArgumentException ("Corrupt TGA");

                        br.Read(linebuffer, index, bytestoread);
                        index += bytestoread;

                        for (int i = 0; i != bytestocopy; ++i)
                        {
                            linebuffer[index + i] = linebuffer[index + i - bytestoread];
                        }
                        index += bytestocopy;
                    }
                    if (!bottomUp)
                        decodeLine(b, b.Height - j - 1, byp, linebuffer, ref cd);
                    else
                        decodeLine(b, j, byp, linebuffer, ref cd);

                    if (index > maxindex)
                    {
                        Array.Copy(linebuffer, maxindex, linebuffer, 0, index - maxindex);
                        index -= maxindex;
                    }
                    else
                        index = 0;

                }
            }
            catch (System.IO.EndOfStreamException)
            {
            }
        }

        static void decodePlain(
            System.Drawing.Imaging.BitmapData b,
            int byp, tgaCD cd, System.IO.BinaryReader br, bool bottomUp)
        {
            int w = b.Width;
            byte[] linebuffer = new byte[w * byp];

            for (int j = 0; j < b.Height; ++j)
            {
                br.Read(linebuffer, 0, w * byp);

                if (!bottomUp)
                    decodeLine(b, b.Height - j - 1, byp, linebuffer, ref cd);
                else
                    decodeLine(b, j, byp, linebuffer, ref cd);
            }
        }

        static void decodeStandard8(
            System.Drawing.Imaging.BitmapData b,
            tgaHeader hdr,
            System.IO.BinaryReader br)
        {
            tgaCD cd = new tgaCD();
            cd.RMask = 0x000000ff;
            cd.GMask = 0x000000ff;
            cd.BMask = 0x000000ff;
            cd.AMask = 0x000000ff;
            cd.RShift = 0;
            cd.GShift = 0;
            cd.BShift = 0;
            cd.AShift = 0;
            cd.FinalOr = 0x00000000;
            if (hdr.RleEncoded)
                decodeRle(b, 1, cd, br, hdr.ImageSpec.BottomUp);
            else
                decodePlain(b, 1, cd, br, hdr.ImageSpec.BottomUp);
        }

        static void decodeSpecial16(
            System.Drawing.Imaging.BitmapData b, tgaHeader hdr, System.IO.BinaryReader br)
        {
            // i must convert the input stream to a sequence of uint values
            // which I then unpack.
            tgaCD cd = new tgaCD();
            cd.RMask = 0x00f00000;
            cd.GMask = 0x0000f000;
            cd.BMask = 0x000000f0;
            cd.AMask = 0xf0000000;
            cd.RShift = 12;
            cd.GShift = 8;
            cd.BShift = 4;
            cd.AShift = 16;
            cd.FinalOr = 0;

            if (hdr.RleEncoded)
                decodeRle(b, 2, cd, br, hdr.ImageSpec.BottomUp);
            else
                decodePlain(b, 2, cd, br, hdr.ImageSpec.BottomUp);
        }

        static void decodeStandard16(
            System.Drawing.Imaging.BitmapData b,
            tgaHeader hdr,
            System.IO.BinaryReader br)
        {
            // i must convert the input stream to a sequence of uint values
            // which I then unpack.
            tgaCD cd = new tgaCD();
            cd.RMask = 0x00f80000;      // from 0xF800
            cd.GMask = 0x0000fc00;      // from 0x07E0
            cd.BMask = 0x000000f8;  // from 0x001F
            cd.AMask = 0x00000000;
            cd.RShift = 8;
            cd.GShift = 5;
            cd.BShift = 3;
            cd.AShift = 0;
            cd.FinalOr = 0xff000000;

            if (hdr.RleEncoded)
                decodeRle(b, 2, cd, br, hdr.ImageSpec.BottomUp);
            else
                decodePlain(b, 2, cd, br, hdr.ImageSpec.BottomUp);
        }


        static void decodeSpecial24(System.Drawing.Imaging.BitmapData b,
            tgaHeader hdr, System.IO.BinaryReader br)
        {
            // i must convert the input stream to a sequence of uint values
            // which I then unpack.
            tgaCD cd = new tgaCD();
            cd.RMask = 0x00f80000;
            cd.GMask = 0x0000fc00;
            cd.BMask = 0x000000f8;
            cd.AMask = 0xff000000;
            cd.RShift = 8;
            cd.GShift = 5;
            cd.BShift = 3;
            cd.AShift = 8;
            cd.FinalOr = 0;

            if (hdr.RleEncoded)
                decodeRle(b, 3, cd, br, hdr.ImageSpec.BottomUp);
            else
                decodePlain(b, 3, cd, br, hdr.ImageSpec.BottomUp);
        }

        static void decodeStandard24(System.Drawing.Imaging.BitmapData b,
            tgaHeader hdr, System.IO.BinaryReader br)
        {
            // i must convert the input stream to a sequence of uint values
            // which I then unpack.
            tgaCD cd = new tgaCD();
            cd.RMask = 0x00ff0000;
            cd.GMask = 0x0000ff00;
            cd.BMask = 0x000000ff;
            cd.AMask = 0x00000000;
            cd.RShift = 0;
            cd.GShift = 0;
            cd.BShift = 0;
            cd.AShift = 0;
            cd.FinalOr = 0xff000000;

            if (hdr.RleEncoded)
                decodeRle(b, 3, cd, br, hdr.ImageSpec.BottomUp);
            else
                decodePlain(b, 3, cd, br, hdr.ImageSpec.BottomUp);
        }

        static void decodeStandard32(System.Drawing.Imaging.BitmapData b,
            tgaHeader hdr, System.IO.BinaryReader br)
        {
            // i must convert the input stream to a sequence of uint values
            // which I then unpack.
            tgaCD cd = new tgaCD();
            cd.RMask = 0x00ff0000;
            cd.GMask = 0x0000ff00;
            cd.BMask = 0x000000ff;
            cd.AMask = 0xff000000;
            cd.RShift = 0;
            cd.GShift = 0;
            cd.BShift = 0;
            cd.AShift = 0;
            cd.FinalOr = 0x00000000;
            cd.NeedNoConvert = true;

            if (hdr.RleEncoded)
                decodeRle(b, 4, cd, br, hdr.ImageSpec.BottomUp);
            else
                decodePlain(b, 4, cd, br, hdr.ImageSpec.BottomUp);
        }


        public static System.Drawing.Size GetTGASize(string filename)
        {
            System.IO.FileStream f = System.IO.File.OpenRead(filename);

            System.IO.BinaryReader br = new System.IO.BinaryReader(f);

            tgaHeader header = new tgaHeader();
            header.Read(br);
            br.Close();

            return new System.Drawing.Size(header.ImageSpec.Width, header.ImageSpec.Height);

        }

        public static System.Drawing.Bitmap LoadTGA(System.IO.Stream source)
        {
            byte[] buffer = new byte[source.Length];
            source.Read(buffer, 0, buffer.Length);

            System.IO.MemoryStream ms = new System.IO.MemoryStream(buffer);

            using (System.IO.BinaryReader br = new System.IO.BinaryReader(ms))
            {
                tgaHeader header = new tgaHeader();
                header.Read(br);

                if (header.ImageSpec.PixelDepth != 8 &&
                    header.ImageSpec.PixelDepth != 16 &&
                    header.ImageSpec.PixelDepth != 24 &&
                    header.ImageSpec.PixelDepth != 32)
                    throw new ArgumentException("Not a supported tga file.");

                if (header.ImageSpec.AlphaBits > 8)
                    throw new ArgumentException("Not a supported tga file.");

                if (header.ImageSpec.Width > 4096 ||
                    header.ImageSpec.Height > 4096)
                    throw new ArgumentException("Image too large.");

                                System.Drawing.Bitmap b;
                                System.Drawing.Imaging.BitmapData bd;

                                // Create a bitmap for the image.
                                // Only include an alpha layer when the image requires one.
                                if (header.ImageSpec.AlphaBits > 0 ||
                                        header.ImageSpec.PixelDepth == 8 ||     // Assume  8 bit images are alpha only
                                        header.ImageSpec.PixelDepth == 32)      // Assume 32 bit images are ARGB
                                {       // Image needs an alpha layer
                                        b = new System.Drawing.Bitmap(
                                                header.ImageSpec.Width,
                                                header.ImageSpec.Height,
                                                System.Drawing.Imaging.PixelFormat.Format32bppArgb);

                                        bd = b.LockBits(new System.Drawing.Rectangle(0, 0, b.Width, b.Height),
                                                System.Drawing.Imaging.ImageLockMode.WriteOnly,
                                                System.Drawing.Imaging.PixelFormat.Format32bppPArgb);
                                }
                                else
                                {       // Image does not need an alpha layer, so do not include one.
                                        b = new System.Drawing.Bitmap(
                                                header.ImageSpec.Width,
                                                header.ImageSpec.Height,
                                                System.Drawing.Imaging.PixelFormat.Format32bppRgb);

                                        bd = b.LockBits(new System.Drawing.Rectangle(0, 0, b.Width, b.Height),
                                                System.Drawing.Imaging.ImageLockMode.WriteOnly,
                                                System.Drawing.Imaging.PixelFormat.Format32bppRgb);
                                }

                switch (header.ImageSpec.PixelDepth)
                {
                    case 8:
                        decodeStandard8(bd, header, br);
                        break;
                    case 16:
                        if (header.ImageSpec.AlphaBits > 0)
                            decodeSpecial16(bd, header, br);
                        else
                            decodeStandard16(bd, header, br);
                        break;
                    case 24:
                        if (header.ImageSpec.AlphaBits > 0)
                            decodeSpecial24(bd, header, br);
                        else
                            decodeStandard24(bd, header, br);
                        break;
                    case 32:
                        decodeStandard32(bd, header, br);
                        break;
                    default:
                        b.UnlockBits(bd);
                        b.Dispose();
                        return null;
                }

                b.UnlockBits(bd);
                return b;
            }
        }

        public static unsafe ManagedImage LoadTGAImage(System.IO.Stream source)
        {
            return LoadTGAImage(source, false);
        }
        
        public static unsafe ManagedImage LoadTGAImage(System.IO.Stream source, bool mask)
        {
            byte[] buffer = new byte[source.Length];
            source.Read(buffer, 0, buffer.Length);

            System.IO.MemoryStream ms = new System.IO.MemoryStream(buffer);

            using (System.IO.BinaryReader br = new System.IO.BinaryReader(ms))
            {
                tgaHeader header = new tgaHeader();
                header.Read(br);

                if (header.ImageSpec.PixelDepth != 8 &&
                    header.ImageSpec.PixelDepth != 16 &&
                    header.ImageSpec.PixelDepth != 24 &&
                    header.ImageSpec.PixelDepth != 32)
                    throw new ArgumentException("Not a supported tga file.");

                if (header.ImageSpec.AlphaBits > 8)
                    throw new ArgumentException("Not a supported tga file.");

                if (header.ImageSpec.Width > 4096 ||
                    header.ImageSpec.Height > 4096)
                    throw new ArgumentException("Image too large.");

                byte[] decoded = new byte[header.ImageSpec.Width * header.ImageSpec.Height * 4];
                System.Drawing.Imaging.BitmapData bd = new System.Drawing.Imaging.BitmapData();

                fixed (byte* pdecoded = &decoded[0])
                {
                    bd.Width = header.ImageSpec.Width;
                    bd.Height = header.ImageSpec.Height;
                    bd.PixelFormat = System.Drawing.Imaging.PixelFormat.Format32bppPArgb;
                    bd.Stride = header.ImageSpec.Width * 4;
                    bd.Scan0 = (IntPtr)pdecoded;

                    switch (header.ImageSpec.PixelDepth)
                    {
                        case 8:
                            decodeStandard8(bd, header, br);
                            break;
                        case 16:
                            if (header.ImageSpec.AlphaBits > 0)
                                decodeSpecial16(bd, header, br);
                            else
                                decodeStandard16(bd, header, br);
                            break;
                        case 24:
                            if (header.ImageSpec.AlphaBits > 0)
                                decodeSpecial24(bd, header, br);
                            else
                                decodeStandard24(bd, header, br);
                            break;
                        case 32:
                            decodeStandard32(bd, header, br);
                            break;
                        default:
                            return null;
                    }
                }

                int n = header.ImageSpec.Width * header.ImageSpec.Height;
                ManagedImage image;

                if (mask && header.ImageSpec.AlphaBits == 0 && header.ImageSpec.PixelDepth == 8)
                {
                    image = new ManagedImage(header.ImageSpec.Width, header.ImageSpec.Height,
                        ManagedImage.ImageChannels.Alpha);
                    int p = 3;

                    for (int i = 0; i < n; i++)
                    {
                        image.Alpha[i] = decoded[p];
                        p += 4;
                    }
                }
                else
                {
                    image = new ManagedImage(header.ImageSpec.Width, header.ImageSpec.Height,
                        ManagedImage.ImageChannels.Color | ManagedImage.ImageChannels.Alpha);
                    int p = 0;

                    for (int i = 0; i < n; i++)
                    {
                        image.Blue[i] = decoded[p++];
                        image.Green[i] = decoded[p++];
                        image.Red[i] = decoded[p++];
                        image.Alpha[i] = decoded[p++];
                    }
                }

                br.Close();
                return image;
            }
        }

        public static System.Drawing.Bitmap LoadTGA(string filename)
        {
            try
            {
                using (System.IO.FileStream f = System.IO.File.OpenRead(filename))
                {
                    return LoadTGA(f);
                }
            }
            catch (System.IO.DirectoryNotFoundException)
            {
                return null;    // file not found
            }
            catch (System.IO.FileNotFoundException)
            {
                return null; // file not found
            }
        }
    }

#endif
}