I am creating placeholder images in certain sizes that will be used as Data URIs
Bitmap bitmap = new Bitmap(16, 10);
I have done some research, but can't find a good way of saving this bitmap as the smallest possible filesize, which is why I want an 8bit PNG.
My question is: How can I save this bitmap into a file/bytearray/stream as an 8bit PNG? Any good libraries?
You can do this with nQuant (which you can install with nuget, or see references below). The following example converts an image on disk and would be readily adapted to meet your needs.
public static bool TryNQuantify(string inputFilename, string outputFilename)
{
var quantizer = new nQuant.WuQuantizer();
var bitmap = new Bitmap(inputFilename);
if (bitmap.PixelFormat != System.Drawing.Imaging.PixelFormat.Format32bppArgb)
{
ConvertTo32bppAndDisposeOriginal(ref bitmap);
}
try
{
using (var quantized = quantizer.QuantizeImage(bitmap))
{
quantized.Save(outputFilename, System.Drawing.Imaging.ImageFormat.Png);
}
}
catch
{
return false;
}
finally
{
bitmap.Dispose();
}
return true;
}
private static void ConvertTo32bppAndDisposeOriginal(ref Bitmap img)
{
var bmp = new Bitmap(img.Width, img.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
using (var gr = Graphics.FromImage(bmp))
gr.DrawImage(img, new Rectangle(0, 0, img.Width, img.Height));
img.Dispose();
img = bmp;
}
For more information see:
https://github.com/mcychan/nQuant.cs
https://code.msdn.microsoft.com/windowsdesktop/Convert-32-bit-PNGs-to-81ef8c81
I like the FreeImage project, it is light weight and easy to use. Below is an example of creating a transparent png. You could easily wrap this in a method and set the width and height and transparency value.
//create a new bit map
FIBITMAP dib = new FIBITMAP();
//allocate a 16x10 bitmap with 8bit depth
dib = FreeImage.Allocate(16, 10, 8);
//set the transpareny
byte[] Transparency = new byte[1];
Transparency[0] = 0x00;
FreeImage.SetTransparencyTable(dib, Transparency);
//save the bitmap
FreeImage.Save(FREE_IMAGE_FORMAT.FIF_PNG, dib, "C:\\temp\\tp.png", FREE_IMAGE_SAVE_FLAGS.DEFAULT);
If all you need is a small transparent image, why stop at 8 bit? You can go straight down to 1 bit! You only need one colour anyway, and it'll be even smaller.
In fact, you don't even need to do anything special for that. Since the pixels on a new indexed bitmap will all default to 0, meaning they reference its first palette colour, all you need to do is make a new 1bpp image, and set that first palette colour to transparent:
public static Bitmap MakePlaceholderImage(Int32 width, Int32 height)
{
Bitmap bm = new Bitmap(width, height, PixelFormat.Format1bppIndexed);
// This colour can't be assigned directly since the .Palette getter actually makes a copy of the palette.
ColorPalette pal = bm.Palette;
pal.Entries[0] = Color.Transparent;
bm.Palette = pal;
return bm;
}
I experimented a bit with this, and, saved as png, the end result seemed to consistently be 8 times smaller than the result of the same code executed with 8bpp. For a 5000x5000 image, the file size as png was barely over 3 KiB.
Google search led me to a 6 year old thread and I can't find my solution. It should've been possible 6 years as well, as it uses the System.Drawing Libraray of the .Net Framework.
This example code should help anyone who want to write an 8 bit indexed png.
static class SaveImages
{
public static void SaveGrayImage(Bitmap srcImg, string name)
{
Bitmap grayImg = new Bitmap(srcImg.Width, srcImg.Height, PixelFormat.Format8bppIndexed);
var pal = grayImg.Palette;
foreach (int i in Enumerable.Range(0, 256))
pal.Entries[i] = Color.FromArgb(255, i, i, i);
grayImg.Palette = pal;
var data = grayImg.LockBits(new Rectangle(0, 0, srcImg.Width, srcImg.Height), ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed);
var bytes = new byte[data.Stride * data.Height];
foreach (int y in Enumerable.Range(0, srcImg.Height))
{
foreach (int x in Enumerable.Range(0, srcImg.Width))
{
var colour = srcImg.GetPixel(x, y);
var c = (int)colour.R + (int)colour.G + (int)colour.B;
c /= 3;
bytes[data.Stride * y + x] = (byte)c;
}
}
Marshal.Copy(bytes, 0, data.Scan0, bytes.Length);
grayImg.Save(name, ImageFormat.Png);
}
}
Edit: To add transparency dabble with the color palette.
1) Create your bitmap with 8 bit per pixel format:
Bitmap bmp = new Bitmap(20, 20, System.Drawing.Imaging.PixelFormat.Format8bppIndexed);
See PixelFormat for more formats.
2) Draw to the bitmap ...
3) Then save it as PNG:
bmp.Save(#"c:\temp\xyz.png", System.Drawing.Imaging.ImageFormat.Png);
The created file will have the same pixel format as bmp (8 bit)
Related
I wrote some code to create ico files from any png, jpg, etc. images. The icons seem to be getting created correctly, and looks almost like the original image, when opened in Paint3d. Here is how it looks:
But when setting the image as a thumbnail to a folder, it looks weird and shiny.
Here is how it looks in windows file explorer:
Firstly, I would like to know if this is an issue in Windows itself, or is it code related? If this is Windows related, the code doesn't matter. If not, here it is:
I picked up a couple of code snippets from across the internet, so probably some non-optimized code, but here is the meat of my code:
//imagePaths => all images which I am converting to ico files
imagePaths.ForEach(imgPath => {
//create a temp png at this path after changing the original img to a squared img
var tempPNGpath = Path.Combine(icoDirPath, imgName.Replace(ext, ".png"));
var icoPath = tempPNGpath.Replace(".png", ".ico");
using (FileStream fs1 = File.OpenWrite(tempPNGpath)) {
Bitmap b = ((Bitmap)Image.FromFile(imgPath));
b = b.CopyToSquareCanvas(Color.Transparent);
b.Save(fs1, ImageFormat.Png);
fs1.Flush();
fs1.Close();
ConvertToIco(b, icoPath, 256);
}
File.Delete(tempPNGpath);
});
public static void ConvertToIco(Image img, string file, int size) {
Icon icon;
using (var msImg = new MemoryStream())
using (var msIco = new MemoryStream()) {
img.Save(msImg, ImageFormat.Png);
using (var bw = new BinaryWriter(msIco)) {
bw.Write((short)0); //0-1 reserved
bw.Write((short)1); //2-3 image type, 1 = icon, 2 = cursor
bw.Write((short)1); //4-5 number of images
bw.Write((byte)size); //6 image width
bw.Write((byte)size); //7 image height
bw.Write((byte)0); //8 number of colors
bw.Write((byte)0); //9 reserved
bw.Write((short)0); //10-11 color planes
bw.Write((short)32); //12-13 bits per pixel
bw.Write((int)msImg.Length); //14-17 size of image data
bw.Write(22); //18-21 offset of image data
bw.Write(msImg.ToArray()); // write image data
bw.Flush();
bw.Seek(0, SeekOrigin.Begin);
icon = new Icon(msIco);
}
}
using (var fs = new FileStream(file, FileMode.Create, FileAccess.Write))
icon.Save(fs);
}
In the Extension class, the method goes:
public static Bitmap CopyToSquareCanvas(this Bitmap sourceBitmap, Color canvasBackground) {
int maxSide = sourceBitmap.Width > sourceBitmap.Height ? sourceBitmap.Width : sourceBitmap.Height;
Bitmap bitmapResult = new Bitmap(maxSide, maxSide, PixelFormat.Format32bppArgb);
using (Graphics graphicsResult = Graphics.FromImage(bitmapResult)) {
graphicsResult.Clear(canvasBackground);
int xOffset = (maxSide - sourceBitmap.Width) / 2;
int yOffset = (maxSide - sourceBitmap.Height) / 2;
graphicsResult.DrawImage(sourceBitmap, new Rectangle(xOffset, yOffset, sourceBitmap.Width, sourceBitmap.Height));
}
return bitmapResult;
}
The differences in scaling are the result of the fact you're not doing the scaling yourself.
The icon format technically only supports images up to 256x256. You have code to make a square image out of the given input, but you never resize it to 256x256, meaning you end up with an icon file in which the header says the image is 256x256, but which is really a lot larger. This is against the format specs, so you are creating a technically corrupted ico file. The strange differences you're seeing are a result of different downscaling methods the OS is using in different situations to remedy this situation.
So the solution is simple: resize the image to 256x256 before putting it into the icon.
If you want more control over any smaller display sizes for the icon, you can add code to resize it to a number of classic used formats, like 16x16, 32x32, 64x64 and 128x128, and put them all in an icon file together. I have written an answer to another question that details the process of putting multiple images into a single icon:
A: Combine System.Drawing.Bitmap[] -> Icon
There are quite a few other oddities in your code, though:
I see no reason to save your in-between image as png file. That whole fs1 stream serves no purpose at all. You never use or load the temp file; you just keep using the b variable, which does not need anything written to disk.
There is no point in first making the icon in a MemoryStream, then loading that as Icon class through its file loading function, and then saving that to a file. You can just write the contents of that stream straight to a file, or, heck, use a FileStream right away.
As I noted in the comments, Bitmap is a disposable class, so any bitmap objects you create should be put in using statements as well.
The adapted loading code, with the temp png writing removed, and the using statements and resizes added:
public static void WriteImagesToIcons(List<String> imagePaths, String icoDirPath)
{
// Change this to whatever you prefer.
InterpolationMode scalingMode = InterpolationMode.HighQualityBicubic;
//imagePaths => all images which I am converting to ico files
imagePaths.ForEach(imgPath =>
{
// The correct way of replacing an extension
String icoPath = Path.Combine(icoDirPath, Path.GetFileNameWithoutExtension(imgPath) + ".ico");
using (Bitmap orig = new Bitmap(imgPath))
using (Bitmap squared = orig.CopyToSquareCanvas(Color.Transparent))
using (Bitmap resize16 = squared.Resize(16, 16, scalingMode))
using (Bitmap resize32 = squared.Resize(32, 32, scalingMode))
using (Bitmap resize48 = squared.Resize(48, 48, scalingMode))
using (Bitmap resize64 = squared.Resize(64, 64, scalingMode))
using (Bitmap resize96 = squared.Resize(96, 96, scalingMode))
using (Bitmap resize128 = squared.Resize(128, 128, scalingMode))
using (Bitmap resize192 = squared.Resize(192, 192, scalingMode))
using (Bitmap resize256 = squared.Resize(256, 256, scalingMode))
{
Image[] includedSizes = new Image[]
{ resize16, resize32, resize48, resize64, resize96, resize128, resize192, resize256 };
ConvertImagesToIco(includedSizes, icoPath);
}
});
}
The CopyToSquareCanvas remains the same, so I didn't copy it here. The Resize function is fairly simple: just use Graphics.DrawImage to paint the picture on a different-sized canvas, after setting the desired interpolation mode.
public static Bitmap Resize(this Bitmap source, Int32 width, Int32 height, InterpolationMode scalingMode)
{
Bitmap result = new Bitmap(width, height, PixelFormat.Format32bppArgb);
using (Graphics g = Graphics.FromImage(result))
{
// Set desired interpolation mode here
g.InterpolationMode = scalingMode;
g.PixelOffsetMode = PixelOffsetMode.Half;
g.DrawImage(source, new Rectangle(0, 0, width, height), new Rectangle(0, 0, source.Width, source.Height), GraphicsUnit.Pixel);
}
return result;
}
And, finally, the above-linked Bitmap[] to Icon function, slightly tweaked to write to a FileStream directly instead of loading the result into an Icon object:
public static void ConvertImagesToIco(Image[] images, String outputPath)
{
if (images == null)
throw new ArgumentNullException("images");
Int32 imgCount = images.Length;
if (imgCount == 0)
throw new ArgumentException("No images given!", "images");
if (imgCount > 0xFFFF)
throw new ArgumentException("Too many images!", "images");
using (FileStream fs = new FileStream(outputPath, FileMode.Create, FileAccess.Write))
using (BinaryWriter iconWriter = new BinaryWriter(fs))
{
Byte[][] frameBytes = new Byte[imgCount][];
// 0-1 reserved, 0
iconWriter.Write((Int16)0);
// 2-3 image type, 1 = icon, 2 = cursor
iconWriter.Write((Int16)1);
// 4-5 number of images
iconWriter.Write((Int16)imgCount);
// Calculate header size for first image data offset.
Int32 offset = 6 + (16 * imgCount);
for (Int32 i = 0; i < imgCount; ++i)
{
// Get image data
Image curFrame = images[i];
if (curFrame.Width > 256 || curFrame.Height > 256)
throw new ArgumentException("Image too large!", "images");
// for these three, 0 is interpreted as 256,
// so the cast reducing 256 to 0 is no problem.
Byte width = (Byte)curFrame.Width;
Byte height = (Byte)curFrame.Height;
Byte colors = (Byte)curFrame.Palette.Entries.Length;
Int32 bpp;
Byte[] frameData;
using (MemoryStream pngMs = new MemoryStream())
{
curFrame.Save(pngMs, ImageFormat.Png);
frameData = pngMs.ToArray();
}
// Get the colour depth to save in the icon info. This needs to be
// fetched explicitly, since png does not support certain types
// like 16bpp, so it will convert to the nearest valid on save.
Byte colDepth = frameData[24];
Byte colType = frameData[25];
// I think .Net saving only supports colour types 2, 3 and 6 anyway.
switch (colType)
{
case 2: bpp = 3 * colDepth; break; // RGB
case 6: bpp = 4 * colDepth; break; // ARGB
default: bpp = colDepth; break; // Indexed & greyscale
}
frameBytes[i] = frameData;
Int32 imageLen = frameData.Length;
// Write image entry
// 0 image width.
iconWriter.Write(width);
// 1 image height.
iconWriter.Write(height);
// 2 number of colors.
iconWriter.Write(colors);
// 3 reserved
iconWriter.Write((Byte)0);
// 4-5 color planes
iconWriter.Write((Int16)0);
// 6-7 bits per pixel
iconWriter.Write((Int16)bpp);
// 8-11 size of image data
iconWriter.Write(imageLen);
// 12-15 offset of image data
iconWriter.Write(offset);
offset += imageLen;
}
for (Int32 i = 0; i < imgCount; i++)
{
// Write image data
// png data must contain the whole png data file
iconWriter.Write(frameBytes[i]);
}
iconWriter.Flush();
}
}
I'm currently loading and saving Texture2Ds used as mapped images into a database so they can be preloaded later. Each color channel needs to be the precise color it was when saved after loading. The problem is Texture2D.FromStream sometimes returns incorrect color channels that are sometimes off by 1 or so. This is unacceptable as the mapped colors are useless if they are incorrect.
The example below provides a situation where an RGB of 255 is changed to an RGB of 254 when the alpha is set at 100. When setting the alpha to 1 or 255 they return as 255 correctly, other alpha values cause the same issue as 100.
Texture2D tex = new Texture2D(GraphicsDevice, 20, 20, false, SurfaceFormat.Color);
Color[] data = new Color[tex.Width * tex.Height];
for (int i = 0; i < data.Length; i++) {
data[i] = new Color(255, 255, 255, 100);
}
tex.SetData<Color>(data);
using (Stream stream = File.Open("TestAlpha.png", FileMode.OpenOrCreate)) {
tex.SaveAsPng(stream, tex.Width, tex.Height);
stream.Position = 0;
tex = Texture2D.FromStream(GraphicsDevice, stream);
tex.GetData<Color>(data);
Console.WriteLine(data[0]); // Returns (R:254 G:254 B:254 A:100)
}
I have confirmed the png has the correct RGB of 255 when looking at the saved image in Paint.NET so it can only be something caused during Texture2D.FromStream.
Well, I did not find the cause of Texture2D.FromStream's issue but I found an all-around better way to load Texture2Ds. In this case I load GDI Bitmap from the stream instead, take its data, and transfer it over to a newly created Texture2D.
I plan on using this whether or not Texture2D.FromSteam is fixable but I'd still love to know if anyone knows what's going on with it.
For those of you who are new, make sure to include System.Drawing as a reference as its not present in XNA projects by default.
public static unsafe Texture2D FromStream(GraphicsDevice graphicsDevice, Stream stream) {
// Load through GDI Bitmap because it doesn't cause issues with alpha
using (Bitmap bitmap = (Bitmap) Bitmap.FromStream(stream)) {
// Create a texture and array to output the bitmap to
Texture2D texture = new Texture2D(graphicsDevice,
bitmap.Width, bitmap.Height, false, SurfaceFormat.Color);
Color[] data = new Color[bitmap.Width * bitmap.Height];
// Get the pixels from the bitmap
Rectangle rect = new Rectangle(0, 0, bitmap.Width, bitmap.Height);
BitmapData bmpData = bitmap.LockBits(rect, ImageLockMode.ReadOnly,
PixelFormat.Format32bppArgb);
// Write the pixels to the data buffer
byte* ptr = (byte*) bmpData.Scan0;
for (int i = 0; i < data.Length; i++) {
// Go through every color and reverse red and blue channels
data[i] = new Color(ptr[2], ptr[1], ptr[0], ptr[3]);
ptr += 4;
}
bitmap.UnlockBits(bmpData);
// Assign the data to the texture
texture.SetData<Color>(data);
// Fun fact: All this extra work is actually 50% faster than
// Texture2D.FromStream! It's not only broken, but slow as well.
return texture;
}
}
My professor kind of "challenged me" to create an application that draws pixel by pixel an image converted in Bitmap, where it's data is saved in some sort of binary that I can't wrap my head around.
Here's the example given to me:
const byte image[]={
B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,
B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,
B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,
B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,
B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,
ect ect ect
Now, if the byte data type saves numbers that go from 0 to 255, how is this possible? In the sample code that I was given, there is also the use of "Word" data type but in my IDE it seems like it doesn't exist.
I already wrote the code that converts any image given in input into a bitmap:
FileStream fs = new FileStream(openFileDialog1.FileName, FileMode.Open, FileAccess.Read); //Path is image location
Byte[] bindata = new byte[Convert.ToInt32(fs.Length)];
fs.Read(bindata, 0, Convert.ToInt32(fs.Length));
Bitmap bmp;
using (var ms = new MemoryStream(bindata))
{
bmp = new Bitmap(ms);
}
pictureBox1.Image = bmp; //For now, I just display the converted image on screen
Now I suppose that the next step is to draw the image byte per byte, but I can't get my head around this binary thing and the word data type.. Any kind of help is appreciated :)
if you just want to draw a bitmap pixel at a time, you can do something like this:
Bitmap b = new Bitmap(10, 10);
b.SetPixel(0, 0, Color.Black);
b.SetPixel(1, 3, Color.Red);
pictureBox1.Image = b;
You can just copy your bytes to the Bitmap's memory buffer itself.
BitmapData bufferData = buffer.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed);
for (int x = 0; x < width; x++)
for (int y = 0; y < height; y++)
bufferData.SetPixel(x, y, CELL_DEAD);
buffer.UnlockBits(bufferData);
//////////
public static unsafe void SetPixel(BitmapData data, int x, int y, byte pixel)
{
*((byte*)data.Scan0 + y * data.Stride + x) = pixel;
}
I've used it as unsafe but you can play your magic with IntPtr. Of course, you must play your own with width-height synchronization.
UPD: set PixelFormat with care. PixelFormat.Format8bppIndexed is what you need if your colors are in default 256-color palette or you want to define your own palette.
public void SaveAsJpeg(Stream stream, int width, int height)
RenderTarget2D.SaveAsJpeg() is missing a parameter to set the Jpeg quality and the output has lots of artifacts. (increasing the size of the image does help a bit but it still looks bad)
Is that parameter hidden somewhere in XNA? How can I improve the quality of the jpeg?
There is no quality parameter in the XNA 4.0 API for encoding JPEG images.
As a very round-about way of doing things, you may be able to create a Silverlight WriteableBitmap, copy the data out of your render target and into that, and then use Extensions.SaveJpeg to save it with a custom quality level.
A better alternative might be to simply save a PNG image.
I will agree with Andrew Russel that a much better alternative would be to simply save to PNG, but if you simply HAVE to save to jpg, then there is a much better way than go trhough WriteableBitmap.
What you can do is create an empty Bitmap object and transfer the texture data over so you will do a Texture2D to Bitmap convertion. Simply copy from below:
public static Bitmap ToBitmap(this Microsoft.Xna.Framework.Graphics.Texture2D rd, int Width, int Height)
{
var Bmp = new Bitmap(Width, Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
byte[] data = ToBytes(rd);
var bmpData = Bmp.LockBits(new Rectangle(0, 0, rd.Width, rd.Height), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
System.Runtime.InteropServices.Marshal.Copy(data, 0, bmpData.Scan0, data.Length);
Bmp.UnlockBits(bmpData);
return Bmp;
}
public static byte[] ToBytes(this Microsoft.Xna.Framework.Graphics.Texture2D rd)
{
byte[] data = new byte[4 * rd.Height * rd.Width];
rd.GetData<byte>(data);
SwapBytes(data);
return data;
}
private static void SwapBytes(byte[] data)
{
System.Threading.Tasks.ParallelOptions po = new System.Threading.Tasks.ParallelOptions();
po.MaxDegreeOfParallelism = -1;
System.Threading.Tasks.Parallel.For(0, data.Length / 4, po, t =>
{
int bi = t * 4;
byte temp = data[bi];
data[bi] = data[bi + 2];
data[bi + 2] = temp;
});
}
Note that the above is pretty efficient, but with certain drawbacks. A similar code to the above can be used to very quickly achieve a Bitmap to Texture2D convertion, but without premultiplying alpha.
I need to convert bitonal (black and white) TIFF files into another format for display by a web browser, currently we're using JPGs, but the format isn't crucial. From reading around .NET doesn't seem to easily support writing bitonal images, so we're ending up with ~1MB files instead of ~100K ones. I'm considering using ImageMagick to do this, but ideally i'd like a solution which doesn't require this if possible.
Current code snippet (which also does some resizing on the image):
using (Image img = Image.FromFile(imageName))
{
using (Bitmap resized = new Bitmap(resizedWidth, resizedHeight)
{
using (Graphics g = Graphics.FromImage(resized))
{
g.DrawImage(img, new Rectangle(0, 0, resized.Width, resized.Height), 0, 0, img.Width, img.Height, GraphicsUnit.Pixel);
}
resized.Save(outputFilename, System.Drawing.Imaging.ImageFormat.Jpeg);
}
}
Is there any way to achieve this?
Thanks.
I believe the problem can be solved by checking that resized bitmap is of PixelFormat.Format1bppIndexed. If it's not, you should convert it to 1bpp bitmap and after that you can save it as black and white png without problems.
In other words, you should use following code instead of resized.Save(outputFilename, System.Drawing.Imaging.ImageFormat.Jpeg);
if (resized.PixelFormat != PixelFormat.Format1bppIndexed)
{
using (Bitmap bmp = convertToBitonal(resized))
bmp.Save(outputFilename, System.Drawing.Imaging.ImageFormat.Png);
}
else
{
resized.Save(outputFilename, System.Drawing.Imaging.ImageFormat.Png);
}
I use following code for convertToBitonal :
private static Bitmap convertToBitonal(Bitmap original)
{
int sourceStride;
byte[] sourceBuffer = extractBytes(original, out sourceStride);
// Create destination bitmap
Bitmap destination = new Bitmap(original.Width, original.Height,
PixelFormat.Format1bppIndexed);
destination.SetResolution(original.HorizontalResolution, original.VerticalResolution);
// Lock destination bitmap in memory
BitmapData destinationData = destination.LockBits(
new Rectangle(0, 0, destination.Width, destination.Height),
ImageLockMode.WriteOnly, PixelFormat.Format1bppIndexed);
// Create buffer for destination bitmap bits
int imageSize = destinationData.Stride * destinationData.Height;
byte[] destinationBuffer = new byte[imageSize];
int sourceIndex = 0;
int destinationIndex = 0;
int pixelTotal = 0;
byte destinationValue = 0;
int pixelValue = 128;
int height = destination.Height;
int width = destination.Width;
int threshold = 500;
for (int y = 0; y < height; y++)
{
sourceIndex = y * sourceStride;
destinationIndex = y * destinationData.Stride;
destinationValue = 0;
pixelValue = 128;
for (int x = 0; x < width; x++)
{
// Compute pixel brightness (i.e. total of Red, Green, and Blue values)
pixelTotal = sourceBuffer[sourceIndex + 1] + sourceBuffer[sourceIndex + 2] +
sourceBuffer[sourceIndex + 3];
if (pixelTotal > threshold)
destinationValue += (byte)pixelValue;
if (pixelValue == 1)
{
destinationBuffer[destinationIndex] = destinationValue;
destinationIndex++;
destinationValue = 0;
pixelValue = 128;
}
else
{
pixelValue >>= 1;
}
sourceIndex += 4;
}
if (pixelValue != 128)
destinationBuffer[destinationIndex] = destinationValue;
}
Marshal.Copy(destinationBuffer, 0, destinationData.Scan0, imageSize);
destination.UnlockBits(destinationData);
return destination;
}
private static byte[] extractBytes(Bitmap original, out int stride)
{
Bitmap source = null;
try
{
// If original bitmap is not already in 32 BPP, ARGB format, then convert
if (original.PixelFormat != PixelFormat.Format32bppArgb)
{
source = new Bitmap(original.Width, original.Height, PixelFormat.Format32bppArgb);
source.SetResolution(original.HorizontalResolution, original.VerticalResolution);
using (Graphics g = Graphics.FromImage(source))
{
g.DrawImageUnscaled(original, 0, 0);
}
}
else
{
source = original;
}
// Lock source bitmap in memory
BitmapData sourceData = source.LockBits(
new Rectangle(0, 0, source.Width, source.Height),
ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
// Copy image data to binary array
int imageSize = sourceData.Stride * sourceData.Height;
byte[] sourceBuffer = new byte[imageSize];
Marshal.Copy(sourceData.Scan0, sourceBuffer, 0, imageSize);
// Unlock source bitmap
source.UnlockBits(sourceData);
stride = sourceData.Stride;
return sourceBuffer;
}
finally
{
if (source != original)
source.Dispose();
}
}
Have you tried saving using the Image.Save overload with Encoder parameters?
Like the Encoder.ColorDepth Parameter?
Trying jaroslav's suggestion for color depth doesn't work:
static void Main(string[] args)
{
var list = ImageCodecInfo.GetImageDecoders();
var jpegEncoder = list[1]; // i know this is the jpeg encoder by inspection
Bitmap bitmap = new Bitmap(500, 500);
Graphics g = Graphics.FromImage(bitmap);
g.DrawRectangle(new Pen(Color.Red), 10, 10, 300, 300);
var encoderParams = new EncoderParameters();
encoderParams.Param[0] = new EncoderParameter(Encoder.ColorDepth, 2);
bitmap.Save(#"c:\newbitmap.jpeg", jpegEncoder, encoderParams);
}
The jpeg is still a full color jpeg.
I don't think there is any support for grayscale jpeg in gdi plus. Have you tried looking in windows imaging component?
http://www.microsoft.com/downloads/details.aspx?FamilyID=8e011506-6307-445b-b950-215def45ddd8&displaylang=en
code example: http://www.codeproject.com/KB/GDI-plus/windows_imaging.aspx
wikipedia: http://en.wikipedia.org/wiki/Windows_Imaging_Component
This is an old thread. However, I'll add my 2 cents.
I use AForge.Net libraries (open source)
use these dlls. Aforge.dll, AForge.Imaging.dll
using AForge.Imaging.Filters;
private void ConvertBitmap()
{
markedBitmap = Grayscale.CommonAlgorithms.RMY.Apply(markedBitmap);
ApplyFilter(new FloydSteinbergDithering());
}
private void ApplyFilter(IFilter filter)
{
// apply filter
convertedBitmap = filter.Apply(markedBitmap);
}
Have you tried PNG with 1 bit color depth?
To achieve a size similar to a CCITT4 TIFF, I believe your image needs to use a 1-bit indexed pallette.
However, you can't use the Graphics object in .NET to draw on an indexed image.
You will probably have to use LockBits to manipulate each pixel.
See Bob Powell's excellent article.