44

I am getting error:

"A Graphics object cannot be created from an image that has an indexed pixel format."

in function:

public static void AdjustImage(ImageAttributes imageAttributes, Image image)
{
        Rectangle rect = new Rectangle(0, 0, image.Width, image.Height);

        Graphics g = Graphics.FromImage(image);       
        g.InterpolationMode = InterpolationMode.HighQualityBicubic;
        g.DrawImage(image, rect, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, imageAttributes);
        g.Dispose();
}

I would like to ask you, how can I fix it?

Micha
  • 5,117
  • 8
  • 34
  • 47
Krivers
  • 1,986
  • 2
  • 22
  • 45

4 Answers4

50

Refering to this, it can be solved by creating a blank bitmap with the same dimensions and the correct PixelFormat and the draw on that bitmap.

// The original bitmap with the wrong pixel format. 
// You can check the pixel format with originalBmp.PixelFormat
Bitmap originalBmp = (Bitmap)Image.FromFile("YourFileName.gif");

// Create a blank bitmap with the same dimensions
Bitmap tempBitmap = new Bitmap(originalBmp.Width, originalBmp.Height);

// From this bitmap, the graphics can be obtained, because it has the right PixelFormat
using(Graphics g = Graphics.FromImage(tempBitmap))
{
    // Draw the original bitmap onto the graphics of the new bitmap
    g.DrawImage(originalBmp, 0, 0);
    // Use g to do whatever you like
    g.DrawLine(...);
}

// Use tempBitmap as you would have used originalBmp embedded in it
return tempBitmap;
BeeFriedman
  • 1,800
  • 1
  • 11
  • 29
Microsoft DN
  • 9,706
  • 10
  • 51
  • 71
  • 8
    But it will not write on original image. It will create a blank image and will write on that. So finally data is not written on original image. – Banketeshvar Narayan Jan 11 '14 at 14:41
  • 4
    This code needs to be followed by more code that draws the original image on the graphics object like this `Dim rect As New System.Drawing.Rectangle(0, 0, bm.width, bm.height) : g.DrawImage(bm, rect, 0, 0, bm.width, bm.Height, GraphicsUnit.Pixel)` – rg89 Jul 11 '14 at 20:09
  • 4
    The only reason that works is because it makes the new image as high colour. That doesn't actually allow you to draw on the 8bit image... – Nyerguds Nov 22 '16 at 20:50
  • typo at `Bitmap originalBmp = new (Bitmap)Image.FromFile("YourFileName.gif");` new near fromfile giving error. – bh_earth0 Dec 06 '17 at 10:24
  • 1
    I suggest avoiding use Graphic object when bitmap source is indexed format like a grayscale one. It would be better to create the new Bitmap with Clone. `Bitmap Clone(Rectangle rect, PixelFormat format)` – Mauro Raymondi Feb 25 '18 at 13:06
  • Your code `g.DrawImage(originalBmp, 0, 0);` cannot display my image correctly. I have to change it to `g.DrawImage(originalBmp, 0, 0, originalBmp.Width, originalBmp.Height);` – yushulx Apr 01 '20 at 12:20
  • 2
    Unfortunately, as pointed out, this does not allow to use graphics on the original image with it's original color space. So if you have a monochrome (1bpp) hi-res image (like 70k x 40k pixels), the memory size would go through the roof... – Oak_3260548 Jun 23 '21 at 09:32
15

The simplest way is to create a new image like this:

Bitmap EditableImg = new Bitmap(IndexedImg);

It creates a new image exactly like the original was with all its contents.

Bart Vanseer
  • 348
  • 3
  • 6
  • What is `IndexedImg`? – AaA Jun 07 '17 at 08:12
  • If the indexed pixel image has a higher dpi than the default then the editable image embedded in the top corner of editable image and is much smaller – SimonKravis Oct 31 '17 at 01:22
  • 4
    "with all its contents"... except the original colour palette. This creates a 32bppARGB image. It doesn't actually allow editing the 8-bit image. – Nyerguds Apr 16 '18 at 13:20
  • It's reducing the DPI of tiff file, can we do without changing DPI? – sandeep sharma Mar 25 '19 at 13:36
  • @sandeepsharma Simply add a line to set the dpi of the new image object to that of the old image object. `im2.SetResolution(im1.HorizontalResolution, im1.VerticalResolution);` – Nyerguds Jul 29 '19 at 08:37
3

Overall, if you want to work with indexed images and actually preserve their colour depth and palette, this will always mean writing explicit checks and special code for them. Graphics simply can't work with them, because it manipulates colours, and the actual pixels of indexed images contain no colours, just indices.

For anyone still seeing this all these years later... the valid way to paint an image onto an existing (8-bit) indexed image is this:

  • Go over all the pixels of the image you want to paste and, for each colour, find the closest match on the target image's colour palette, and save its index into a byte array.
  • Open the backing bytes array of the indexed image using LockBits, and paste your matched bytes onto it, at the desired location, by looping over the relevant indices using the height and image stride.

It's not an easy task, but it's certainly possible. If the pasted image is also indexed, and contains more than 256 pixels, you can speed up the process by doing the colour matching on the palette instead of on the actual image data, then getting the backing bytes from the other indexed image, and remapping them using the created mapping.

Note that all of this only applies to eight bit. If your image is four-bit or one-bit, the simplest way to handle it is to convert it to 8-bit first so you can handle it as one byte per pixel, and convert it back afterwards.

For more information on that, see How can I work with 1-bit and 4-bit images?

Nyerguds
  • 5,360
  • 1
  • 31
  • 63
3

Though the accepted answer works, it creates a new 32bpp ARGB image from the indexed bitmap.

To manipulate indexed bitmaps directly you can use this library (alert: shameless self promotion). Its GetReadWriteBitmapData extension allows creating a writable managed accessor even for indexed pixel formats.

And then you can use one of the DrawInto methods that can be used similarly to Graphics.DrawImage. Of course, as the target bitmap is indexed, the drawing operation must quantize the pixels using the target palette colors but there are a sort of overloads that can use dithering to preserve more image details.

Usage example (see more examples in the links above):

using (IReadWriteBitmapData indexedTarget = myIndexedBitmap.GetReadWriteBitmapData())
using (IReadableBitmapData source = someTrueColorBitmap.GetReadableBitmapData())
{
    // or DrawIntoAsync if you want to use async-await
    source.DrawInto(indexedTarget, targetRect, OrderedDitherer.Bayer8x8);
}

Image examples:

All images below had been created with PixelFormat.Format8bppIndexed format with the default palette, and a 256x256 icon and an alpha gradient rainbow were drawn on top of each other. Note that blending is used as much as possible with the available palette.

Image Description
8bpp alpha gradient drawn on icon without dithering No dithering
8bpp alpha gradient drawn on icon with ordered dithering Ordered Bayer8x8 dithering
8bpp alpha gradient drawn on icon with error diffusion dithering Floyd-Steinberg error diffusion dithering

Disclaimer: Of course, the library has also some limitations compared to Graphics, for example there are no shape-drawing methods. But in worst case you still can use the accepted answer, and then call the ConvertPixelFormat method in the end if you need to produce an indexed result.

György Kőszeg
  • 17,093
  • 6
  • 37
  • 65