Zmiana bitmapy kolorowej w czarno-białą

0

Jak zrobić z normalnej bitmapy wczytanej do programu czarno-białą(nie w skali szarości - dwa kolory - czarny i biały). Nie mogę nic w googlach na ten temat znaleźć.

1

Twój słownik na dzisiaj: monochromatic, monochrome, black and white, bitmap, C#, google.

0

Udało się znaleźć, zastosowałem i... nic z tego. Obrazek co prawda stał się czarno-biały, ale nie zmienił swojego rozmiaru(ciągle waży 60KB), a wydaje mi się, że informacja o kolorze to sporo danych. Oto kod, który znalazłem i zastosowałem:

Klasa Monochromatic:

/// <summary>
        /// Copies a bitmap into a 1bpp/8bpp bitmap of the same dimensions, fast
        /// </summary>
        /// <param name="b">original bitmap</param>
        /// <param name="bpp">1 or 8, target bpp</param>
        /// <returns>a 1bpp copy of the bitmap</returns>
        public static System.Drawing.Bitmap CopyToBpp(System.Drawing.Bitmap b, int bpp)
        {
            if (bpp != 1 && bpp != 8) throw new System.ArgumentException("1 or 8", "bpp");

            // Plan: built into Windows GDI is the ability to convert
            // bitmaps from one format to another. Most of the time, this
            // job is actually done by the graphics hardware accelerator card
            // and so is extremely fast. The rest of the time, the job is done by
            // very fast native code.
            // We will call into this GDI functionality from C#. Our plan:
            // (1) Convert our Bitmap into a GDI hbitmap (ie. copy unmanaged->managed)
            // (2) Create a GDI monochrome hbitmap
            // (3) Use GDI "BitBlt" function to copy from hbitmap into monochrome (as above)
            // (4) Convert the monochrone hbitmap into a Bitmap (ie. copy unmanaged->managed)

            int w = b.Width, h = b.Height;
            IntPtr hbm = b.GetHbitmap(); // this is step (1)
            //
            // Step (2): create the monochrome bitmap.
            // "BITMAPINFO" is an interop-struct which we define below.
            // In GDI terms, it's a BITMAPHEADERINFO followed by an array of two RGBQUADs
            BITMAPINFO bmi = new BITMAPINFO();
            bmi.biSize = 40;  // the size of the BITMAPHEADERINFO struct
            bmi.biWidth = w;
            bmi.biHeight = h;
            bmi.biPlanes = 1; // "planes" are confusing. We always use just 1. Read MSDN for more info.
            bmi.biBitCount = (short)bpp; // ie. 1bpp or 8bpp
            bmi.biCompression = BI_RGB; // ie. the pixels in our RGBQUAD table are stored as RGBs, not palette indexes
            bmi.biSizeImage = (uint)(((w + 7) & 0xFFFFFFF8) * h / 8);
            bmi.biXPelsPerMeter = 1000000; // not really important
            bmi.biYPelsPerMeter = 1000000; // not really important
            // Now for the colour table.
            uint ncols = (uint)1 << bpp; // 2 colours for 1bpp; 256 colours for 8bpp
            bmi.biClrUsed = ncols;
            bmi.biClrImportant = ncols;
            bmi.cols = new uint[256]; // The structure always has fixed size 256, even if we end up using fewer colours
            if (bpp == 1) { bmi.cols[0] = MAKERGB(0, 0, 0); bmi.cols[1] = MAKERGB(255, 255, 255); }
            else { for (int i = 0; i < ncols; i++) bmi.cols[i] = MAKERGB(i, i, i); }
            // For 8bpp we've created an palette with just greyscale colours.
            // You can set up any palette you want here. Here are some possibilities:
            // greyscale: for (int i=0; i<256; i++) bmi.cols[i]=MAKERGB(i,i,i);
            // rainbow: bmi.biClrUsed=216; bmi.biClrImportant=216; int[] colv=new int[6]{0,51,102,153,204,255};
            //          for (int i=0; i<216; i++) bmi.cols[i]=MAKERGB(colv[i/36],colv[(i/6)%6],colv[i%6]);
            // optimal: a difficult topic: http://en.wikipedia.org/wiki/Color_quantization
            // 
            // Now create the indexed bitmap "hbm0"
            IntPtr bits0; // not used for our purposes. It returns a pointer to the raw bits that make up the bitmap.
            IntPtr hbm0 = CreateDIBSection(IntPtr.Zero, ref bmi, DIB_RGB_COLORS, out bits0, IntPtr.Zero, 0);
            //
            // Step (3): use GDI's BitBlt function to copy from original hbitmap into monocrhome bitmap
            // GDI programming is kind of confusing... nb. The GDI equivalent of "Graphics" is called a "DC".
            IntPtr sdc = GetDC(IntPtr.Zero);       // First we obtain the DC for the screen
            // Next, create a DC for the original hbitmap
            IntPtr hdc = CreateCompatibleDC(sdc); SelectObject(hdc, hbm);
            // and create a DC for the monochrome hbitmap
            IntPtr hdc0 = CreateCompatibleDC(sdc); SelectObject(hdc0, hbm0);
            // Now we can do the BitBlt:
            BitBlt(hdc0, 0, 0, w, h, hdc, 0, 0, SRCCOPY);
            // Step (4): convert this monochrome hbitmap back into a Bitmap:
            System.Drawing.Bitmap b0 = System.Drawing.Bitmap.FromHbitmap(hbm0);
            //
            // Finally some cleanup.
            DeleteDC(hdc);
            DeleteDC(hdc0);
            ReleaseDC(IntPtr.Zero, sdc);
            DeleteObject(hbm);
            DeleteObject(hbm0);
            //
            return b0;
        }

        [System.Runtime.InteropServices.DllImport("gdi32.dll")]
        public static extern bool DeleteObject(IntPtr hObject);

        [System.Runtime.InteropServices.DllImport("user32.dll")]
        public static extern int InvalidateRect(IntPtr hwnd, IntPtr rect, int bErase);

        [System.Runtime.InteropServices.DllImport("user32.dll")]
        public static extern IntPtr GetDC(IntPtr hwnd);

        [System.Runtime.InteropServices.DllImport("gdi32.dll")]
        public static extern IntPtr CreateCompatibleDC(IntPtr hdc);

        [System.Runtime.InteropServices.DllImport("user32.dll")]
        public static extern int ReleaseDC(IntPtr hwnd, IntPtr hdc);

        [System.Runtime.InteropServices.DllImport("gdi32.dll")]
        public static extern int DeleteDC(IntPtr hdc);

        [System.Runtime.InteropServices.DllImport("gdi32.dll")]
        public static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj);

        [System.Runtime.InteropServices.DllImport("gdi32.dll")]
        public static extern int BitBlt(IntPtr hdcDst, int xDst, int yDst, int w, int h, IntPtr hdcSrc, int xSrc, int ySrc, int rop);
        static int SRCCOPY = 0x00CC0020;

        [System.Runtime.InteropServices.DllImport("gdi32.dll")]
        static extern IntPtr CreateDIBSection(IntPtr hdc, ref BITMAPINFO bmi, uint Usage, out IntPtr bits, IntPtr hSection, uint dwOffset);
        static uint BI_RGB = 0;
        static uint DIB_RGB_COLORS = 0;
        [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
        public struct BITMAPINFO
        {
            public uint biSize;
            public int biWidth, biHeight;
            public short biPlanes, biBitCount;
            public uint biCompression, biSizeImage;
            public int biXPelsPerMeter, biYPelsPerMeter;
            public uint biClrUsed, biClrImportant;
            [System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.ByValArray, SizeConst = 256)]
            public uint[] cols;
        }

        static uint MAKERGB(int r, int g, int b)
        {
            return ((uint)(b & 255)) | ((uint)((r & 255) << 8)) | ((uint)((g & 255) << 16));
        }
    } 

