1
votes

I am trying to add a black banner on the top and bottom of an image. I can add the banner but the pixel format of the resulted bitmap is changed to 32-bit. Is there any way to get an 8-bit bitmap as a result. As mentioned here, If I set the 8-bit pixelFormat in the constructor, creating a graphics will raise an exception. I read that if I convert from 32 to 8, maybe the pixel values will be different than original ones. Don't know if I can create a new bitmap with the desired height and add the black banners's pixels using for loops. Anyone has a better and simple way?

My code is as below:

            Bitmap img = image as Bitmap;

            using (Bitmap bitmap = new Bitmap(img.Width, img.Height + 200*2)) // create blank bitmap of desired size
            using (Graphics graphics = Graphics.FromImage(bitmap))
            {
                SolidBrush brush = new SolidBrush(Color.White);
                graphics.FillRectangle(brush, 0, 0, img.Width,200);
                // draw existing image onto new blank bitmap
                graphics.DrawImage(img, 0, 200, img.Width, img.Height);

                // draw your rectangle below the original image
                graphics.FillRectangle(brush, 0, 200 + img.Height, img.Width, 200);
               // bitmap.Save(@"c:\Test1.bmp");
            }
1
The link you quote shows you the way(s) to go.. - I am tempted to close as a duplicate..TaW

1 Answers

0
votes

Here is a routine that does a FillRectangle on a bmp8bpp bitmap.

You pass in an index which, if positive will be used to put the color into the palette. If you pass in a negative index it will try to find the color and if it doesn't find it it will place the color at the end of the index. This will overwrite whatever color was there before!

void FillIndexedRectangle(Bitmap bmp8bpp, Rectangle rect, Color col, int index)
{
    var pal = bmp8bpp.Palette;
    int idx = -1;
    if (index >= 0) idx = index;
    else
    {
        if (pal.Entries.Where(x => x.ToArgb() == col.ToArgb()).Any())
            idx = pal.Entries.ToList().FindIndex(x => x.ToArgb() == col.ToArgb());
        if (idx < 0) idx = pal.Entries.Length - 1;
    }

    pal.Entries[idx] = col;
    bmp8bpp.Palette = pal;

    var bitmapData = 
        bmp8bpp.LockBits(new Rectangle(Point.Empty, bmp8bpp.Size),
                         ImageLockMode.ReadWrite, bmp8bpp.PixelFormat);
    byte[] buffer=new byte[bmp8bpp.Width*bmp8bpp.Height];

    Marshal.Copy(bitmapData.Scan0, buffer,0, buffer.Length);

    for (int y = rect.Y; y < rect.Bottom; y++)
    for (int x = rect.X; x < rect.Right; x++)
    {
            buffer[x + y * bmp8bpp.Width] = (byte)idx;
    }

    Marshal.Copy(buffer, 0, bitmapData.Scan0,buffer.Length);
    bmp8bpp.UnlockBits(bitmapData);
}

Example calls:

Bitmap img = new Bitmap(200, 200, PixelFormat.Format8bppIndexed);
FillIndexedRectangle(img, new Rectangle(0,0,200, 200), Color.Silver, 21);
FillIndexedRectangle(img, new Rectangle(23, 23, 55, 99), Color.Red, 22);
FillIndexedRectangle(img, new Rectangle(123, 123, 55, 33), Color.Black, 23);
FillIndexedRectangle(img, new Rectangle(1, 1, 123, 22), Color.Orange, 34);
FillIndexedRectangle(img, new Rectangle(27, 27, 16, 12), Color.Black, -1);
img.Save("D:\\__bmp8bpp.png");

Result:

__bmp8bpp

There is room for improvement:

  • All error checking in the lockbits is missing, both wrt pixelformat and rectangle data
  • Adding colors with a more dynamical scheme could be nice instead of adding to the end
  • A scheme for finding the closest color already in the palette could also be nice
  • A scheme for drawing with transparency would also be nice. For this all necessary new colors would have to be determined first; also the tranparency mode.
  • Maybe one should return the index used from the method so the next calls can refer to it..

For other shapes than rectangles one could use a copy routine that first draws them onto a 'normal' 32bpp bitmap and then transfers the pixels to the buffer..

Update: Here are a few lines to add (**) or change (*) to allow drawing unfilled rectangles; stroke 0 fills the rectangle..:

 void FillIndexedRectangle(Bitmap bmp8bpp, Rectangle rect, 
                           Color col, int index, int stroke)  // * 

...
...
Marshal.Copy(bitmapData.Scan0, buffer,0, buffer.Length);
Rectangle ri = rect;                               //**
if (stroke > 0) ri.Inflate(-stroke, -stroke);      //**
for (int y = rect.Y; y < rect.Bottom; y++)
for (int x = rect.X; x < rect.Right; x++)
{
        if (ri == rect || !ri.Contains(x,y))      //**
            buffer[x + y * bmp8bpp.Width] = (byte)idx;
}

Marshal.Copy(buffer, 0, bitmapData.Scan0,buffer.Length);