C# Resizing Bitmap without changing the Pixelformat - c#

I have a bitmap in BGR format and resolution 1920*1200 and want to scale it without changing the pixelformat.
private Bitmap rescale(Size size, Bitmap origin)
{
Bitmap scaled = new Bitmap(origin, size);
return scaled;
}
the problem is, that scaled turns out to be a RGBA bitmap(all alphas at 255), which is not only useless to me, but also troubles me as later I am doing a AbsDiff (from EMGU) on different images and then, the alpha value always turns out zero..
Is there a way to not change the pixelformat when scaling or to do AbsDiff without the alpha-values? Because like this, when I load the image later, it is invisible..

Bitmap scaled = new Bitmap(origin, size);
There are a lot of implicit assumptions built into that constructor call. You'll get:
A bitmap with the 32bppPArgb pixel format. Meant to help the programmer fall into the pit of success, it is the most optimal pixel format on modern PCs. Compatible with the pixel format of the video adapter frame buffer, it can be blitted without any conversion. It is ten times faster than all the other ones.
The resolution is set to the video adapter DPI. This is usually a bit less optimal although it is pretty hard to argue that it should use the resolution of the source image after rescaling it. You might want to modify that.
A transparent background. That matters if the source bitmap has transparency or has pixels with the alpha channel set to a value < 255. Usually fine, if the source bitmap was transparent then the new one will be as well. Not so fine with alpha, rescaling the bitmap is pretty likely to affect that negatively. YMMV.
Bilinear interpolation of the source image. That is fairly modest, you might favor InterpolationMode.HighQualityBicubic for a better result, especially when you shrink it by more than 50%. Or NearestNeighbor if speed is your concern or the source image is very small and you enlarge it with the intention to keep the pixels visible as-is.
Clearly you are unhappy, the first bullet is the source of your complaint. Writing it out with all details tweakable:
public static Bitmap RescaleImage(Image source, Size size) {
// 1st bullet, pixel format
var bmp = new Bitmap(size.Width, size.Height, source.PixelFormat);
// 2nd bullet, resolution
bmp.SetResolution(source.HorizontalResolution, source.VerticalResolution);
using (var gr = Graphics.FromImage(bmp)) {
// 3rd bullet, background
gr.Clear(Color.Transparent);
// 4th bullet, interpolation
gr.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
gr.DrawImage(source, new Rectangle(0, 0, size.Width, size.Height));
}
return bmp;
}
-

The (admittedly sort of ugly) way I would do this is to create a new Bitmap with the original Pixelformat and the new Size and draw the original bitmap onto it (untested):
private Bitmap rescale(Size size, Bitmap origin)
{
Bitmap rescaled = new Bitmap(size.Width, size.Height, origin.PixelFormat);
using(Graphics g = Graphics.FromImage(rescaled))
{
g.DrawImage(origin, 0, 0, size.Width, size.Height);
}
return rescaled;
}

This seems to be what you need:
http://www.codeproject.com/Tips/552141/Csharp-Image-resize-convert-and-save
and
Bitmap(Image, Size) constructor
public Bitmap(
Image original,
Size newSize
)
This is a constructor that takes a new size. The image is automatically resized to that size.

Related

A Graphics object cannot be created from an image that has an indexed pixel format [duplicate]

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?
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;
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.
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?
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
No dithering
Ordered Bayer8x8 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.

GDI+ generic error saving bitmap created from memory using LockBits

The GDI+ generic error when saving a bitmap is obviously a common problem according to my research here on SO and the web. Given following simplified snippet:
byte[] bytes = new byte[2048 * 2048 * 2];
for (int i = 0; i < bytes.Length; i++)
{
// set random or constant pixel data, whatever you want
}
Bitmap bmp = new Bitmap(2048, 2048, PixelFormat.Format16bppGrayScale);
BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, 2048, 2048), ImageLockMode.ReadWrite, bmp.PixelFormat);
System.Runtime.InteropServices.Marshal.Copy(bytes, 0, bmpData.Scan0, 8388608);
bmp.UnlockBits(bmpData);
bmp.Save(#"name.bmp");
This results in the 0x80004005 generic error. The usual reason for this are said to be locks on components, but I do not see anything here I. Am I just blind? The path I am saving to exists, of course, only a empty bmp file is created (0B).
Background: I am getting pixel data from a camera driver that I transfer to .NET using a C++/CLI wrapper, so the Bitmap object above is returned by a function call. But since this small example already fails, I guess that there is nothing wrong with the adapter.
Any suggestions are highly appreciated!
Bitmap bmp = new Bitmap(2048, 2048, PixelFormat.Format16bppGrayScale);
GDI+ exceptions are rather poor, you'll have little hope to diagnose the two mistakes. The lesser one is your Save() call, it doesn't specify the ImageFormat you want to save. The default is PNG, not BMP as you hoped.
But the core one is PixelFormat.Format16bppGrayScale. When GDI+ was designed, long before .NET came around, everybody was still using CRTs instead of LCD monitors. CRTs were quite good at displaying a gamut of colors. Although good, there were no mainstream CRTs yet that were capable of display 65536 distinct gray colors. Most of all restricted by the DAC in the video adapter, the chip that converts the digital pixel value to an analog signal for the CRT. A DAC that can convert with 16-bit accuracy at 100 MHz or more wasn't technologically feasible yet. Microsoft gambled on display technology improving to make that possible someday so specified Format16bppGrayScale as a pixel format that might someday be available.
That did not happen. Rather the opposite, LCDs are significantly worse at color resolution. Typical LCD panels can only resolve 6 bits of a color rather than the 8 bits available from the pixel format. Getting to 16-bit color resolution is going to require a significant technological break-through.
So they guessed wrong and, since the pixel format isn't useful, GDI+ doesn't actually have an image encoder that can write a 16bpp grayscale image format. Kaboom when you try to save it to disk, regardless of the ImageFormat you pick.
16bpp grayscale is actually used, radiological imaging uses that pixel format. With very expensive displays to make it actually useful. Such equipment however invariable uses a custom image format to go with that, DICOM is the usual choice. GDI+ doesn't have a codec for it.
You'll need to go shopping for a library that supports the image format that your customer wants. Lead Tools is the thousand pound gorilla in that product segment.
PixelFormat.Format32bppArgb seems to work for me on Ubuntu 20 using GDI.
var bitmapdata = bitmap.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly,
PixelFormat.Format32bppArgb);
The error I was getting was
System.ArgumentException: 'Parameter is not valid.'
at System.Drawing.SafeNativeMethods.Gdip.CheckStatus(Int32 status)
at System.Drawing.Bitmap.LockBits(Rectangle rect, ImageLockMode flags, PixelFormat format, BitmapData bitmapData)
at System.Drawing.Bitmap.LockBits(Rectangle rect, ImageLockMode flags, PixelFormat format)

