2

I'm trying to create an 8-bit image with a solid background color. It seems like it should be pretty straight forward but the details on the file list it as 32-bit color depth. What am I missing?

    public void CreateImage()
    {
        var bmpOut = new Bitmap(300, 300);
        var g = Graphics.FromImage(bmpOut);
        g.FillRectangle(new SolidBrush(Color.Gray), 0, 0, 300, 300);

        var stream = new MemoryStream();
        bmpOut.Save(stream, GetPngCodecInfo(), GetEncoderParameters());

        bmpOut.Save(@"C:\image.png", GetPngCodecInfo(), GetEncoderParameters());
    }

    public EncoderParameters GetEncoderParameters()
    {
        var parameters = new EncoderParameters();
        parameters.Param[0] = new EncoderParameter(Encoder.ColorDepth, 8);

        return parameters;
    }

    public ImageCodecInfo GetPngCodecInfo()
    {
        var encoders = ImageCodecInfo.GetImageEncoders();

        ImageCodecInfo codecInfo = null;

        foreach (var imageCodecInfo in encoders)
        {
            if (imageCodecInfo.FormatID != ImageFormat.Png.Guid)
                continue;

            codecInfo = imageCodecInfo;
            break;
        }

        return codecInfo;
    }
EricLaw
  • 56,563
  • 7
  • 151
  • 196
DigitalNomad
  • 428
  • 1
  • 3
  • 18

3 Answers3

2

Use this constructor to specify a pixel format : http://msdn.microsoft.com/en-us/library/3z132tat.aspx

Since you cannot create a Graphics from an indexed pixel format, you can only write raw pixels to a 8-bit image.

Bitmap bitmap = new Bitmap(32, 32, PixelFormat.Format8bppIndexed);
var bitmapData = bitmap.LockBits(new Rectangle(Point.Empty, bitmap.Size), ImageLockMode.ReadWrite, bitmap.PixelFormat);
Random random=new Random();
byte[] buffer=new byte[bitmap.Width*bitmap.Height];
random.NextBytes(buffer);
Marshal.Copy(buffer,0,bitmapData.Scan0,buffer.Length);
bitmap.UnlockBits(bitmapData);
bitmap.Save("test.bmp",ImageFormat.Bmp);

You can either use such code on WinForms : http://www.codeproject.com/Articles/17162/Fast-Color-Depth-Change-for-Bitmaps

Or if you can reference this class from WPF it will be much easier : http://msdn.microsoft.com/en-us/library/system.windows.media.imaging.formatconvertedbitmap(v=vs.85).aspx

aybe
  • 15,516
  • 9
  • 57
  • 105
  • 2
    Be careful, though. Specifying the pixel format alone will not be enough as creating an 8-bit indexed image will fail on the next line—creating a `Graphics` from the image. – DocMax Mar 26 '13 at 06:21
  • Creating a Graphics from an indexed format will raise an exception : A Graphics object cannot be created from an image that has an indexed pixel format. – aybe Mar 26 '13 at 13:55
  • 1
    What on earth is that `Random` doing there? – Nyerguds Mar 31 '17 at 18:01
  • @Nyerguds being used to create a random image, I supposed? As a matter of fact, I bumped into this answer looking for a way to visualize my random data – Lelo Apr 11 '17 at 04:07
  • This answer doesn't actually answer the question, by the way. This does not in any way allow a user to determine how to select and paint a specific color. To do that, you need to edit the _color palette_. As far as I can see, the resulting random image from this just uses the colors of the default palette created with an 8-bit image. – Nyerguds Apr 11 '17 at 14:26
  • @Aybe So at the end, how can we get the 8bit bitmap after creating a graphics? – Pablo Gonzalez Dec 11 '18 at 09:01
1

You could also create the image at a higher bit rate and then convert it to 8 bits just before saving. This would allow you to use a Graphics context when creating the image. See this question for suggestions on how to convert to 8 bits:C# - How to convert an Image into an 8-bit color Image?

Community
  • 1
  • 1
Thorsten Dittmar
  • 55,956
  • 8
  • 91
  • 139
0
  • ImageExtensions.cs

    using System.Runtime.InteropServices;
    using System.Linq;
    
    using System.Drawing.Imaging;
    using System.Drawing;
    using System;
    
    public static partial class ImageExtensions {
        public static ColorPalette ToGrayScale(this ColorPalette palette) {
            var entries=palette.Entries;
    
            for(var i=entries.Length; i-->0; entries[i]=entries[i].ToGrayScale())
                ;
    
            return palette;
        }
    
        public static Color ToGrayScale(this Color color, double[] luminance=null) {
            var list=(luminance??new[] { 0.2989, 0.5870, 0.1140 }).ToList();
            var channel=new[] { color.R, color.G, color.B };
            var c=(byte)Math.Round(list.Sum(x => x*channel[list.IndexOf(x)]));
            return Color.FromArgb(c, c, c);
        }
    
        public static Bitmap To8bppIndexed(this Bitmap original) {
            var rect=new Rectangle(Point.Empty, original.Size);
            var pixelFormat=PixelFormat.Format8bppIndexed;
            var destination=new Bitmap(rect.Width, rect.Height, pixelFormat);
    
            using(var source=original.Clone(rect, PixelFormat.Format32bppArgb)) {
                var destinationData=destination.LockBits(rect, ImageLockMode.WriteOnly, pixelFormat);
                var sourceData=source.LockBits(rect, ImageLockMode.ReadOnly, source.PixelFormat);
    
                var destinationSize=destinationData.Stride*destinationData.Height;
                var destinationBuffer=new byte[destinationSize];
    
                var sourceSize=sourceData.Stride*sourceData.Height;
                var sourceBuffer=new byte[sourceSize];
    
                Marshal.Copy(sourceData.Scan0, sourceBuffer, 0, sourceSize);
                source.UnlockBits(sourceData);
    
                destination.Palette=destination.Palette.ToGrayScale();
                var list=destination.Palette.Entries.ToList();
    
                for(var y=destination.Height; y-->0; ) {
                    for(var x=destination.Width; x-->0; ) {
                        var pixelIndex=y*destination.Width+x;
                        var sourceIndex=4*pixelIndex;
    
                        var color=
                            Color.FromArgb(
                                sourceBuffer[0+sourceIndex],
                                sourceBuffer[1+sourceIndex],
                                sourceBuffer[2+sourceIndex],
                                sourceBuffer[3+sourceIndex]
                                ).ToGrayScale();
    
                        destinationBuffer[pixelIndex]=(byte)list.IndexOf(color);
                    }
                }
    
                Marshal.Copy(destinationBuffer, 0, destinationData.Scan0, destinationSize);
                destination.UnlockBits(destinationData);
            }
    
            return destination;
        }
    }
    

Call bmpOut=bmpOut.To8bppIndexed(); before you it save to file.

Ken Kin
  • 4,503
  • 3
  • 38
  • 76
  • There is _no sense_ in converting a palette to grayscale for a new image. Just fill the palette with color values from 00,00,00 to FF,FF,FF. With a palette like that, you are 100% sure to have all gray values, and the 8-bit value to put on the final pixel is simply equal to any of the three color components of the actual grayscaled pixel colour. No more lookups needed. – Nyerguds Apr 11 '17 at 14:32