I'm handling a lot of different image formats within my application. Luckily I was able to source out the biggest part of dealing with image formats to C#WPF. However, I've got an issue when saving images:
I'd like to save my images as JPEGs using JpegBitmapEncoder with an RGB profile. This works all fine for color image formats. However, when handling images i.e. of format Gray16 or Gray8, the resulting JPEGs will have a Grayscale profile. I really do need an RGB profile for my JPEGs!
I know that I could just create a new WriteableBitmap in Bgra32, copy to it the data and use it to create the JPEG. This however means, that I would need to handle the different image formats myself. Most importantly I believe that this detour would be quite inefficient computationally (I need to convert a lot of image data!).
Also I can't use any solutions outside of C#/WPF (like Imagemagick).
I hope that there's a way to solve this easily and efficiently. I found no way to configure JpegBitmapEncoder for this and I had a try with ColorConvertedBitmap but to no avail!
Any ideas?
The hint for FormatConvertedBitmap gave me the solution to my problem! Thanks RononDex!
FormatConvertedBitmap convertImg = new FormatConvertedBitmap(img, PixelFormats.Bgra32, null, 0);
JpegBitmapEncoder encoder = new JpegBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(convertImg));
encoder.Save(stream);
Related
I have huge images (1.800MP # 8bit or 16bit), all grayscale, no alpha, transparency or other stuff.
They may come as png, tiff, bmp, or even jpeg, so I need an image library to handle the reading, decompression and stuff.
After this, I just want to get an array with the grayscale pixel values out - preferrably 2d, but 1d is also alright. It also may be ushort all the time, even for the 8bit images.
I tried using the buid-int BitmapImage of C# - no luck, just throws exceptions for images this large.
Any other libraries that can give me the grayscale values, without hassle?
It will be faster if you use simple FileReader to read the content and generate your own array rather then looking for a library.
Using WinForm, I have a small bmp file in the resources which is just black and white so two colour, saved as Lion.bmp. I need it just two colour as bmp. When the user clicks on a button a dialog comes up asking them where they want to save this. I want to then copy the resource to that space but save it as two colour bmp just as the original in resources. I have managed to save it as all manner of other files and even a 32 bit bitmap but not a two colour bitmap.
First I thought I would just copy byte for byte but found that was not possible with my knowledge. I could probably do it if I knew how.
Next I thought I would just create a new bitmap and save it
new Bitmap(Resources.Lion).Save(dialog.SelectedPath + "\\lion.bmp");
This compiled and I was happy till I realised I was saving a PNG file called .bmp. Next I find that I can add an Image format so I try
new Bitmap(Resources.Lion).Save(dialog.SelectedPath + "\\lion.bmp", ImageFormat.Bmp);
Again it compiles and saves a file but even though the resource is monochrome it saves it now a 32bit colour depth and not 2. I next try to just write it out as a stream of bytes which was my original plan
File.WriteAllBytes((dialog.SelectedPath + "\\lion.bmp"), Resources.Lion);
That does not compile as it says Resources.Lion is not a byte[] but I think it must be as it is in resources. Next I find in Bitmap I can have
Encoder.ColourDepth, 2
I think this would work but I cannot work out how to use that as ever time I try it will not compile.
new Bitmap(Resources.Lion).Save(dialog.SelectedPath + "\\lion.bmp", ImageFormat.Bmp, (Encoder.ColorDepth,2));
I guess I need to ask those wiser than me what the syntax may be to get this to work so that I can copy the monochrome bitmap from resources to monochrome bitmap on disk.
In the end a colleague came up with the suggestion of cloning the resource "Lion" that I wanted.This has worked. The code I used is below:
Resources.Lion.Clone(new Rectangle(0, 0, Resources.Lion.Width, Resources.Lion.Height),System.Drawing.Imaging.PixelFormat.Format1bppIndexed).Save(dialog.SelectedPath + "\\lion.bmp", ImageFormat.Bmp);
I have a Sharpdx Direct2D1 Bitmap from the render target. I want to save it to a file as an image though. This is for test purposes. I am not sure if i need a WIC bitmap for that or how to convert these bitmaps. Or how to receive the WIC bitmap in the first place.
Also i can't find an easy explanation how to save this bitmap to a file in general.
Any help appriciated.
Edit:
I'm using this approach now: http://www.rolandk.de/wp/2013/06/inhalt-der-rendertarget-textur-in-ein-bitmap-kopieren/
It is in German, but what he says is, that you have to copy the resource of the
render target to a staging resource to have access via the cpu. Then copy the contents over to a bitmap.
I'm trying this approach then finally using bitmap.Save(filename). But somehow the bitmap stays empty. Maybe i miss to encode the file correctly. But seems more like I don't get the data from the back buffer target at all.
If you have your image in a WicBitmap it is very easy to move it into a GDI+ bitmap and save it from there:
var pixelData = new byte[width*height*4];
wicBitmap.CopyPixels(pixelData, width*4);
var bmp = new System.Drawing.Bitmap(width, height);
var bd = bmp.LockBits(new System.Drawing.Rectangle(0, 0, width, height), ImageLockMode.WriteOnly,
System.Drawing.Imaging.PixelFormat.Format32bppPArgb);
Marshal.Copy(pixelData, 0, bd.Scan0, pixelData.Length);
bmp.UnlockBits(bd);
There may or may not be better ways to solve this. But this is the one I have used successfully.
The question is answered by Dan Bysröm, if you have the wicBitmap and then simply copy the data to the gdi bitmap.
I solved my particular problem with the link I posted in the edit part though. I did not use wic or anything, because that maybe was a totally wrong approach for my problem in the first place.
The question that arised in the edit part was solved by not using CopyResource(...) but CopySubResource(...) instead with default parameter to copy all. I am not sure why CopyResource(...) did not work though.
I just got a real surprise when I loaded a jpg file and turned around and saved it with a quality of 100 and the size was almost 4x the original. To further investigate I open and saved without explicitly setting the quality and the file size was exactly the same. I figured this was because nothing changed so it's just writing the exact same bits back to a file. To test this assumption I drew a big fat line diagonally across the image and saved again without setting quality (this time I expected the file to jump up because it would be "dirty") but it decreased ~10Kb!
At this point I really don't understand what is happening when I simply call Image.Save() w/out specifying a compression quality. How is the file size so close (after the image is modified) to the original size when no quality is set yet when I set quality to 100 (basically no compression) the file size is several times larger than the original?
I've read the documentation on Image.Save() and it's lacking any detail about what is happening behind the scenes. I've googled every which way I can think of but I can't find any additional information that would explain what I'm seeing. I have been working for 31 hours straight so maybe I'm missing something obvious ;0)
All of this has come about while I implement some library methods to save images to a database. I've overloaded our "SaveImage" method to allow explicitly setting a quality and during my testing I came across the odd (to me) results explained above. Any light you can shed will be appreciated.
Here is some code that will illustrate what I'm experiencing:
string filename = #"C:\temp\image testing\hh.jpg";
string destPath = #"C:\temp\image testing\";
using(Image image = Image.FromFile(filename))
{
ImageCodecInfo codecInfo = ImageUtils.GetEncoderInfo(ImageFormat.Jpeg);
// Set the quality
EncoderParameters parameters = new EncoderParameters(1);
// Quality: 10
parameters.Param[0] = new EncoderParameter(
System.Drawing.Imaging.Encoder.Quality, 10L);
image.Save(destPath + "10.jpg", codecInfo, parameters);
// Quality: 75
parameters.Param[0] = new EncoderParameter(
System.Drawing.Imaging.Encoder.Quality, 75L);
image.Save(destPath + "75.jpg", codecInfo, parameters);
// Quality: 100
parameters.Param[0] = new EncoderParameter(
System.Drawing.Imaging.Encoder.Quality, 100L);
image.Save(destPath + "100.jpg", codecInfo, parameters);
// default
image.Save(destPath + "default.jpg", ImageFormat.Jpeg);
// Big line across image
using (Graphics g = Graphics.FromImage(image))
{
using(Pen pen = new Pen(Color.Red, 50F))
{
g.DrawLine(pen, 0, 0, image.Width, image.Height);
}
}
image.Save(destPath + "big red line.jpg", ImageFormat.Jpeg);
}
public static ImageCodecInfo GetEncoderInfo(ImageFormat format)
{
return ImageCodecInfo.GetImageEncoders().ToList().Find(delegate(ImageCodecInfo codec)
{
return codec.FormatID == format.Guid;
});
}
Using reflector, it turns out Image.Save() boils down to the GDI+ function GdipSaveImageToFile, with the encoderParams NULL. So I think the question is what the JPEG encoder does when it gets a null encoderParams. 75% has been suggested here, but I can't find any solid reference.
EDIT You could probably find out for yourself by running your program above for quality values of 1..100 and comparing them with the jpg saved with the default quality (using, say, fc.exe /B)
IIRC, it is 75%, but I dont recall where I read this.
I don't know much about the Image.Save method, but I can tell you that adding that fat line would logicly reduce the size of the jpg image. This is due to the way a jpg is saved (and encoded).
The thick black line makes for a very simple and smaller encoding (If I remember correctly this is relevent mostly after the Discrete cosine transform), so the modified image can be stored using less data (bytes).
jpg encoding steps
Regarding the changes in size (without the added line), I'm not sure which image you reopened and resaved
To further investigate I open and saved without explicitly setting the quality and the file size was exactly the same
If you opened the old (original normal size) image and resaved it, then maybe the default compression and the original image compression are the same.
If you opened the new (4X larger) image and resaved it, then maybe the default compression for the save method is derived from the image (as it was when loaded).
Again, I don't know the save method, so I'm just throwing ideas (maybe they'll give you a lead).
When you save an image as a JPEG file with a quality level of <100%, you are introducing artefacts into the saved-off image, which are a side-effect of the compression process. This is why re-saving the image at 100% is actually increasing the size of your file beyond the original - ironically there's more information present in the bitmap.
This is also why you should always attempt to save in a non-lossy format (such as PNG) if you intend to do any edits to your file afterwards, otherwise you'll be affecting the quality of the output through multiple lossy transformations.
I am loading a JPG image from hard disk into a byte[]. Is there a way to resize the image (reduce resolution) without the need to put it in a Bitmap object?
thanks
There are always ways but whether they are better... a JPG is a compressed image format which means that to do any image manipulation on it you need something to interpret that data. The bimap object will do this for you but if you want to go another route you'll need to look into understanding the jpeg spec, creating some kind of parser, etc. It might be that there are shortcuts that can be used without needing to do full intepretation of the original jpg but I think it would be a bad idea.
Oh, and not to forget there are different file formats for JPG apparently (JFIF and EXIF) that you will ened to understand...
I'd think very hard before avoiding objects that are specifically designed for the sort of thing you are trying to do.
A .jpeg file is just a bag o' bytes without a JPEG decoder. There's one built into the Bitmap class, it does a fine job decoding .jpeg files. The result is a Bitmap object, you can't get around that.
And it supports resizing through the Graphics class as well as the Bitmap(Image, Size) constructor. But yes, making a .jpeg image smaller often produces a file that's larger. That's an unavoidable side-effect of Graphics.Interpolation mode. It tries to improve the appearance of the reduced image by running the pixels through a filter. The Bicubic filter does an excellent job of it.
Looks great to the human eye, doesn't look so great to the JPEG encoder. The filter produces interpolated pixel colors, designed to avoid making image details disappear completely when the size is reduced. These blended pixel values however make it harder on the encoder to compress the image, thus producing a larger file.
You can tinker with Graphics.InterpolationMode and select a lower quality filter. Produces a poorer image, but easier to compress. I doubt you'll appreciate the result though.
Here's what I'm doing.
And no, I don't think you can resize an image without first processing it in-memory (i.e. in a Bitmap of some kind).
Decent quality resizing involves using an interpolation/extrapolation algorithm; it can't just be "pick out every n pixels", unless you can settle with nearest neighbor.
Here's some explanation: http://www.cambridgeincolour.com/tutorials/image-interpolation.htm
protected virtual byte[] Resize(byte[] data, int width, int height) {
var inStream = new MemoryStream(data);
var outStream = new MemoryStream();
var bmp = System.Drawing.Bitmap.FromStream(inStream);
var th = bmp.GetThumbnailImage(width, height, null, IntPtr.Zero);
th.Save(outStream, System.Drawing.Imaging.ImageFormat.Jpeg);
return outStream.ToArray(); }