Graphics on indexed image

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?
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;
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.
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?
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
No dithering
Ordered Bayer8x8 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.

Creating a custom Image display in WPF as an optimization mechanism

I am doing some image processing on a custom class representing 16 bit gray-scale images.
The intensity of the pixels is stored in a single dimension ushort array: ushort[] data
I also have the width, the height, the dpi, the stride, if necessary.
My motivation is the following: I show the results of certain operations quite fast, but the conversion from the array to the bitmapsource to the image object is too lengthy, so I was thinking of an Image object which drew its "source" directly from the array. I could thus write a method "update()" instead of doing multiple conversions.
1/ Is this possible?
2/ Would it be faster?
3/ How would I go about doing it?
The way I currently draw the image is with the following code (there's a bit more to that, but essetially that the heart of it)
BitmapSource bmps = BitmapSource.Create(Width, Height, Dpi, Dpi, PixelFormats.Gray16, null,
data, stride);
image.Source=bmps;
Thank you!
Instead of using a separate array to store pixels and then creating a BitmapSource to show the results, I think it would be better using a WriteableBitmap, this way you can store pixel data (16-bit greyscale values) directly in its BackBuffer. Thus you can code something like this:
// You create "bmp" just once and then update its content when needed
var bmp = new WriteableBitmap(640, 480, 96, 96, PixelFormats.Gray16, null);
var imgRect = new Int32Rect(0, 0, bmp.PixelWidth, bmp.PixelHeight);
Then you update the image:
bmp.Lock();
ApplySomeFilter(bmp.BackBuffer, ImageFilter.Blur); // this is just an example ;)
bmp.AddDirtyRect(imgRect);
bmp.Unlock();
The ApplySomeFilter method can use unsafe code to modify pixel data of the WriteableBitmap.
For example, if ApplySomeFilter is defined like this:
unsafe public void ApplySomeFilter(void* imgBuffer, ImageFilter filter)
{
// code that modifies pixels goes here
}
then you can call it this way:
ApplySomeFilter(bmp.BackBuffer.ToPointer(), ImageFilter.Blur);

Increase the resolution (dpi) of an image

I'm not sure how feasible this will be without some thirdparty libraries, but here goes:
I have an image, 450x900 in size, which im trying to print.
The problem is, the method I'm using to print is sending raw data to the printer.
The resolution of the image is 96dpix96dpi, the printer runs at 203dpi.
So... the image comes out small.
I need to increase the dpi of the image to print it at its 'real' size.
Bitmap b0 = LoadBitmap();
//I need to rotate it because for some odd reason it prints backwards and upside down.
b0.RotateFlip(RotateFlipType.Rotate180FlipX);
//Set a new resolution, 203dpi
b0.SetResolution(203, 203);
//I need to save and reload the bitmap, because RotateFlip compresses it.
//(annoying as hell, took me ages to figure out why it wasn't working.)
Stream imgStream = new MemoryStream();
b0.Save(imgStream, ImageFormat.Bmp);
b0 = new Bitmap(imgStream);
//get my byte array
ImageConverter converter = new ImageConverter();
byte[] imageData = (byte[])converter.ConvertTo(b0, typeof(byte[]));
So, fairly straight forward.
But SetResolution(...) doesn't actually seem to do anything.
The image prints exactly the same size, and the resultant byte array is exactly the same size.
So I'm trying to work out what it is actually doing.
But I guess it would need to pad out all the image data with extra pixels to do what I want it to?
If this isn't a practical method, is there a simple stretch method or similar I can use to get the desired effect?
Why not scale your image to be bigger:
System.Drawing.Bitmap b0 = LoadBitmap();
double scale = 203/96;
int width = (int)(b0.Width * scale);
int height = (int)(b0.Height * scale);
System.Drawing.Bitmap bmpScaled = new System.Drawing.Bitmap(b0,width, height);
You have to understand that simply changing resolution (dpi) of a bitmap does nothing to its pixel content. For bitmap "resolution" is only a metadata. What really matter is size in pixels.
So if you want your picture to be bigger in print you'll have to either change printer resolution or scale your image (for example, using Grzegorz's method).

Categories

Resources