a to już dalsze użycie:

 
Bitmap saveMonochromatic = Monochromatic.CopyToBpp(screen, 1);
            

Tak jak mówiłem, kolory z obrazka znikają ale nie ma najmniejszej zmiany rozmiaru...

Pojawił się też drugi problem, chcę zmniejszyć rozdzielczość tego obrazka, znalazłem już odpowiednią metodę, ale nie działa(rozdzielczość się nie zmienia):

saveMonochromatic.SetResolution(640, 480); 

Co z tym zrobić?

1

Pewnie mapę bitową zapisujesz jako 24-bitową. Musisz zapisać bmp z encoderem i podać color depth równe 1.

Do skalowania obrazka możesz użyć klasy Graphics.

0

Spróbowałem, problem w tym, że mam już jeden parametr, Quality. Wygląda to tak:

 myEncoder = Encoder.Quality;
            myEncoderParameter = new EncoderParameter(myEncoder, 10L);
            myEncoderParameters.Param[0] = myEncoderParameter;
            jgpEncoder = GetEncoder(ImageFormat.Jpeg);
(...)
private ImageCodecInfo GetEncoder(ImageFormat format)
        {

            ImageCodecInfo[] codecs = ImageCodecInfo.GetImageDecoders();

            foreach (ImageCodecInfo codec in codecs)
            {
                if (codec.FormatID == format.Guid)
                {
                    return codec;
                }
            }
            return null;
        }
 

Dopisałem parę linijek i wyszło coś takiego:

  myEncoder = Encoder.Quality;
            myEncoderParameter = new EncoderParameter(myEncoder, 10L);
            encColorDepth = Encoder.ColorDepth;
            myEncoderParameter2 = new EncoderParameter(encColorDepth, 1L);
            myEncoderParameters.Param[0] = myEncoderParameter;
            myEncoderParameters.Param[1] = myEncoderParameter2;
            jgpEncoder = GetEncoder(ImageFormat.Jpeg);

a potem użycie:

 saveMonochromatic.Save(path, jgpEncoder, myEncoderParameters);

A głębia ciągle 24bit...

