Does making a Bitmap Transparent a smaller image, as in bytes - c#

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?

Related

How to load a part of a TIFF image without loading whole image into memory?

How to load a part of a *.tif image without loading this image into memory.
I have to work with big TIFF files. (> 4 GB). I tried to read this file using BinaryReader, and using BitMiracle.LibTiff.Classic to convert bytes into image. But I didn't find example for how to read a specific pixel in a TIFF file.
Maybe be you have some solution for this task.
Lets say i have a BigScan.tif file and it is always:
Image Compression - NONE
Pixel Order - Interleaved (RGBRGB)
Byte Order - IBM PC
I have some variable:
ImagePart with User Defined Width
ImagePart with User Define Height
ImagePArt with User Defined Location
The question is, how could I get ImagePart from BigScan.tif?
But it would be best to have the ability to read information of the pixel in "BigScan.tif" with (x,y) coorinates.
I need to read a pixel from the BigScan.tif in specified place, with such function:
public Color GetPixelColorFromTiffImage(string TiffFileName, int PixelPositionX, int PixelPositionY)
{
//Some Code
return returnedColor;
}
Very strange but the support did`t unswer my quastion. May be somebody knows it. Why dose this part of the code from BitMiracle Samples wrote to 'raster' array numbers like "-11512229", "-11838376" and so on.
using (Tiff image = Tiff.Open(fullImageLocation, "r"))
{
// Find the width and height of the image
FieldValue[] value = image.GetField(TiffTag.IMAGEWIDTH);
width = value[0].ToInt();
value = image.GetField(TiffTag.IMAGELENGTH);
height = value[0].ToInt();
int imageSize = height * width;
int[] raster = new int[imageSize];
// Read the image into the memory buffer
if (!image.ReadRGBAImage(width, height, raster))
{
MessageBox.Show("Could not read image");
}
using (Bitmap btm = new Bitmap(200, 200))
{
for (int i = 0; i < btm.Width; ++i)
for (int j = 0; j < btm.Height; ++j)
btm.SetPixel(i, j, getSample(i + 330, j + 30, raster, width, height));
ReternedBitmap = btm;
}
}//using
Your question is unclear (you ask at least two different questions).
If you need to crop a part of a larger image then you need to:
read each relevant scanline of a source image
copy part of that scanline to a new image.
If you need to get color value of a single pixel in a given location than again you need to:
read relevant scanline
find relevant bytes in that scanline
pack those bytes into a Color structure or whatever else
You didn't specify what are Photometric, BitsPerSample and SamplesPerPixel values for your image, so it's hard to tell what exactly are you dealing with.
Most probably, you are faced with geo-images. If so, they are probably RGB, 24bit, tiled images.
For tiled images it is possible to read only small part (say, 256 x 256 pixels) of the image at once. But even if they are stripped, one scanline of such an image will take only about 1 MB of memory (219 000 pixels * 3 bytes per pixel). It's nothing if you are really need to process such big images.
I wouldn't recommend you to try developing your own parser. It's not that easy if you know only basics about TIFF format.

DrawingContext to Bitmap file

I have a DrawingContext (part of a Visual or a DrawingGroup), where I draw a bunch of rectangles and/or 1-bit images on top of each other. Think of it as a masking 1-bit image. I would like to convert this into a bitmap image file.
Using RenderTargetBitmap is not an option because it can only render at 32bit pixel format, so if I have to render a 20MB 1-bit image, I will end up with a 640MB (20*32) of memory on my heap. This of course creates magnificent LOH fragmentation, and the application runs out-of-memory on the second shot.
So, I basically need a way to write a 1-bit bitmap file from a drawing context efficiently. Any ideas/suggestions/alternate methods would be appreciated.
A number of ideas, some are a bit convoluted...
Print to XPS then extract Bitmap
You could print the Visual to an XPS Document.
If you're lucky then it will combine the 1bit images that you drew in in the DrawingContext and produce a single bitmap in the XPS file.
For the Rectangles it might keep the Rectangles as vector based information in the XPS (the "Shape" based Rectangle or DrawingContext DrawRectangle might both do this)....if that happens then create a bitmap into which your draw the rectangle part and draw that bitmap into the context.
Once you have the XPS files, you could then parse it and extract the "image" if you are lucky that's what it produced inside. (XPS is just a ZIP file that uses XAML to describe content and uses subdirectories to store bitmap image data files, fonts, etc).
(I think you can use the Package class to access the raw parts of an XPS document, or XpsDocument for a higher level interpretation).
Or just display the XPS in an XPS viewer if your intention is just to provide a way to allow your combined 1bit images to be viewed in a different context.
Convert WPF (XAML) Control to XPS Document
How to create BIG bitmaps with RenderTargetBitmap?
Printing a WPF BitmapImage
Use a Printer Driver that provides Print to Image functionality
This isn't ideal...not least because you have to install a Printer Driver on the machine.....but you might be able to use a "Print to Image" printer driver and then use that to produce your bitmap. Here are some suggestions:
http://sourceforge.net/projects/imageprinter/
http://www.zan1011.com/
Create a GDI+ HBITMAP and do the drawing using GDI+ calls...then wrap it for use by WPF...or save out to disk.
[1] First create a GDI+ Bitmap large enough to hold your composed rendering
(there are a few different ways to do this...one way is to use a WriteableBitmap to provide the backbuffer bits/store...that's if you wanted access to the bits in memory...in your case I don't think you do/need to).
var bmp = new System.Drawing.Bitmap( pixelwidth, pixelheight, System.Drawing.Imaging.Format1bppIndexed );
Or this way if you want WriteableBitmap access.
Getting a DrawingContext for a wpf WriteableBitmap
[2] Convert any WPF BitmapSources to GDI+ Bitmaps so you can draw them onto the GDI+ Graphics context using DrawImage.
You can use CopyPixels to do that on your BitmapSources.
Is there a good way to convert between BitmapSource and Bitmap?
For your rectangles you can just use the GDI+ DrawRectangle rendering command.
[3] Save the GDI+ Bitmap to disk
Use the .Save method on System.Image.Bitmap to save it as a Bitmap.
[4] Use the saved image as the Source to an Image element
(note this will probably use masses of memory when it loads your image even though it's 1bpp...because the WPF rendering path is all 32bpp).
[4] OR wrap the GDI+ Bitmap for use in WPF
You can use InteropBitmap to wrap the GDI+ based bitmap so you can use it as a BitmapSource in WPF. (note it may have to be a 32bpp one....if it has to...then you are back at square 1...but try anyway).
http://arbel.net/2008/10/22/improper-use-of-interopbitmap-can-cause-a-memory-leak/
http://arbel.net/2008/10/22/improper-use-of-interopbitmap-can-cause-a-memory-leak/
Create a Bitmap "Service"
Create another process which acts as a service (doesn't have to be an NT service...could just be a child process you start) to render your combined 1bpp images....there are various ways to communicate with it to give it the rendering commands.
When memory consumption gets too high/the LOH gets fragmented, then you could restart this service.
Other Ideas
You could render using OpenGL (e.g. use OpenTK or SharpGL), or render using DirectX...via the 3D path with D3DImage or Direct2D (whether this behaves the same as RenderTargetBitmap in terms of memory usage...that's to find out).
http://www.codeproject.com/Articles/265903/Using-OpenGL-in-a-WPF-Application
http://www.codeproject.com/Articles/113991/Using-Direct2D-with-WPF
http://www.codeproject.com/Articles/28526/Introduction-to-D3DImage
Try out NET 4.5 as there have been a number of improvements in the LOH Heap:
http://blogs.msdn.com/b/dotnet/archive/2011/10/04/large-object-heap-improvements-in-net-4-5.aspx
http://blogs.msdn.com/b/dotnet/archive/2012/07/20/the-net-framework-4-5-includes-new-garbage-collector-enhancements-for-client-and-server-apps.aspx
I dont think you can do anything better. Since RenderTargetBitmap uses MILcore which you can't access. And I don't think there is any other way to copy Visual. However I think there is one option more. It won't be one-line but I think it will be good enough.
Basically you will render visual block by block(PGBRA32) and convert it into BlackWhite on the fly, and then concat it with Blackwhite buffer. I've started a little example code but in halfway decided that it's not gonna be so easy, but you can finish it.
/// <summary>
/// Renders only part of the visual and returns byte[] array
/// which holds only black/white information.
/// </summary>
/// <param name="oVisual"></param>
/// <param name="x"></param>
/// <param name="y"></param>
/// <param name="width"></param>
/// <param name="height"></param>
/// <returns>black/white pixel information. one pixel=one bit. 1=white, 0=black</returns>
public static byte[] RenderPartially(Visual oVisual,
int x, int y, double width, double height)
{
int nWidth = (int)Math.Ceiling(width);
int nHeight = (int)Math.Ceiling(height);
RenderTargetBitmap oTargetBitmap = new RenderTargetBitmap(
nWidth,
nHeight,
96,
96,
PixelFormats.Pbgra32
);
DrawingVisual oDrawingVisual = new DrawingVisual();
using (DrawingContext oDrawingContext = oDrawingVisual.RenderOpen())
{
VisualBrush oVisualBrush = new VisualBrush(oVisual);
oDrawingContext.DrawRectangle(
oVisualBrush,
null,
new Rect(
new Point(x, y),
new Size(nWidth, nHeight)
)
);
oDrawingContext.Close();
oTargetBitmap.Render(oDrawingVisual);
}
//Pbgra32 == 32 bits ber pixel?!(4bytes)
// Calculate stride of source and copy it over into new array.
int bytesPerPixel = oTargetBitmap.Format.BitsPerPixel / 8;
int stride = oTargetBitmap.PixelWidth * bytesPerPixel;
byte[] data = new byte[stride * oTargetBitmap.PixelHeight];
oTargetBitmap.CopyPixels(data, stride, 0);
//assume pixels in byte[] are stored as PBGRA32 which means that 4 bytes form single PIXEL.
//so the layout is like:
// R1, G1, B1, A1, R2, G2, B2, A2, R3, G3, B3, A3, and so on.
byte [] bitBufferBlackWhite = new byte[oTargetBitmap.PixelWidth
* oTargetBitmap.PixelHeight / bytesPerPixel];
for(int row = 0; row < oTargetBitmap.PixelHeight; row++)
{
for(int col = 0; col < oTargetBitmap.PixelWidth; col++)
{
//calculate concrete pixel from PBGRA32
int index = stride * row + bytesPerPixel * col;
int r = data[index];
int g = data[index + 1];
int b = data[index + 2];
int transparency = data[index + 3];
//determine whenever pixel is black or white.
//note that I dont know the exact process how one converts
//from PBGRA32 to BlackWhite format. But you should get the idea.
bool isBlack = r + g + b + transparency == 0;
//calculate target index and USE bitwise operators in order to
//set right bits.
}
}
return bitBufferBlackWhite;
}
So essentially, set up new WriteableBitmap with BlackWhite format, and then call this function like this:
WriteableBitmap blackWhiteFullBuffer = new WriteableBItmap(....., PIxelFormats.BlackWhite);
for(int x = 0; x < Visual.Width; x += <some uniform amount that divides correctly>)
{
for(int y = 0; y < VIsual.Height; y+= <some uniform amount that divides correctly>)
{
byte[] partialBuffer = PartialRenderer.RenderPartially(Visual, x, y, <uniform amX>,
<uniform amY>);
//concat that partial blackWhite buffer with other blackWhite buffer.
//you do this as long as needed and memory wont grow too much
//hopefully it will be fast enough too.
PartialRenderer.ConcateBuffers(blackWhiteFullBuffer, partialBuffer);
}
}
//then save blackWhiteFullBuffer to HDD if needed.
What about PixelFormats
Edit: (thanks to Anders Gustafsson)
The lower is PixelFormats.BlackWhite, with 1bit per pixel.
So this way, you can convert any BitmapImage to a FormatConvertedBitmap, where you can modify the format to a lower bpp.

Is there a way to find out about a png image transparency by reading the image header?

Is there a way to find out about a png image transparency by reading the image header?
Binary access
Following my comment about the poor performance of GetPixel for each pixel, I tried to write a snippet which finds if there are transparent pixels or not in an image (including PNG). Here it is.
public static bool IsImageTransparent(string fullName)
{
using (Bitmap bitmap = Bitmap.FromFile(fullName) as Bitmap)
{
bool isTransparent;
// Not sure if the following enumeration is correct. Maybe some formats do not actually allow transparency.
PixelFormat[] formatsWithAlpha = new[] { PixelFormat.Indexed, PixelFormat.Gdi, PixelFormat.Alpha, PixelFormat.PAlpha, PixelFormat.Canonical, PixelFormat.Format1bppIndexed, PixelFormat.Format4bppIndexed, PixelFormat.Format8bppIndexed, PixelFormat.Format16bppArgb1555, PixelFormat.Format32bppArgb, PixelFormat.Format32bppPArgb, PixelFormat.Format64bppArgb, PixelFormat.Format64bppPArgb };
if (formatsWithAlpha.Contains(bitmap.PixelFormat))
{
// There might be transparency.
BitmapData binaryImage = bitmap.LockBits(new Rectangle(Point.Empty, bitmap.Size), ImageLockMode.ReadOnly, PixelFormat.Format64bppArgb);
unsafe
{
byte* pointerToImageData = (byte*)binaryImage.Scan0;
int numberOfPixels = bitmap.Width * bitmap.Height;
isTransparent = false;
// 8 bytes = 64 bits, since our image is 64bppArgb.
for (int i = 0; i < numberOfPixels * 8; i += 8)
{
// Check the last two bytes (transparency channel). First six bytes are for R, G and B channels. (0, 32) means 100% opacity.
if (pointerToImageData[i + 6] != 0 || pointerToImageData[i + 7] != 32)
{
isTransparent = true;
break;
}
}
}
bitmap.UnlockBits(binaryImage);
}
else
{
// No transparency available for this image.
isTransparent = false;
}
return isTransparent;
}
}
Pros:
Binary access, much faster than GetPixel,
Does not require additional libraries nor WPF,
Works with any format supported by GDI+: BMP, GIF, JPEG, PNG, TIFF, Exif, WMF, and EMF.
Cons:
Requires unsafe,
Is slower than reading PNG file directly.
Palettes
A less manual approach would be to use palettes. There might probably exist some .NET Framework or third party libraries which let you do that. I tried the following (using WPF):
using (Stream imageStreamSource = new FileStream(fullName, FileMode.Open, FileAccess.Read, FileShare.Read))
{
PngBitmapDecoder decoder = new PngBitmapDecoder(imageStreamSource, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);
BitmapSource bitmapSource = decoder.Frames[0];
return bitmapSource.Palette.Colors.Any(c => c.A != 0);
}
but I does not work, since bitmapSource.Palette is null most of the time. Further, using palettes will heavily decrease performance, compared to the first snippet, since every color must be loaded into a list of colors before proceeding.
Find out what? If the image HAS transparency? You could check the bit depth, 24 bit (RGB) usually means there is no transparency, and 32 bit (RGBA) means there is an opacity/transparency layer
You can sometimes tell that it definitely doesn't have transparency if either the bit depth is 24 or if it's lower and none of the palette members contain a relevant alpha value (you don't say whether you want to count partial transparency as transparent or not).
However, to be sure that there actually is some transparency, does require one to examine the entire image. Hence, it's O(n) for the stream size (which is roughly O(x * y) for the image size), but with a possible short-cut from the header for some cases.
If the png is indexed then you can check the TRNS chunk Png chunks description. If not then you need the get it pixel by pixel as you do it in that method.
Thanks everyone. Got it to work by using ChrisF's link
Determine if Alpha Channel is Used in an Image
Thanks ChrisF.
Here is my code:
private bool IsImageTransparent(Bitmap image)
{
for (int i = 0; i < image.Width; i++)
for (int j = 0; j < image.Height; j++)
{
var pixel = image.GetPixel(i, j);
if (pixel.A != 255)
return true;
}
return false;
}

Display captured Jpeg File

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);
});

Converting from a Format8bppIndexed to a Format24bppRgb in C#/GDI+

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.

Categories