I am converting a bunch of TIFF images to JPEG with the following code. I am having an issue that the image sizes are growing exponentially on black and white images with a lot of black specks on them. I have found out that the image coming in(TIFF) is PixelFormat Format1bppIndexed, but then it always saves them to JPEG as Format24bppRgb. Is there a way to always keep the PixelFormat of the original image so this issue doesn't occur? The PixelFormat of the original image is not always the same, as the images are color, b&W, grayscale.
public static string[] ConvertTiffToJpeg(string fileName)
{
using (Image imageFile = Image.FromFile(fileName))
{
var test = imageFile.PixelFormat;
FrameDimension frameDimensions = new FrameDimension(
imageFile.FrameDimensionsList[0]);
// Gets the number of pages from the tiff image (if multipage)
int frameNum = imageFile.GetFrameCount(frameDimensions);
string[] jpegPaths = new string[frameNum];
for (int frame = 0; frame < frameNum; frame++)
{
// Selects one frame at a time and save as jpeg.
imageFile.SelectActiveFrame(frameDimensions, frame);
using (Bitmap bmp = new Bitmap(imageFile))
{
jpegPaths[frame] = String.Format("{0}\\{1}{2}.jpg",
Path.GetDirectoryName(fileName),
Path.GetFileNameWithoutExtension(fileName),
frame);
bmp.Save(jpegPaths[frame], ImageFormat.Jpeg);
}
}
return jpegPaths;
}
}
The .jpg files will be 24-bit color or 8-bit grayscale. You can use 8-bit grayscale for 1 bpp black and white tiff images and save some space, but it's not possible to use indexed color or 1 bit per pixel with .jpg files.
Depending on the image target app, you may be able to save the 1 bpp tiff images as .png or .gif image instead of .jpg. That should be about as small as the tiff file if it is saved with indexed color or with 1 bpp.
Related
I want to do stamping on tiff file (Png image on tiff) but without changing its properties like DPI.
I have tried below code but it is reducing the size of tiff.
System.Drawing.Bitmap bitmap = (System.Drawing.Bitmap) System.Drawing.Image.FromFile (inputpath);
Bitmap EditableImg = new Bitmap (bitmap);
System.Drawing.Bitmap bitmapStamping = (System.Drawing.Bitmap) System.Drawing.Image.FromFile (stamppath);
using (System.Drawing.Graphics graphics = System.Drawing.Graphics.FromImage (EditableImg)) {
graphics.DrawImage (bitmapStamping, secondLocation);
}
EditableImg.Save (outputpath);
When building Bitmaps and collectively building a few Bitmaps and combining them into one Bitmap does it help to .MakeTransparent().
And or, before I send the Bitmap to the requesting client if I .MakeTransparent() will it become smaller in size? not width or height, buy in bytes?
In other words will .MakeTransparent() optimize the Bitmap, and if not does anyone get any suggestions on how to optmize a Bitmap before sending to the requesting client over the wire via internet?
The code in question is sheet.MakeTransparent()
internal static Task<Bitmap> GetDoorSecheduleSheetAsync(ShopDrawing.DoorSchedules schedules, RotateFlipType rotate, byte schedulesPerSheet, byte currentI)
{
return Task.Run(() =>
{
var sheet = new Bitmap(DrawingOptions.PAGE_HEIGHT_SIZE, DrawingOptions.PAGE_WIDTH_SIZE);
sheet.SetResolution(150, 150);
byte scheduleCnt = 0;
float prevWidth = 0;
using (Graphics dc = Graphics.FromImage(sheet))
{
dc.Clear(Color.White);
using (Pen pen = new Pen(Color.FromArgb(80, Color.Black), 4))
{
for (; currentI < schedules.Count(); currentI++)
{
if (scheduleCnt > 0)
{
dc.DrawLine(pen, prevWidth, 380/*need constant for start height*/, prevWidth, sheet.Height);
};
using (var doorSchedule = schedules[currentI].Door)
{
dc.DrawImage(doorSchedule, prevWidth + 50, 380/*need constant for start height*/);
prevWidth += doorSchedule.Width + 50;
scheduleCnt++;
}
if (scheduleCnt == schedulesPerSheet)
{
sheet.RotateFlip(rotate);
sheet.MakeTransparent();
return sheet;
}
};
};
};
sheet.MakeTransparent();
sheet.RotateFlip(rotate);
return sheet;
});
}
Thank you!
BMP is uncompressed format with essentially raw bytes (or palette indexes) for each pixel - there is no transformation that will change size of resulting file except changing bit-per-pixel count.
Don't send uncompressed bmp over network - use either loss-less PNG/GIF or (if it works for you) JPG.
As said by Alexei Levenkov sending the raw BMP-Format over the wire is the worst choice.
Calling MakeTransparent() would (if it's not already the case) convert the image format to 32bit (= with alpha cannel), which is most byte hungry - transparency has its costs.
You should save the Bitmap as a i.e. jpeg (if you don't really need transparency) or png (if you really need it) - they're both much more efficient.
Look at this answered question about it: High Quality Image Scaling Library
It shows how to use the .NET built-in image encoders.
When you call the MakeTransparent method the bitmap will be converted to the Format32bppArgb format, as this format supports an alpha channel.
The Format32bppArgb is a format what uses 32 bits per pixel; 8 bits each are used for the alpha, red, green, and blue components. Therefore it could mean a change in size.
But just to have control over what really happens you could zip the bitmap or compress it bye converting it to other formats as PNG o JPG as Alexei Levenkov suggested
Here you can find and example of how to convert PNG to BMP, you can use the same code but exchanging formats
How to convert PNG to BMP at runtime?
I'm using System.Drawing.Image in .Net to do a simple conversion from png to jpeg.
I'm basically just using these two lines of code:
Image img = Image.FromFile(filename);
img.Save(newFilename, System.Drawing.Imaging.ImageFormat.Jpeg);
it works fine except for when the png files contain transparency due to the alpha channel. In which case the converted jpeg has a black background. Is there any way to make the background white instead?
// Assumes myImage is the PNG you are converting
using (var b = new Bitmap(myImage.Width, myImage.Height)) {
b.SetResolution(myImage.HorizontalResolution, myImage.VerticalResolution);
using (var g = Graphics.FromImage(b)) {
g.Clear(Color.White);
g.DrawImageUnscaled(myImage, 0, 0);
}
// Now save b as a JPEG like you normally would
}
I'm able to save the captured image from a barcode scanner using this code:
Microsoft.Win32.SaveFileDialog dlg = new Microsoft.Win32.SaveFileDialog();
dlg.DefaultExt = ".jpg";
dlg.Filter = "JPEG Images (.jpg)|*.jpg|All files (*.*)|*.*";
if (dlg.ShowDialog() == true)
{
using (FileStream file = File.OpenWrite(dlg.FileName))
{
file.Write(e.ImageBuffer, 0, e.ImageSize);
}
}
However, I would like to display the captured image using WPF but I get a distorted image.
private void _barcodeScannerInstance_SavePhotoEvent(object sender, ImageEventArgs e)
{
SetBitmap(e.ImageBuffer, 350, 263, 96);
}
private void SetBitmap(byte[] image, int width, int height, int dpi)
{
MainWindow.Instance.Dispatcher.Invoke(DispatcherPriority.Normal, (ThreadStart)delegate()
{
BitmapSource bitmapSource = BitmapSource.Create(
width, height, (double)dpi, (double)dpi, PixelFormats.Bgr24, null, image, ((width * 24 + 31) & ~31) >> 3);
HwModeScreen.BarcodeImageCanvas.Children.Clear();
Image myImage = new Image();
myImage.Width = HwModeScreen.BarcodeImageCanvas.ActualWidth;
myImage.Height = HwModeScreen.BarcodeImageCanvas.ActualHeight;
myImage.Stretch = Stretch.Fill;
myImage.Source = bitmapSource;
HwModeScreen.BarcodeImageCanvas.Children.Add(myImage);
});
Here is the image I see. It should be a black and white picture of a kleenex box.
Here is the saved jpg file:
did you mix up width and height? are you sure your dpi value is correct?
I suspect the whole problem is this line:
BitmapSource bitmapSource = BitmapSource.Create(
width, height, (double)dpi, (double)dpi, PixelFormats.Bgr24, null, image, ((width * 24 + 31) & ~31) >> 3)
What I would do to debug the issue is to write out the image to file and confirm all the inputs. Use photoshop, paint.net, file properties...
Are you sure you are working with bitmap format?
Are you sure you are working with 24bits per pixel?
Are you sure you have height and width correct, and you are feeding the values into the correct argument
What is this line all about, and why are you doing it? I am slightly suspicious.
((width * 24 + 31) & ~31) >> 3)
Basically, the way I look at this is that you are feeding the bitmap library a stream of bits... it doesn't know what the bits are but it will attempt to create the image from the information you give it: bits per pixel, size, etc. If you give it incorrect information, it will create a corrupted image as you have shown.
I am slightly suspicious that the problem is not with width and height; even if you mix those two values up-- I think you would get at least part of the first row of pixels to be rendered correctly. I see static / noise / snow, which tells me that there is something about the way the stream of bits was interpreted-- it is rendered as random blacks and whites.
Another thing: in your screen cap, I see color. this is another hint that there is something incorrect about your assumptions about the image. The values should probably 1 to 256 ( 8 bits per pixel I think? ) I would try creating a 8 bit per pixel black and white bitmap. Somehow the library thinks this is a color image.
I just noticed that you are assuming jpeg. jpeg is a lossy format-- I would have assumed that you would end up with a bitmap or tiff image. double check that you are indeed getting back a jpeg image (check the barcode api documentation)
The JPEG compression algorithm is quite unsuitable for the kind of image you are capturing. It works well for photos, it behaves poorly on images containing fine lines. The slight artifacts the compression produces makes it a lot harder to properly scan the barcode.
You don't see the Kleenex box because you are writing the raw image bytes. You need to use an image encoder. I recommend you use the PngBitmapEncoder class. GifBitmapEncoder should work too since you don't need a lot of colors, it makes smaller files. A code snippet that shows how to use an encoder is available here.
this is likely distorting it
myImage.Stretch = Stretch.Fill;
I used a jpeg decoder to fix the problem.
private void SetBitmap(byte[] image, int width, int height, int dpi)
{
MainWindow.Instance.Dispatcher.Invoke(DispatcherPriority.Normal, (ThreadStart)delegate()
{
BMemoryStream ms = new MemoryStream(image);
JpegBitmapDecoder decoder = new JpegBitmapDecoder(ms, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);
BitmapSource bitmapSource = decoder.Frames[0];
HwModeScreen.BarcodeImageCanvas.Children.Clear();
Image myImage = new Image();
myImage.Width = HwModeScreen.BarcodeImageCanvas.ActualWidth;
myImage.Height = HwModeScreen.BarcodeImageCanvas.ActualHeight;
myImage.Stretch = Stretch.Fill;
myImage.Source = bitmapSource;
HwModeScreen.BarcodeImageCanvas.Children.Add(myImage);
});
Alright, I have an image coming through from an external application in an 8-bit indexed format. I need this image converted to a 24-bit format of the exact same size.
I've tried creating a new Bitmap of the same size and of type Format24bppRgb and then using a Graphics object to draw the 8-bit image over it before saving it as a Bmp. This approach doesn't error out but when I open the resulting image the BMP header has all kinds of funky values. The height and width are HUGE and, in addition, there are funny (and large) values for the compression flags and a few others. Unfortunately my particular requirements are to pass this file off to a specific printer driver that demands a 24-bit image with specific header values (which I'm trying to achieve through GDI+)
Anyone know of an example on "up-converting" an indexed file to a not-indexed 24-bit file? If not an example, which path should I start down to write my own?
-Kevin Grossnicklaus
kvgros#sseinc.com
I used the code below to "up-convert" an image from 8bpp to 24bpp. Inspecting the generated 24bpp file with a hex editor and comparing against the 8bpp file shows no difference in height and width in the two files. That is, the 8bpp image was 1600x1200, and the 24bpp image has the same values.
private static void ConvertTo24(string inputFileName, string outputFileName)
{
Bitmap bmpIn = (Bitmap)Bitmap.FromFile(inputFileName);
Bitmap converted = new Bitmap(bmpIn.Width, bmpIn.Height, PixelFormat.Format24bppRgb);
using (Graphics g = Graphics.FromImage(converted))
{
// Prevent DPI conversion
g.PageUnit = GraphicsUnit.Pixel
// Draw the image
g.DrawImageUnscaled(bmpIn, 0, 0);
}
converted.Save(outputFileName, ImageFormat.Bmp);
}
Everything else in the headers looks reasonable, and the images display identical on my system. What "funky values" are you seeing?
This is my conversion code. Notice the matching of resolution between source image and resulting image.
private void ConvertTo24bppPNG(Stream imageDataAsStream, out byte[] data)
{
using ( Image img = Image.FromStream(imageDataAsStream) )
{
using ( Bitmap bmp = new Bitmap(img.Width, img.Height, PixelFormat.Format24bppRgb) )
{
// ensure resulting image has same resolution as source image
// otherwise resulting image will appear scaled
bmp.SetResolution(img.HorizontalResolution, img.VerticalResolution);
using ( Graphics gfx = Graphics.FromImage(bmp) )
{
gfx.DrawImage(img, 0, 0);
}
using ( MemoryStream ms = new MemoryStream() )
{
bmp.Save(ms, ImageFormat.Png);
data = new byte[ms.Length];
ms.Position = 0;
ms.Read(data, 0, (int) ms.Length);
}
}
}
}
It seems odd that you're creating a Bitmap of the same width and height as your input, yet the generated BMP is much larger. Can you post some code?
The problem is probably the difference between the Vertical- and HorizontalResolution of your source image and your output image. If you load a 8bpp indexed bitmap with a resolution of 72 DPI, and then create a new 24bpp bitmap (default resolution will be 96 DPI... at least it is on my system) and then use Graphics.DrawImage to blit to the new bitmap, your image will appear slightly zoomed in and cropped.
Having said that, I don't know off the top of my head how to properly create the output Bitmap and/or Graphics object to scale properly when saved. I suspect it will have something to do with creating the images using a common scale like inches instead of pixels.