using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;

namespace Sheng.Winform.Controls.Drawing
{
    /// <summary>
    /// 
    /// </summary>
    public class BmpAdjuster
    {
        #region 原有方法
        /// <summary>
        /// 
        /// </summary>
        /// <param name="plt"></param>
        /// <returns></returns>
        public delegate ColorPalette PaletteAdjustEvent(ColorPalette plt);
        /// <summary>
        /// 
        /// </summary>
        /// <param name="srcLine"></param>
        /// <param name="dstLine"></param>
        /// <param name="width"></param>
        /// <param name="srcPixBit"></param>
        /// <param name="dstPixBit"></param>
        /// <param name="srcBmp"></param>
        /// <param name="dstBmp"></param>

        public unsafe delegate void ConvertScanLineEvent(IntPtr srcLine, IntPtr dstLine, int width, int srcPixBit, int dstPixBit, Bitmap srcBmp, Bitmap dstBmp);

        private readonly int alpha = 255;
        /// <summary>
        /// 
        /// </summary>
        public BmpAdjuster()
        {

        }

        /// <summary>
        /// 初始化的时候给个alpha值,这样在灰图片的时候可以半透明
        /// </summary>
        /// <param name="alpha"></param>
        public BmpAdjuster(int alpha)
        {
            this.alpha = alpha;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="bmp"></param>
        /// <param name="format"></param>
        /// <param name="PalleteAdjust"></param>
        /// <param name="ConvertScanLine"></param>
        public void AdjustColor(ref Bitmap bmp, PixelFormat format, PaletteAdjustEvent PalleteAdjust, ConvertScanLineEvent ConvertScanLine)
        {
            Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
            Bitmap bmpOut = new Bitmap(bmp.Width, bmp.Height, format);

            bmpOut.Palette = PalleteAdjust(bmpOut.Palette);

            PixelFormat srcFmt = bmp.PixelFormat;
            PixelFormat dstFmt = bmpOut.PixelFormat;
            int srcPixBit = GetPixelSize(srcFmt);
            int dstPixBit = GetPixelSize(dstFmt);

            BitmapData srcData = null;
            BitmapData dstData = null;
            try
            {
                srcData = bmp.LockBits(rect, ImageLockMode.ReadOnly, srcFmt);
                dstData = bmpOut.LockBits(rect, ImageLockMode.WriteOnly, dstFmt);

                unsafe
                {
                    byte* srcLine = (byte*)srcData.Scan0.ToPointer();
                    byte* dstLine = (byte*)dstData.Scan0.ToPointer();
                    for (int L = 0; L < srcData.Height; L++)
                    {
                        ConvertScanLine((IntPtr)srcLine, (IntPtr)dstLine, srcData.Width, srcPixBit, dstPixBit, bmp, bmpOut);

                        srcLine += srcData.Stride;
                        dstLine += dstData.Stride;
                    }
                }
            }
            finally
            {
                bmp.UnlockBits(srcData);
                bmpOut.UnlockBits(dstData);
            }

            bmp = bmpOut;
        }

        internal int GetPixelSize(PixelFormat format)
        {
            switch (format)
            {
                case PixelFormat.Format16bppRgb555: return 16;
                case PixelFormat.Format16bppRgb565: return 16;
                case PixelFormat.Format24bppRgb: return 24;
                case PixelFormat.Format32bppRgb: return 32;
                case PixelFormat.Format1bppIndexed: return 1;
                case PixelFormat.Format4bppIndexed: return 4;
                case PixelFormat.Format8bppIndexed: return 8;
                case PixelFormat.Format16bppArgb1555: return 16;
                case PixelFormat.Format32bppPArgb: return 32;
                case PixelFormat.Format16bppGrayScale: return 16;
                case PixelFormat.Format48bppRgb: return 48;
                case PixelFormat.Format64bppPArgb: return 64;
                case PixelFormat.Canonical: return 32;
                case PixelFormat.Format32bppArgb: return 32;
                case PixelFormat.Format64bppArgb: return 64;
            }
            return 0;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="bmp"></param>
        public unsafe void Monochrome(ref Bitmap bmp)
        {
            AdjustColor(ref bmp, PixelFormat.Format1bppIndexed,
                new PaletteAdjustEvent(SetBlackWhitePallete),
                new ConvertScanLineEvent(ConvertBlackWhiteScanLine));
        }

        ColorPalette SetBlackWhitePallete(ColorPalette plt)
        {
            plt.Entries[0] = Color.Black;
            plt.Entries[1] = Color.White;
            return plt;
        }

        unsafe void ConvertBlackWhiteScanLine(IntPtr srcLine, IntPtr dstLine, int width, int srcPixBit, int dstPixBit, Bitmap srcBmp, Bitmap dstBmp)
        {
            byte* src = (byte*)srcLine.ToPointer();
            byte* dst = (byte*)dstLine.ToPointer();
            int srcPixByte = srcPixBit / 8;
            int x, v, t = 0;

            for (x = 0; x < width; x++)
            {
                v = 28 * src[0] + 151 * src[1] + 77 * src[2];
                t = (t << 1) | (v > 200 * 256 ? 1 : 0);
                src += srcPixByte;

                if (x % 8 == 7)
                {
                    *dst = (byte)t;
                    dst++;
                    t = 0;
                }
            }

            if ((x %= 8) != 7)
            {
                t <<= 8 - x;
                *dst = (byte)t;
            }
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="bmp"></param>
        public void Gray(ref Bitmap bmp)
        {
            AdjustColor(ref bmp, PixelFormat.Format8bppIndexed,
                new PaletteAdjustEvent(SetGrayPallete),
                new ConvertScanLineEvent(ConvertGaryScanLine));
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="plt"></param>
        /// <returns></returns>
        ColorPalette SetGrayPallete(ColorPalette plt)
        {
            //for (int i = plt.Entries.Length - 1; i >= 0; i--)
            //    plt.Entries[i] = Color.FromArgb( i, i, i);
            for (int i = plt.Entries.Length - 1; i >= 0; i--)
                plt.Entries[i] = Color.FromArgb(alpha, i, i, i);
            return plt;
        }

        unsafe void ConvertGaryScanLine(IntPtr srcLine, IntPtr dstLine, int width, int srcPixBit, int dstPixBit, Bitmap srcBmp, Bitmap dstBmp)
        {
            byte* src = (byte*)srcLine.ToPointer();
            byte* dst = (byte*)dstLine.ToPointer();
            int srcPixByte = srcPixBit / 8;

            for (int x = 0; x < width; x++)
            {
                *dst = (byte)((28 * src[0] + 151 * src[1] + 77 * src[2]) >> 8);
                src += srcPixByte;
                dst++;
            }
        }

        #endregion

        /// <summary>
        /// 使图片单色化
        /// </summary>
        /// <param name="pimage"></param>
        /// <returns></returns>
        public static Bitmap MonochromeLockBits(Bitmap pimage)
        {
            Bitmap source;

            // If original bitmap is not already in 32 BPP, ARGB format, then convert
            if (pimage.PixelFormat != PixelFormat.Format32bppArgb)
            {
                source = new Bitmap(pimage.Width, pimage.Height, PixelFormat.Format32bppArgb);
                source.SetResolution(pimage.HorizontalResolution, pimage.VerticalResolution);
                using (Graphics g = Graphics.FromImage(source))
                {
                    g.DrawImageUnscaled(pimage, 0, 0);
                }
            }
            else
            {
                source = pimage;
            }

            // Lock source bitmap in memory
            BitmapData sourceData = source.LockBits(new Rectangle(0, 0, source.Width, source.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);

            // Copy image data to binary array
            int imageSize = sourceData.Stride * sourceData.Height;
            byte[] sourceBuffer = new byte[imageSize];
            Marshal.Copy(sourceData.Scan0, sourceBuffer, 0, imageSize);

            // Unlock source bitmap
            source.UnlockBits(sourceData);

            // Create destination bitmap
            Bitmap destination = new Bitmap(source.Width, source.Height, PixelFormat.Format1bppIndexed);

            // Lock destination bitmap in memory
            BitmapData destinationData = destination.LockBits(new Rectangle(0, 0, destination.Width, destination.Height), ImageLockMode.WriteOnly, PixelFormat.Format1bppIndexed);

            // Create destination buffer
            imageSize = destinationData.Stride * destinationData.Height;
            byte[] destinationBuffer = new byte[imageSize];

            int sourceIndex;
            int destinationIndex;
            int pixelTotal;
            byte destinationValue;
            int pixelValue;
            int height = source.Height;
            int width = source.Width;
            int threshold = 500;

            // Iterate lines
            for (int y = 0; y < height; y++)
            {
                sourceIndex = y * sourceData.Stride;
                destinationIndex = y * destinationData.Stride;
                destinationValue = 0;
                pixelValue = 128;

                // Iterate pixels
                for (int x = 0; x < width; x++)
                {
                    // Compute pixel brightness (i.e. total of Red, Green, and Blue values)
                    pixelTotal = sourceBuffer[sourceIndex + 1] + sourceBuffer[sourceIndex + 2] + sourceBuffer[sourceIndex + 3];
                    if (pixelTotal > threshold)
                    {
                        destinationValue += (byte)pixelValue;
                    }
                    if (pixelValue == 1)
                    {
                        destinationBuffer[destinationIndex] = destinationValue;
                        destinationIndex++;
                        destinationValue = 0;
                        pixelValue = 128;
                    }
                    else
                    {
                        pixelValue >>= 1;
                    }
                    sourceIndex += 4;
                }
                if (pixelValue != 128)
                {
                    destinationBuffer[destinationIndex] = destinationValue;
                }
            }

            // Copy binary image data to destination bitmap
            Marshal.Copy(destinationBuffer, 0, destinationData.Scan0, imageSize);

            // Unlock destination bitmap
            destination.UnlockBits(destinationData);

            // Dispose of source if not originally supplied bitmap
            if (source != pimage)
            {
                source.Dispose();
            }

            // Return
            return destination;
        }
    }
}
