I use the following code for resizing a gif file.
public void Resize(int newWidth, int newHeight)
{
var dimension = new FrameDimension(_image.FrameDimensionsList[0]);
var frameCount = _image.GetFrameCount(dimension);
// load frames
var encoder = new GifBitmapEncoder();
for (var i = 0; i < frameCount; ++i)
{
_image.SelectActiveFrame(dimension, i);
encoder.Frames.Add(BitmapFrame.Create(
new Bitmap(_image, newWidth, newHeight).CreateBitmapSource()));
}
using (var ms = new MemoryStream())
{
encoder.Save(ms);
ms.Position = 0;
_image = new Bitmap(ms);
}
}
public static BitmapSource CreateBitmapSource(this Bitmap bitmap)
{
if (bitmap == null)
throw new ArgumentNullException("bitmap");
lock (bitmap)
{
IntPtr hBitmap = bitmap.GetHbitmap();
return System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
hBitmap,
IntPtr.Zero,
Int32Rect.Empty,
BitmapSizeOptions.FromEmptyOptions());
}
}
If I output it to a filestream, the gif is animated (altough the frames seem a little slower), but if i load it into an image, it doesn't matter wether from the memorystream or loading it from the "working" gif that came from the filestream, it only contains 1 frame.
What am I doing wrong?
Related
I have to convert a BitmapSource into a BitmapImage because of the Interface I am using. If I display the BitmapSource to an Image view, it works. but when I try and convert the BitmapSource into a BitmapImage, I get this error: "The parameter value cannot be less than '3686400'.\r\nParameter name: buffer" string
My PixelFormat is Bgr24
Here is my code :
void GetFrame(BitmapSource image)
{
try
{
BitmapSourceImageChanged?.Invoke(image);
BitmapImage tempImage = new BitmapImage();
//tempImage = image as BitmapImage;
// Frame = tempImage; var temp = image.Format;
int imagePixelHeight = image.PixelHeight;
int imagePixelWidth = image.PixelWidth;
byte[] pixels = new byte[imagePixelHeight * imagePixelWidth];
int stride = (imagePixelWidth * image.Format.BitsPerPixel +7) / 8;
image.CopyPixels(pixels,stride,0);
Frame = BitmapImageFromArray(pixels, image.PixelWidth, image.PixelHeight);
if (Frame == null)
{
int stop = 0;
}
}
catch (Exception err)
{
Error = err.Message;
}
}
BitmapImage BitmapImageFromArray(byte[] transfer, int imageWidth, int imageHeight)
{
BitmapImage image = new BitmapImage();
try
{
using (
// Convert byte array to BitmapImage()
MemoryStream stream = new MemoryStream(transfer))
{
image.BeginInit();
image.DecodePixelWidth = imageWidth;
image.DecodePixelHeight = imageHeight;
image.StreamSource = stream;
image.CacheOption = BitmapCacheOption.OnLoad;
image.EndInit();
}
}
catch (Exception err)
{
}
return image;
}
My code throws the exception at the CopyPixels method.
Is my stride equation not correct?
Your stride calculation is ok, but not the calculation of the buffer size.
The buffer size is the product of the number of scan lines and the bytes per scan line, i.e. the stride:
int stride = (imagePixelWidth * image.Format.BitsPerPixel + 7) / 8;
byte[] pixels = new byte[imagePixelHeight * stride]; // here
However, your code will still not work, since you can not decode a BitmapImage from a raw pixel buffer. Decoding means to read an encoded bitmap frame, for example a JPG or PNG.
In order to decode a BitmapImage, you would have to create an encoded frame first:
public static BitmapImage BitmapSourceToBitmapImage(BitmapSource bitmapSource)
{
var bitmapImage = bitmapSource as BitmapImage;
if (bitmapImage == null)
{
bitmapImage = new BitmapImage();
var encoder = new BmpBitmapEncoder(); // or some other encoder
encoder.Frames.Add(BitmapFrame.Create(bitmapSource));
using (var stream = new MemoryStream())
{
encoder.Save(stream);
stream.Position = 0;
bitmapImage.BeginInit();
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
bitmapImage.StreamSource = stream;
bitmapImage.EndInit();
}
}
return bitmapImage;
}
That said, there should actually never be a need to have a method like the above at all. All properties that are relevant for a bitmap in WPF are declared in the BitmapSource class. The additional properties in BitmapImage are only used during creation, but have no relevance afterwards.
I recently acquired the NuGet Package Nquant.
I plan to use this to reduce the file size of the bitmap and save it into PNG. But I get this error:
The image you are attempting to quantize does not contain a 32 bit ARGB palette. This image has a bit depth of 8 with 256 colors.
Does anyone here has used Nquant? And have you encountered this error and how did you fix it?
My code for your reference:
var bitmap = new Bitmap(width, jbgsize / height, PixelFormat.Format8bppIndexed);
ColorPalette pal = bitmap.Palette;
for (int i = 0; i <= 255; i++)
{
// create greyscale color table
pal.Entries[i] = Color.FromArgb(i, i, i);
}
bitmap.Palette = pal; // you need to re-set this property to force the new ColorPalette
var bitmap_data = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed);
Marshal.Copy(output, 0, bitmap_data.Scan0, output.Length);
bitmap.UnlockBits(bitmap_data);
MemoryStream stream = new MemoryStream();
var quantizer = new WuQuantizer();
using(var bmp = new Bitmap(bitmap))
{
using (var quantized = quantizer.QuantizeImage(bitmap))
{
quantized.Save(stream, ImageFormat.Png);
}
}
byteArray = stream.ToArray();
return byteArray.Concat(output).ToArray();
You can convert your image to Format32bppPArgb and then Quantize it.
This is my working example of that decrease image size for ~3 times.
public static byte[] CompressImageStream(byte[] imageStream)
{
using (var ms = new MemoryStream(imageStream))
using (var original = new Bitmap(ms))
using (var clonedWith32PixelsFormat = new Bitmap(
original.Width,
original.Height,
PixelFormat.Format32bppPArgb))
{
using (Graphics gr = Graphics.FromImage(clonedWith32PixelsFormat))
{
gr.DrawImage(
original,
new Rectangle(0, 0, clonedWith32PixelsFormat.Width, clonedWith32PixelsFormat.Height));
}
using (Image compressedImage = new WuQuantizer().QuantizeImage(clonedWith32PixelsFormat))
{
return ImageToByteArray(compressedImage);
}
}
}
public static byte[] ImageToByteArray(Image image)
{
if (image == null)
{
throw new ArgumentNullException(nameof(image));
}
using (var stream = new MemoryStream())
{
image.Save(stream, ImageFormat.Png);
return stream.ToArray();
}
}
I have a System.Windows.Media.Drawing object that I am wanting to convert into a Bitmap object, and then from there extract the bytes that represent the image. I've looked about the internet and I can't seem to find how to do what I need, so any help would be appreciated.
So I finally found a way to convert a System.Windows.Media.Drawing object to a System.Drawing.Bitmap object, and from that get a byte[] object representing the image data. The following is not pretty but does actually work.
public static byte[] DrawingToBytes(Drawing drawing)
{
DrawingVisual visual = new DrawingVisual();
using (DrawingContext context = visual.RenderOpen())
{
// If using the BitmapEncoder uncomment the following line to get a white background.
// context.DrawRectangle(Brushes.White, null, drawing.bounds);
context.DrawDrawing(drawing);
}
int width = (int)(drawing.Bounds.Width)
int height = (int)(drawing.Bounds.Height)
Bitmap bmp = new Bitmap(width, height);
Bitmap bmpOut;
using (Graphics g = Graphics.FromImage(bmp))
{
g.Clear(System.Drawing.Color.White);
RenderTargetBitmap rtBmp = new RenderTargetBitmap(width, height,
bmp.HorizontalResolution,
bmp.VerticalResolution,
PixelFormats.Pbgra32);
rtBmp.Render(visual);
// Alternative using BmpBitmapEncoder, use in place of what comes after if you wish.
// MemoryStream stream = new MemoryStream();
// BitmapEncoder encoder = new BmpBitmapEncoder();
// encoder.Frames.Add(BitmapFrame.Create(rtBmp));
// encoder.save(stream);
int stride = width * ((rtBmp.Format.BitsPerPixel + 7) / 8);
byte[] bits = new byte[height * stride];
bitmapSource.CopyPixels(bits, stride, 0);
unsafe
{
fixed (byte* pBits = bits)
{
IntPtr ptr = new IntPtr(pBits);
bmpOut = new Bitmap(width, height, stride,
System.Drawing.Imaging.PixelFormat.Format32bppPArgb, ptr);
}
}
g.DrawImage(bmpOut, 0, 0, bmp.Width, bmp.Height);
}
byte[] bytes;
using (MemoryStream ms = new MemoryStream())
{
bmp.Save(ms, ImageFormat.bmp);
data = ms.ToArray();
}
return bytes;
}
So yeah, it's horrible but it actually works.
You can try that:
byte[] ImageToByte(Image image)
{
ImageConverter converter = new ImageConverter();
return (byte[])converter.ConvertTo(img, typeof(byte[]));
}
This work also for Bitmap.
I am using kinect!!I get a frame and then I convert it to bitmap in order to use Emgucv to convert frame in grayscale then convert bitmap to bitmpa source in order to show in window!I am usign C# visual studio WPF!But my program consume much CPU usage and in case the video is frozen for seconds!!I guess that is the conversion bitmpa source to bitmap and viceverse
byte[] colorData = null;
WriteableBitmap colorImageBitmap = null;
void myKinect_ColorFrameReady(object sender, ColorImageFrameReadyEventArgs e)
{
using (ColorImageFrame colorFrame = e.OpenColorImageFrame())
{
if (colorFrame == null) return;
if (colorData == null)
colorData = new byte[colorFrame.PixelDataLength];
colorFrame.CopyPixelDataTo(colorData);
if (colorImageBitmap == null)
{
this.colorImageBitmap = new WriteableBitmap(
colorFrame.Width,
colorFrame.Height,
96, // DpiX
96, // DpiY
PixelFormats.Bgr32,
null);
}
this.colorImageBitmap.WritePixels(
new Int32Rect(0, 0, colorFrame.Width, colorFrame.Height),
colorData, // video data
colorFrame.Width * colorFrame.BytesPerPixel, // stride,
0 // offset into the array - start at 0
);
Image<Gray, Byte> My_Image = new Image<Gray, byte>(BitmapFromSource(colorImageBitmap));
kinectVideo.Source = ToBitmapSource(My_Image);
}
}
private System.Drawing.Bitmap BitmapFromSource(BitmapSource bitmapsource)
{
System.Drawing.Bitmap bitmap;
using (MemoryStream outStream = new MemoryStream())
{
BitmapEncoder enc = new BmpBitmapEncoder();
enc.Frames.Add(BitmapFrame.Create(bitmapsource));
enc.Save(outStream);
bitmap = new System.Drawing.Bitmap(outStream);
}
return bitmap;
}
[DllImport("gdi32")]
private static extern int DeleteObject(IntPtr o);
public static BitmapSource ToBitmapSource(IImage image)
{
using (System.Drawing.Bitmap source = image.Bitmap)
{
IntPtr ptr = source.GetHbitmap(); //obtain the Hbitmap
BitmapSource bs = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
ptr,
IntPtr.Zero,
Int32Rect.Empty,
System.Windows.Media.Imaging.BitmapSizeOptions.FromEmptyOptions());
DeleteObject(ptr); //release the HBitmap
return bs;
}
}
}
Instead of using Emgucv to do the greyscaling...which means you have to create a GDI+ Bitmap (System.Drawing.Bitmap) pass it to Emgucv, then convert it back from the GDI+ Bitmap to a BitmapSource, you could use FormatConvertedBitmap to keep it in the WPF world.
void myKinect_ColorFrameReady(object sender, ColorImageFrameReadyEventArgs e)
{
using (ColorImageFrame colorFrame = e.OpenColorImageFrame())
{
if (colorFrame == null) return;
if (colorData == null)
colorData = new byte[colorFrame.PixelDataLength];
colorFrame.CopyPixelDataTo(colorData);
if (colorImageBitmap == null)
{
this.colorImageBitmap = new WriteableBitmap(
colorFrame.Width,
colorFrame.Height,
96, // DpiX
96, // DpiY
PixelFormats.Bgr32,
null);
}
this.colorImageBitmap.WritePixels(
new Int32Rect(0, 0, colorFrame.Width, colorFrame.Height),
colorData, // video data
colorFrame.Width * colorFrame.BytesPerPixel, // stride,
0 // offset into the array - start at 0
);
kinectVideo.Source = new FormatConvertedBitmap(colorImageBitmap, PixelFormats.Gray32Float, null, 0);
}
}
http://www.shujaat.net/2010/08/wpf-image-format-conversion-including.html
Another option is to use a pixel shader that applies a greyscale effect on your "kinectVideo" element (which is presumably an Image element to display the frame).
http://bursjootech.blogspot.co.uk/2008/06/grayscale-effect-pixel-shader-effect-in.html
I'm trying to rotate the image.. I have a pictureBox 369x276. But when I rotate, this size decrease.
The pictureBox sizeMode is PictureBoxSizeMode.StretchImage
here is my code:
Bitmap oldBitmap = (Bitmap)pictureBox1.Image;
float angle = 90;
var newBitmap = new Bitmap(oldBitmap.Width, oldBitmap.Height);
var graphics = Graphics.FromImage(newBitmap);
graphics.TranslateTransform((float)oldBitmap.Width / 2, (float)oldBitmap.Height / 2);
graphics.RotateTransform(angle);
graphics.TranslateTransform(-(float)oldBitmap.Width / 2, -(float)oldBitmap.Height / 2);
graphics.DrawImage(oldBitmap, new Point(0, 0));
pictureBox1.Image = newBitmap;
Just use RotateFlip:
Bitmap oldBitmap = (Bitmap)pictureBox1.Image;
oldBitmap.RotateFlip(RotateFlipType.Rotate90FlipNone);
pictureBox1.Image = oldBitmap;
As #Dan-o has pointed out, this allows a rotation of any of the degree's in the System.Drawing.RotateFlipType enum.
To rotate a Bitmap any angle without losing the size, you could do the following, but it's a bit convoluted!
One - Add the WriteableBitmapEx library to your project
Two - Add the XAML, WindowsBase and PresentationCore libraries to your project
Three - Use the following to rotate your Bitmap any amount of degrees:
class Program
{
static void Main(string[] args)
{
Bitmap oldBitmap = (Bitmap)pictureBox1.Image;;
var bitmapAsWriteableBitmap = new WriteableBitmap(BitmapToBitmapImage(oldBitmap));
bitmapAsWriteableBitmap.RotateFree(23);
var rotatedImageAsMemoryStream = WriteableBitmapToMemoryStream(bitmapAsWriteableBitmap);
oldBitmap = new Bitmap(rotatedImageAsMemoryStream);
}
public static BitmapImage BitmapToBitmapImage(Bitmap bitmap)
{
var memStream = BitmapToMemoryStream(bitmap);
return MemoryStreamToBitmapImage(memStream);
}
public static MemoryStream BitmapToMemoryStream(Bitmap image)
{
var memoryStream = new MemoryStream();
image.Save(memoryStream, ImageFormat.Bmp);
return memoryStream;
}
public static BitmapImage MemoryStreamToBitmapImage(MemoryStream ms)
{
ms.Position = 0;
var bitmap = new BitmapImage();
bitmap.BeginInit();
bitmap.StreamSource = ms;
bitmap.CacheOption = BitmapCacheOption.OnLoad;
bitmap.EndInit();
bitmap.Freeze();
return bitmap;
}
private static MemoryStream WriteableBitmapToMemoryStream(WriteableBitmap writeableBitmap)
{
var ms = new MemoryStream();
var encoder = new JpegBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(writeableBitmap));
encoder.Save(ms);
return ms;
}
}
Pain in the ass, but works!
The smaller image size is to be expected. I've never figured out why, but the Graphics.DrawImage really only works if you provide it not only a start location, but also a size. One of the overloads allows you to include size.