Reputation: 16908
See: Save a 32-bit Bitmap as 1-bit .bmp file in C#
Listing #1
public static Bitmap BitmapTo1Bpp(Bitmap source)
{
int Width = source.Width;
int Height = source.Height;
Bitmap dest = new Bitmap(Width, Height, PixelFormat.Format1bppIndexed);
BitmapData destBmpData = dest.LockBits(new Rectangle(0, 0, Width, Height), ImageLockMode.ReadWrite, PixelFormat.Format1bppIndexed);
byte[] destBytes = new byte[(Width + 7) / 8];//19 bytes
for (int y = 0; y < Height; y++)
{
for (int x = 0; x < Width; x++)
{
Color c = source.GetPixel(x, y);
if (x % 8 == 0)
{
destBytes[x / 8] = 0;
}
if (c.GetBrightness() >= 0.5)
{
destBytes[x / 8] |= (byte)(0x80 >> (x % 8));
}
}
Marshal.Copy(destBytes, 0, (IntPtr)((long)destBmpData.Scan0 + destBmpData.Stride * y), destBytes.Length);
}
dest.UnlockBits(destBmpData);
return dest;
}
Listing #2
public static Bitmap BitmapTo1Bpp222(Bitmap source)
{
int Width = source.Width;
int Height = source.Height;
Bitmap dest = new Bitmap(Width, Height, PixelFormat.Format1bppIndexed);
BitmapData destBmpData = dest.LockBits(new Rectangle(0, 0, Width, Height), ImageLockMode.ReadWrite, PixelFormat.Format1bppIndexed);
int destStride = destBmpData.Stride;
int destSize = Math.Abs(destStride) * Height;
byte[] destBytes = new byte[destSize];
for (int y = 0; y < Height; y++)
{
for (int x = 0; x < Width; x++)
{
Color c = source.GetPixel(x, y);
if (x % 8 == 0)
{
destBytes[x*y / 8] = 0;
}
if (c.GetBrightness() >= 0.5)
{
destBytes[x*y / 8] |= (byte)(0x80 >> (x % 8));
}
}
}
Marshal.Copy(destBytes, 0, destBmpData.Scan0, destBytes.Length);
dest.UnlockBits(destBmpData);
return dest;
}
See the position of Marshal.Copy()
.
Why does the Listing #1 work, but Listing #2 doesn't?
What modification can make the Listing #2 work?
Upvotes: 1
Views: 1557
Reputation: 5629
Both of these are overly complicated. LockBits
can convert data to 1bpp. Just open the source as 1bpp, copy its data into the new 1bpp image, and you're done.
I'm also quite baffled by the combination of GetPixel
and LockBits
. Usually, using LockBits
means you realized that GetPixel
is a horribly slow waste of time that performs a LockBits
internally on every call.
public static Bitmap BitmapTo1Bpp(Bitmap source)
{
Rectangle rect = new Rectangle(0, 0, source.Width, source.Height);
Bitmap dest = new Bitmap(rect.Width, rect.Height, PixelFormat.Format1bppIndexed);
dest.SetResolution(source.HorizontalResolution, source.VerticalResolution);
BitmapData sourceData = source.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format1bppIndexed);
BitmapData targetData = dest.LockBits(rect, ImageLockMode.WriteOnly, PixelFormat.Format1bppIndexed);
Int32 actualDataWidth = (rect.Width + 7) / 8;
Int32 h = source.Height;
Int32 origStride = sourceData.Stride;
Int32 targetStride = targetData.Stride;
// buffer for one line of image data.
Byte[] imageData = new Byte[actualDataWidth];
Int64 sourcePos = sourceData.Scan0.ToInt64();
Int64 destPos = targetData.Scan0.ToInt64();
// Copy line by line, skipping by stride but copying actual data width
for (Int32 y = 0; y < h; y++)
{
Marshal.Copy(new IntPtr(sourcePos), imageData, 0, actualDataWidth);
Marshal.Copy(imageData, 0, new IntPtr(destPos), actualDataWidth);
sourcePos += origStride;
destPos += targetStride;
}
dest.UnlockBits(targetData);
source.UnlockBits(sourceData);
return dest;
}
Do note that conversion of data to indexed formats should be avoided in cases where your result is not 1bpp for pure black and white. Indexed formats are paletted, and doing it this way will not do any kind of reduction to an optimised palette approaching the image colours; it will just change the colours on the image to their closest match on the standard palette for this bit depth. For 1bpp this is just black and white, which is perfect, but for 4bpp and 8bpp it will give pretty bad results.
Also note that for some reason you can't convert from a higher to a lower indexed pixel format; it will throw an exception. Since you can convert a bitmap to 32-bit using the new Bitmap(Bitmap)
constructor, this problem can easily be avoided by calling the code like this:
public static Bitmap ConvertTo1Bpp(Bitmap source)
{
PixelFormat sourcePf = source.PixelFormat;
if ((sourcePf & PixelFormat.Indexed) == 0 || Image.GetPixelFormatSize(sourcePf) == 1)
return BitmapTo1Bpp(source);
using (Bitmap bm32 = new Bitmap(source))
return BitmapTo1Bpp(bm32);
}
Upvotes: 3