0
public static Bitmap ConvertTo1bpp(Image srcImage)
{
    Bitmap tmpBmp = new Bitmap(srcImage);

    return tmpBmp.Clone(new RectangleF(0, 0, srcImage.Width, srcImage.Height), PixelFormat.Format1bppIndexed);
}

Niby w szczegółach pokazuje głębie w bitach: 24, ale rozmiar pliku jest dobry.

var encParams = new EncoderParameters(2);

encParams.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 100L);
encParams.Param[1] = new EncoderParameter(System.Drawing.Imaging.Encoder.ColorDepth, 1L);
0
 screen = screen.Clone(new RectangleF(0, 0, screen.Width, screen.Height), PixelFormat.Format1bppIndexed);

OutOfMemoryException.

0

Ok, nie wiem o co chodziło, ale znalazłem taką klasę na necie, która daje specjalną metodę do takiej konwersji i działa, szczegóły ciągle 24bit, ale rozmiar adekwatny do jakości. Dzięki wszystkim.

Wrrróć za wcześnie się ucieszyłem nie działa. Już tracę na to nerwy 3 godziny dziś nad tym siedzę i nic.

screen = ImageUtilities.ResizeImage(screen, 1024, 768);
            screen = Monochromatic.CopyToBpp(screen, 1);
            screen.Save(path, jpgEncoder, encParams); 

Problem musi być w zapisie, te cholerne 24bit ciągle robią problem. Tu encParams i jpgEncoder:

        Encoder encQuality, encColorDepth;
        EncoderParameter encQualityParam, encColorDepthParam;
        EncoderParameters encParams = new EncoderParameters(2);
        ImageCodecInfo jpgEncoder;

(...)

encQuality = Encoder.Quality;
            encQualityParam = new EncoderParameter(encQuality, 10L);
            encParams.Param[0] = encQualityParam;
            encColorDepth = Encoder.ColorDepth;
            encColorDepthParam = new EncoderParameter(encColorDepth, 1L);
            encParams.Param[1] = encColorDepthParam;
            jpgEncoder = GetEncoderInfo("image/jpeg");

(...)

private static ImageCodecInfo GetEncoderInfo(String mimeType)
        {
            int j;
            ImageCodecInfo[] encoders;
            encoders = ImageCodecInfo.GetImageEncoders();
            for (j = 0; j < encoders.Length; ++j)
            {
                if (encoders[j].MimeType == mimeType)
                    return encoders[j];
            }
            return null;
        }
   
0
    public static class BitmapExtensions
    {
        public static void SaveAs(this Bitmap bmp, string FileName, ImageFormat Format, long ColorDepth)
        {
            ImageCodecInfo imgCodecInfo;
            System.Drawing.Imaging.Encoder myEncoder;
            EncoderParameter epColorDepth;
            EncoderParameters eParams;

            imgCodecInfo = GetEncoder(Format);
            myEncoder = System.Drawing.Imaging.Encoder.ColorDepth;
            eParams = new EncoderParameters(1);

            epColorDepth = new EncoderParameter(myEncoder, ColorDepth);
            eParams.Param[0] = epColorDepth;
            bmp.Save(FileName, imgCodecInfo, eParams);
        }

        public static Bitmap ConvertTo1bpp(Image srcImage)
        {
            Bitmap srcBmp = new Bitmap(srcImage);

            return srcBmp.Clone(new RectangleF(0, 0, srcImage.Width, srcImage.Height), PixelFormat.Format1bppIndexed);
        }

        public static ImageCodecInfo GetEncoder(ImageFormat Format)
        {
            ImageCodecInfo[] codecs = ImageCodecInfo.GetImageDecoders();

            foreach (ImageCodecInfo codec in codecs)
            {
                if (codec.FormatID == Format.Guid)
                {
                    return codec;
                }
            }

            return null;
        }
    }

Użycie:

BitmapExtensions.SaveAs(BitmapExtensions.ConvertTo1bpp(Image.FromFile(@"Temp\Original.png")),
                @"Temp\test.jpg", ImageFormat.Jpeg, 1L);

Metoda SaveAs zawiera w sobie kod pochodzący z: MSDN. Działa doskonale u mnie.

PS Tylko jeden EncoderParameter jest brany pod uwagę, ten w Param[0].

0

Za chwilę postaram się przetestować, problem musi istnieć właśnie w tym, że pod uwagę brany jest Param[0]. Więc dlaczego jest to tablica, skoro "działa" tylko pierwsza pozycja? Wydaje mi się, że będę musiał po prostu dwa razy zapisywać plik - zapisać z obniżoną jakością -> zdjąć kolory -> zapisać z obniżoną głębią.

0

Twój sposób nie działa. OutOfMemoryException i basta a pamięci wolnej opór. Ale tak jak mówiłem mam już do tego klasę i starczy. Chcę tylko to normalnie zapisać, potrzebuję obniżenia jakości i głębi kolorów, dwóch parametrów, skoro EncoderParameters to tablica to musi być na to sposób.

1 użytkowników online, w tym zalogowanych: 0, gości: 1