A little bit of background:
I'm writing a bar code image scanner desktop app using WPF, that can take input from either a file location (previously scanned image) or have it come directly from a scanner (using NTWAIN). In both cases I create or get a stream.
Now when I create a new Bitmap from the stream and save it as a JPEG file using an Encoder
using (var bmp = Image.FromStream(rawStream))
{
EncoderParameter ratio = new EncoderParameter(Encoder.Quality, 100L);
EncoderParameter depth = new EncoderParameter(Encoder.ColorDepth, 8L);
EncoderParameters codecParams = new EncoderParameters(2);
codecParams.Param[0] = ratio;
codecParams.Param[1] = depth;
ImageCodecInfo jpegCodecInfo = ImageCodecInfo.GetImageEncoders().FirstOrDefault(x => x.FormatID == ImageFormat.Jpeg.Guid);
bmp.Save(file.FileFullPath, jpegCodecInfo, codecParams); // Save to JPG
}
or the built in
bmp.Save(file.FileFullPath, ImageFormat.Jpeg);
I tend to end up with much larger file sizes. Of course, this isn't always the case, but definately true when I'm loading a small black and white tiff file into memory and encoding as jpg.
My knowledge on image handling is rudimentary, but I think it is because the jpg files are saved with a color depth of 24 bits and the tiff images are originally stored as 1 bit. (Black and white)
No matter what I do, I can't get the jpg files to match the original file's bit depth.
The only work around I found is simply renaming the file to "filename.jpg" and saving like so
using (Bitmap bmp = new Bitmap(rawStream))
{
Save(file.FileFullPath);
}
But this feels like a solution that won't work indefinitely (as a side question, can one simply rename any *.bmp or *tiff file to *.jpg and it will still work?)
Based on my initial research it seems like
bmp.Save()
doesn't honor the encoding parameter for bit depth in jpeg images. Understandably my clients won't be happy having files grow from 16kb to 200kb for "no reason".
Is there a known work around for this problem or am I missing something obvious when it comes to working with streams and images?
JPEG works best for photographs with a multitude of colors, shades and gradients. Typical bit-depths: 8 (for greyscale) or 24 (for full color).
If you want monochrome (1-bit), I'd recommend agains using JPEG, not least because JPEG will introduce encoding artifacts that may not matter for photographs, but which will look like "added pepper and salt" if your original source is 1-bit. And the more you compress them, the more it will be there.
You should try using PNG instead, it has no such artifacts, and is better suited for digital sources with sharp edges.
You could also try making the TIFF smaller by 50% or 75% using a smart resize algorithm (using e.g. 8-bit output) that will convert micro-dots in the original into small gradients in the output. I did so long ago with 1-bit fax/scanner images, with actually quite good results. But too long to still have those sources.
Related
I have an image https://drive.google.com/file/d/16Xotc-2CJ6HkEJDysfKBkjClkU1OGiyQ/view?usp=sharing that is GrayScale but every library I have tried, ImageMagick, ImageSharp, System.Drawing seem to interpret it as black and white, but when you open it in ImageJ or Photoshop or Incarta or many other software you can clearly see it is grayscale.
can anyone help me find a way to display this image? here is something I've tried but i've tried almost a dozen different things
TiffEncoder encoder = new TiffEncoder();
encoder.PhotometricInterpretation = SixLabors.ImageSharp.Formats.Tiff.Constants.TiffPhotometricInterpretation.BlackIsZero;
SixLabors.ImageSharp.Image image = SixLabors.ImageSharp.Image.Load(mysteryTiff);
PixelTypeInfo pixType = image.PixelType;
// Stretches the image to fit the pictureBox.
Stream stream = new MemoryStream();
image.SaveAsTiff(stream, encoder);
stream.Position = 0;
MagickImage magickImage = new MagickImage(stream);
pictureBox1.SizeMode = PictureBoxSizeMode.StretchImage;
pictureBox1.ClientSize = new System.Drawing.Size(1200, 1200);
pictureBox1.Image = magickImage.ToBitmap();
Can anyone display this image correctly. It will display correctly when uploaded to
What you have there, according to the image tag directory, is a 2024x2024 16-bpp greyscale LZW-compressed extended TIFF. It even opens in some software, which proves that it's not malformed. So far so good.
Now here's where it breaks down: 16-bpp greyscale is not supported by a lot of things. The 'why' is mildly convoluted, having to do largely with "but we all use 8 bits per channel, and so does the hardware, so why bother", but the end result isn't: if you want to use anything above 8 bits per channel, you'll either have to find something that will do the work for you or convert the data to 8-bpp at some point.
Even when the file format explicitly support 16-bpp greyscale (TIFF and PNG for instance), most libraries tend not to support either read or write in that format because it is so rarely used that they don't bother to implement it. I ended up writing my own PNG encoder for 16-bpp greyscale images (converted from 12-bpp and 16-bpp XRAY images), but the images aren't viewable in most programs that supposedly support the full PNG standard.
In this case your best option is probably going to be to write a conversion of your own for this type of file. Assuming that the same format (16-bpp, LZW-compressed) is produced by the source application every time, it shouldn't be too difficult to convert the pixel buffer to 8-bpp and save out as TIFF, PNG or whatever you like. You'll lose half of your greyscale (depth) resolution, but for display purposes they're not going to help much anyway. It only really matters when there's a good reason to retain the full range of values.
I'm currently working on a small program to read png files from disk, do some modifications and save it back. Everything is running smoothly except for one small problem, after I saved the file back to disk, its size always increases, for example, a 27.1MB file will become 33.3MB.
After some debugging I finally narrow it down to my reading and saving code. This is the code I'm currently using:
Bitmap img = new Bitmap(<path to file>);
//omitted
img.Save(<path to new file>, ImageFormat.Png);
I've verified no matter if I do or do not make any modification, simply reading and saving the image will cause it size to change. Furthermore, if I opened the saved file with Paint and save from there, the file will shrink back to original size.
How do I read and save the image without changing its size?
Apart from the color depth and how many channels (w/o alpha) are used, saved PNG file size depends mainly on two factors:
How the pre-processing on image lines (called filtering) is done.
The compression level for the deflate algorithm (0-9).
This two factors will greatly affect the output image file size. Filtering is empirical and you can use one out of 4 filtering algorithm for all image lines or different algorithms for different lines or even adaptively try different algorithms on individual lines and choose the largest compression rate. The adaptively way is the most time consuming and impractical for most image writers.
After the filtering, image data is deflate compressed. The compression level for deflate algorithm usually ranges from 0-9 from lowerest to highest compression rate. The higher the compression rate, the slower the compression process. Usually 4 is the best for most of the images.
The filtering process plays a very important sometimes crucial role in PNG compression process. Different filtering algorithm may result in large difference in saved image size. On the other hand, image size is less sensitive to compression level.
You can use tools like TweakPNG to check about the color depth and number of channels the image contains. If the original and the re-saved image has the same color depth and channels, then most probably the filtering and compression level are the culprit for the increased file size.
The truth is if the encoder is not optimized, more often than not, the file size will increase. There are however a lot PNG optimization softwares out there if you don't mind post-processing your resulting images.
Have you tried playing with the Endoder.ColorDepth field? PNG also supports transparency and might be saving some information not needed by your image.
ImageCodecInfo pngCodec = ImageCodecInfo.GetImageEncoders().Where(codec => codec.FormatID.Equals(ImageFormat.Png.Guid)).FirstOrDefault();
if (pngCodec != null)
{
EncoderParameters parameters = new EncoderParameters();
parameters.Param[0] = new EncoderParameter(Encoder.ColorDepth, 24); //8, 16, 24, 32 (base on your format)
image.Save(stream, pngCodec, parameters);
}
Additional info here: https://msdn.microsoft.com/en-us/library/system.drawing.imaging.encoder.colordepth(v=vs.110).aspx
I think you are missing the compression part.
Add to your code like this -
Bitmap img = new Bitmap(<path to file>);
here is what you missed -
ImageCodecInfo myImageCodecInfo = GetEncoderInfo("image/jpeg");
EncoderParameter myEncoderParameter = new EncoderParameter(Encoder.Quality, 25L);
EncoderParameters myEncoderParameters.Param[0] = myEncoderParameter;
and save like this -
img.Save(<path to file>, myImageCodecInfo, myEncoderParameters);
Here is the MSDN link. hope it helps.
I'm making a C#/WPF/Windows 8 App store app and I'm trying to load up some PNGs/JPGs to display them in view. The images are all reasonably high resolution, but the file sizes are normally only around 200k or so. The problem is that when I load them up using the BitmapImage class (which is the only one I can find) the total memory used jumps up to 100s of megs. From what I can tell it takes the png/jpb and converts it to a bitmap image, which massively increases the memory usage. So far I haven't found a way around this, although it seems like there should be a simple solution.
Is there something really obvious I'm missing?
My code below
private async Task TestFunction(IReadOnlyList<StorageFile> files)
{
var images = new ObservableCollection<Image>();
imagePanel.ItemsSource = coverImages;
foreach (var file in files)
{
var bitmap = new BitmapImage();
var item = await file.OpenAsync(FileAccessMode.Read);
bitmap.SetSource(item);
var image = new Image();
image.Source = bitmap;
image.Height = 200;
images.Add(image);
}
}
If the image on disk is in compressed format (and most image file formats use some form of compression), the in-memory footprint will be larger.
If the image is 100x100 pixels in size and uses 8bits for colour depth, the raw data for that image would take up 100x100=10,000 bytes and that's the amount of data that has to be rendered to the screen.
If your looking for a way to reduce memorty usage in your WPF application there are a few option you can try.
Don't cache the images in memory, or selecting the best time to
load the images, using the BitmapCacheOption e.g: bitmap.CacheOption = BitmapCacheOption.None
this will fill the image as needed from disk, if the images are only 200k the performance drop should not be too bad, but it will not be as fast as caching
Make sure you not rendering images bigger than they need to be, If
the Element you are displaying the image on is 200x200 and the image
is 1024x768 you can set the DecodePixelWidth, this will create the
Bitmap to the size you define instead of its actual size.
e.g: bitmap.DecodePixelWidth = 200
PNG, JPG and all other forms of image compression are useful for storage only. In order to display compressed image content in WPF you have to decompress it to Bitmap which is a raw one-to-one representation of the image data.
If you were not to store decompressed image data in memory, then every time the system tried to reference the image for display it would have to decompress the image again, using precious CPU resources. In the case of popular formats like PNG or JPG, the compression and decompression process is rather complex.
There are image compression formats out there which are designed for dynamic decompression. These formats, such as DXT1-5, however are typically only supported by 3D libraries. (more info here)
I currently have an application which takes a screenshot of a presenter's desktop and then broadcasts it via a custom protocol to the viewers. In order for the images to be transfered quick enough to get a frame rate of 2 - 3 images per second, I need to ensure the image size is always less then ~ 300 KB.
I'm using C# for the presenter application, which encodes the screenshot into a JPEG via the process below. My concern is that the image quality can vary greatly when using a static compression setting. If I have the application capturing my screen, the images output will be ~200 KB when I have Visual Studio full screen, but if I minimize my screen and have my desktop background appearing, it will be ~400 KB.
I could put the encoding process into a loop, and continuously decrease the image size until the size of the byte array is less then 300 KB, but that seems like a tedious operation. Is there any other method I could use?
Thanks in advance.
// get the screenshot
System.Drawing.Rectangle totalSize = System.Drawing.Rectangle.Empty;
//foreach (Screen s in Screen.AllScreens)
totalSize = System.Drawing.Rectangle.Union(totalSize, Screen.PrimaryScreen.Bounds);
Bitmap screenShotBitmap = new Bitmap(totalSize.Width, totalSize.Height, System.Drawing.Imaging.PixelFormat.Format32bppRgb);
screenShotBitmap.SetResolution(96, 96);
Graphics screenShotGraphics = Graphics.FromImage(screenShotBitmap);
screenShotGraphics.CopyFromScreen(totalSize.X, totalSize.Y,
0, 0, totalSize.Size, CopyPixelOperation.SourceCopy);
screenShotGraphics.Dispose();
// image codec information
ImageCodecInfo imageCodecInfo = GetEncoderInfo("image/jpeg");
// encoder settings
System.Drawing.Imaging.Encoder encoderQuality;
System.Drawing.Imaging.Encoder encoderColor;
encoderQuality = System.Drawing.Imaging.Encoder.Quality;
encoderColor = System.Drawing.Imaging.Encoder.ColorDepth;
// compression & quality for JPEG output
Int64 quality = 40L;
// storage for exported JPEG
byte[] screenShotByteArray;
// encoder parameters
EncoderParameter encoderQualityParameter = new EncoderParameter(encoderQuality, quality);
//EncoderParameter encoderColorParameter = new EncoderParameter(encoderColor, 8L);
// encoder parameters table
EncoderParameters encoderParameters = new EncoderParameters(1);
encoderParameters.Param[0] = encoderQualityParameter;
//encoderParameters.Param[1] = encoderColorParameter;
// get the code into a memory stream
MemoryStream screenShotMemoryStream = new MemoryStream();
screenShotBitmap.Save(screenShotMemoryStream, imageCodecInfo, encoderParameters);
// convert to a byte array
screenShotByteArray = screenShotMemoryStream.GetBuffer();
// close the memory stream
screenShotMemoryStream.Close();
If you're putting things into a loop, be careful to use something similar to binary search instead of just increasing/decreasing the quality parameter by a fixed amount until the desired size is reached.
EDIT: Explaining the binary search a bit. Take the hypothetical case of a picture that compresses to quality*10000 bytes, so the optimal quality setting would be 30. Now the naive approach would be to try some fixed quality setting (f.e. 80 which would give 800,000 bytes) and then decreasing by a certain amount until 300000 bytes are reached. If you f.e. decrease image quality by 5 in each steps, you'd try 12 quality settings with this method until you found the desired setting. A binary search would give a result faster, like this:
Quality Size Next step
80 800000 Too big, so quality := quality/2
40 400000 Too big, so quality := quality/2
20 200000 Too small, so quality := (40+20)/2
30 300000 Reached desired size
This gives the result after only 4 tries (or 3 depending on 200000 bytes being too small or just fine for you). As size doesn't have a linear relation to quality, this example is a bit unrealistic, but binary search should still give you better results than the naive approach.
You could also use some typical images for "training". Encode them using different quality settings (f.e. 100,90,...,20,10) and see how big they get relative to their original size. This might give a good first estimate in most cases although you will still have to adjust when encountering images with much more or less details in them.
Alternatively, have a look at JPEG2000 encoders, those have the option to set a filesize instead of quality.
EDIT: I don't know of JPEG2000 encoding libaries for C#, there only seem to be decoders floating around, so this could get more complicated than I thought at first. You might give CSJ2K a try, but the description doesn't sound like it's ready-to-use.
The title pretty much explains my question. I would like to be able to read and write JPEG data on a per-pixel basis using C#.
I'm thinking something along the lines of CreateJPEG(x, y) which would set up a blank JPEG image in memory, and would give me a JPEG object, and then something like SetPixel(x, y, Color) and GetPixel(x, y) the latter of which would return a Color or something similar. You could then call an Apply() or Save() method, for example, to save the image in a standard JPEG-readable format (preferrably with compression options, but that's not necessary).
And I'm assuming some C# library or namespace makes this all a very easy process, I'd just like to know the best way to go about it.
Have a look at the Bitmap class. For advanced drawing besides manipulating single pixel you will have to use the Graphics class.
var image = new Bitmap("foo.jpg");
var color = image.GetPixel(1, 2);
image.SetPixel(42, 42, Color.White);
image.Save("bar.jpg", ImageFormat.Jpeg);
As Lasse V. Karlsen mentions in his answer this will not really manipulate the JPEG file. The JPEG file will be decompressed, this image data will be altered, and on saving a new JPEG file is created from the altered image data.
This will lower the image quality because even recompressing an unaltered image does usually not yield a bit-identical JPEG file due to the nature of lossy JPEG compressions.
There are some operations that can be performed on JPEG files without decompressing and recompressing it - for example rotating by 90° - put manipulating single pixels does not fit in this category.
JPEG is not a processing format, it's a storage format.
As such, you don't actually use a JPEG image in memory, you just have an image. It's only when you store it that you pick the format, like PNG or JPEG.
As such, I believe you're looking for the Bitmap class in .NET.