I am using the below code to transform a bitmap to jpeg. The bitmap is being passed through at 300 dpi (horizontal/vertical resolution) but the CreateBitmapSourcefromHBitmap method always changes the subsequent jpeg to be saved at 96dpi.
Is there any way to set the source to retain the original 300dpi? The dpiX and dpiY values are read only.
Thanks in advance.
public static MemoryStream GetJpgMemoryStream(Bitmap bitMap, int jpgQuality)
{
IntPtr hBitmap = bitMap.GetHbitmap();
try
{
BitmapSource source = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(hBitmap, IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
var jpegBitmapEncoder = new JpegBitmapEncoder();
jpegBitmapEncoder.QualityLevel = jpgQuality;
jpegBitmapEncoder.Frames.Add(BitmapFrame.Create(source));
var jpegStream = new MemoryStream();
jpegBitmapEncoder.Save(jpegStream);
jpegStream.Flush();
return jpegStream;
}
}
The MSDN Forum has a discussion that's similar to your problem. The recommended answer is to not use the Interop, instead use a WriteableBitmap as the BitmapSource for the JPEG.
You have to change the method used for generating the BitmapSource. You can use the method stated in fast converting Bitmap to BitmapSource wpf for generating BitmapSource. Here is the updated code.
BitmapData data = bitMap.LockBits(
new System.Drawing.Rectangle(0,0,bitMap.Width,bitMap.Height),
System.Drawing.Imaging.ImageLockMode.ReadOnly,
bitMap.PixelFormat);
BitmapSource source = BitmapSource.Create(bitMap.Width, bitMap.Height,
bitMap.HorizontalResolution, bitMap.VerticalResolution,
System.Windows.Media.PixelFormats.Bgr24, null,
data.Scan0, data.Stride*bitMap.Height, data.Stride);
bitMap.UnlockBits(data);
var jpegBitmapEncoder = new JpegBitmapEncoder();
jpegBitmapEncoder.QualityLevel = jpgQuality;
jpegBitmapEncoder.Frames.Add(BitmapFrame.Create(source));
var jpegStream = new MemoryStream();
jpegBitmapEncoder.Save(jpegStream);
Related
What I'm trying to do:
Load images from a number of .png files saved to disk (if the file doesn't exist, create a new image and fill it with a given colour)
Add these images to a Canvas (they are the same size as the canvas and are expected to mostly be off screen with a portion overlapping the actual Canvas area)
Draw onto the Canvas using lines, ellipses, etc
(On save) take the Canvas and convert it to a flat image held in memory (let's call it the CanvasImage)
For each image I loaded in step 1, calculate the portion that overlaps the actual Canvas area and copy that portion of the CanvasImage into the appropriate portion of the original image
Save the resulting images to disk, overwriting the existing files
I've had different portions of this working at different times, using code I found by searching for how to do each exact step, but never the full process. I'm mostly struggling with steps 4 to 6 as I don't really understand all the different image-related classes, how you convert between them, and which to use at each step in the process. Though I'm also having some issues with step 1, as I need to be able to overwrite the files at the end.
I've read a lot of the related questions (overwriting images, converting a canvas to an image, copying a portion of an image, etc) and tried the solutions (some of which worked in more simple examples), but I'm struggling to put it all together.
Any help or advice would be greatly appreciated. If there's anything you'd like me to elaborate on I'd be happy to, and if you'd like to see some of the code I can share it (I've just tried a lot of different things so I'm not sure how useful it would be)
I've managed to solve the problem. I'll post the code below for each step I had trouble with in case anyone else will find it useful. The code here is slightly modified to suit the general case.
Step 1: Loading an image from the disk that can be overwritten
public static System.Windows.Controls.Image LoadImage(string filePath)
{
var image = new System.Windows.Controls.Image();
image.Stretch = Stretch.Uniform;
var bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
bitmapImage.CreateOptions = BitmapCreateOptions.IgnoreImageCache;
bitmapImage.UriSource = new Uri(filePath, UriKind.Absolute);
bitmapImage.EndInit();
image.Width = bitmapImage.PixelWidth;
image.Height = bitmapImage.PixelHeight;
image.Source = bitmapImage;
return image;
}
Step 4: Convert canvas to a BitmapImage
public static BitmapImage Convert(Canvas canvas)
{
var sizeRender = new Size(canvas.ActualWidth, canvas.ActualHeight);
var dv = new DrawingVisual();
using (DrawingContext ctx = dv.RenderOpen())
{
VisualBrush vb = new VisualBrush(canvas);
ctx.DrawRectangle(vb, null, new Rect(new Point(), sizeRender));
}
var renderBitmap =
new RenderTargetBitmap(
(int)canvas.ActualWidth,
(int)canvas.ActualHeight,
96d,
96d,
PixelFormats.Pbgra32);
renderBitmap.Render(dv);
BitmapEncoder pngEncoder = new PngBitmapEncoder();
pngEncoder.Frames.Add(BitmapFrame.Create(renderBitmap));
BitmapImage bitmap;
using (var stream = new MemoryStream())
{
pngEncoder.Save(stream);
bitmap = new BitmapImage();
bitmap.BeginInit();
bitmap.StreamSource = stream;
bitmap.CacheOption = BitmapCacheOption.OnLoad;
bitmap.EndInit();
bitmap.Freeze();
}
return bitmap;
}
Step 5: Copy a portion of an image into a different image
public static void SaveImage(Image inputImage, BitmapImage canvasImage, string fileName)
{
... Calculate the portion that overlaps (left, top, width, height) ...
var canvasImageAsBitmap = BitmapConverter.ConvertBitmapImageToBitmap(canvasImage);
var croppingRectangle = new Rectangle(left, top, width, height);
var croppedRegion = canvasImageAsBitmap.Clone(croppingRectangle, canvasImageAsBitmap.PixelFormat);
var image = BitmapConverter.ConvertBitmapImageToBitmap((BitmapImage)inputImage.Source);
var bitmap = new Bitmap((int)inputImage.Width, (int)inputImage.Height);
... Calculate the position of the cropped image in the input image (xPos, yPos) ...
using (var graphics = Graphics.FromImage(bitmap))
{
graphics.DrawImage(image, 0, 0);
graphics.DrawImage(croppedRegion, xPos, yPos);
graphics.Flush();
}
bitmap.Save(fileName);
}
This step relies on the following code to work:
public static Bitmap ConvertBitmapImageToBitmap(BitmapImage bitmapImage)
{
using (MemoryStream memoryStream = new MemoryStream())
{
BitmapEncoder encoder = new BmpBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bitmapImage));
encoder.Save(memoryStream);
Bitmap bitmap = new Bitmap(memoryStream);
return new Bitmap(bitmap);
}
}
Step 6: Overwrite the image file to save in the disk.
This is just the line bitmap.Save(fileName); shown in Step 5, which works due to the implementation of Step 1
The problem:
I receive a YUV_420_888 image from an android device as a byte buffer (simply concatenated the image plane buffers). I know the dimension of the image and I need to display it onto my GUI.
What I have so far:
At the moment I can use only the grayscale Y-plane with the following function:
private BitmapImage GetImageFromBuffer(byte[] imgBuffer)
{
Image<Gray, byte> emguImg = new Image<Gray, byte>(1280, 720);
emguImg.Bytes = imgBuffer;
var img = new BitmapImage();
using (MemoryStream ms = new MemoryStream(emguImg.ToJpegData()))
{
img.BeginInit();
img.CacheOption = BitmapCacheOption.OnLoad;
img.CreateOptions = BitmapCreateOptions.PreservePixelFormat;
img.StreamSource = ms;
img.EndInit();
}
return img;
}
I also have tested a similar code, using Image.ToBitmap() function and copying the intermediate Bitmap to the memory stream, which serves as source for the BitmapImage.
Anyway, I would like to create a BitmapImage of BitmapSource (or any type I can use to display on the GUI) from the incoming byte[].
As far as I could read up on it, I have to create a Mat instance from the byte array, convert it to RGB and then save it to a diplay-able format.
The following code seems to do the trick:
private BitmapImage GetImageFromBuffer(byte[] imgBuffer)
{
unsafe
{
fixed (byte* p = imgBuffer)
{
IntPtr ptr = (IntPtr)p;
Mat yuvMat = new Mat(1080, 1280, Emgu.CV.CvEnum.DepthType.Cv8U, 1, ptr, 1280);
Mat rgbMat = new Mat();
CvInvoke.CvtColor(yuvMat, rgbMat, Emgu.CV.CvEnum.ColorConversion.Yuv420Sp2Rgb);
var img = new BitmapImage();
using (MemoryStream ms = new MemoryStream())
{
img.BeginInit();
rgbMat.Bitmap.Save(ms, ImageFormat.Bmp);
img.CacheOption = BitmapCacheOption.OnLoad;
img.CreateOptions = BitmapCreateOptions.PreservePixelFormat;
img.StreamSource = ms;
img.EndInit();
}
return img;
}
}
}
Maybe someone can confirm, if this is the way to go or comment suggestions for improvements.
I'm tying together two libraries. One only gives output of type System.Windows.Media.Imaging.BitmapSource, the other only accepts input of type System.Drawing.Image.
How can this conversion be performed?
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;
}
This is an alternate technique that does the same thing. The accepted answer works, but I ran into problems with images that had alpha channels (even after switching to PngBitmapEncoder). This technique may also be faster since it just does a raw copy of pixels after converting to compatible pixel format.
public Bitmap BitmapFromSource(System.Windows.Media.Imaging.BitmapSource bitmapsource)
{
//convert image format
var src = new System.Windows.Media.Imaging.FormatConvertedBitmap();
src.BeginInit();
src.Source = bitmapsource;
src.DestinationFormat = System.Windows.Media.PixelFormats.Bgra32;
src.EndInit();
//copy to bitmap
Bitmap bitmap = new Bitmap(src.PixelWidth, src.PixelHeight, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
var data = bitmap.LockBits(new Rectangle(Point.Empty, bitmap.Size), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
src.CopyPixels(System.Windows.Int32Rect.Empty, data.Scan0, data.Height * data.Stride, data.Stride);
bitmap.UnlockBits(data);
return bitmap;
}
Hi I wanna convert binary array to bitmap and show image in a picturebox. I wrote the following code but I got exception that says that the parameter is not valid .
public static Bitmap ByteToImage(byte[] blob)
{
MemoryStream mStream = new MemoryStream();
byte[] pData = blob;
mStream.Write(pData, 0, Convert.ToInt32(pData.Length));
Bitmap bm = new Bitmap(mStream);
mStream.Dispose();
return bm;
}
It really depends on what is in blob. Is it a valid bitmap format (like PNG, BMP, GIF, etc?). If it is raw byte information about the pixels in the bitmap, you can not do it like that.
It may help to rewind the stream to the beginning using mStream.Seek(0, SeekOrigin.Begin) before the line Bitmap bm = new Bitmap(mStream);.
public static Bitmap ByteToImage(byte[] blob)
{
using (MemoryStream mStream = new MemoryStream())
{
mStream.Write(blob, 0, blob.Length);
mStream.Seek(0, SeekOrigin.Begin);
Bitmap bm = new Bitmap(mStream);
return bm;
}
}
Don't dispose of the MemoryStream. It now belongs to the image object and will be disposed when you dispose the image.
Also consider doing it like this
var ms = new MemoryStream(blob);
var img = Image.FromStream(ms);
.....
img.Dispose(); //once you are done with the image.
System.IO.MemoryStream mStrm = new System.IO.MemoryStream(your byte array);
Image im = Image.FromStream(mStrm);
im.Save("image.bmp");
Try this. If you still get any error or exception; please post your bytes which you are trying to convert to image. There should be problem in your image stream....
Can someone please suggest how to save bgr101010 format .tiff file in 32bpp? My code is saving in 48bpp? Basically I want to save tiff a file with 10 bit color depth.
private void Bgr()
{
BitmapImage myBitmapImage = new BitmapImage();
BitmapSource bs = new BitmapImage(new Uri(#"img\android1.png", UriKind.Relative));
int stride = bs.PixelWidth * (bs.Format.BitsPerPixel / 8);
byte[] data = new byte[stride * bs.PixelHeight];
bs.CopyPixels(data, stride, 0);
WriteableBitmap w2Bmp = new WriteableBitmap(bs.PixelWidth, bs.PixelWidth, 96.0, 96.0,PixelFormats.Bgr101010, null);
w2Bmp.WritePixels(
new Int32Rect(0, 0, bs.PixelWidth, bs.PixelHeight),
data, stride, 0);
image1.Source = w2Bmp;
var encoder = new TiffBitmapEncoder();
BitmapFrame frame = BitmapFrame.Create(w2Bmp);
encoder.Frames.Add(frame);
using (var stream = File.Create("XXX3.tiff"))
{
encoder.Save(stream);
}
}
A cursory glance at the decompiled sources of TiffBitmapEncoder reveals that it calls a native method to actually write to the TIFF. If, even on explicitly being passed the PixelFormat to write to it chooses to write something else, it might be a limitation of the underlying TIFF encoder.
Have you tried using ImageMagick or something similar with TIFF support?