Marshal.Copy/UnlockBits hangs - c#

A user selects a portion of an image for a cut and paste operation. I create a new bitmap, paste the selected portion in the new image, wipe the source array and paste it back into the old image. Works but at least half the time it hangs with Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
Any thoughts or help?
public BitmapSource CutToNew(double left, double top, double width, double height, double pageWidth, double pageHeight)
{
var destBmp = new Bitmap((int)pageWidth, (int)pageHeight);
var g = Graphics.FromImage(destBmp);
g.FillRectangle(new SolidBrush(Color.White), 0, 0,
(int)pageHeight, (int)pageWidth);
g.Dispose();
var croppedArea = new Rectangle((int)left, (int)top, (int)width, (int)height);
BitmapData croppedSource = _bitmapImage.LockBits(croppedArea,
ImageLockMode.ReadWrite, BitmapImage.PixelFormat);
var croppedDestArea = new Rectangle((int)left, (int)top, (int)width, (int)height);
BitmapData croppedDest = destBmp.LockBits(croppedDestArea,
ImageLockMode.WriteOnly, BitmapImage.PixelFormat);
// Create data array to hold bmpSource pixel data
int stride = croppedSource.Stride;
int numBytes = stride * (int)height;
var srcData = new byte[numBytes];
var destData = new byte[numBytes];
Marshal.Copy(croppedSource.Scan0, srcData, 0, numBytes);
//Tried creating a separate array in case that helped.
Array.Copy(srcData, destData, srcData.Length);
//Often hangs here with Attempted to read or write protected memory.
Marshal.Copy(destData, 0, croppedDest.Scan0, numBytes);
destBmp.UnlockBits(croppedDest);
var retVal = new DocAppImage {BitmapImage = destBmp};
destBmp.Dispose();
//Blank the source area
for (int y = 0; y < srcData.Length; y++)
srcData[y] = 0xFF;
Marshal.Copy(srcData, 0, croppedSource.Scan0, numBytes);
_bitmapImage.UnlockBits(croppedSource);
return retVal.bmpSource;
}
private Bitmap _bitmapImage;
public Bitmap BitmapImage
{
get
{
if (_bitmapImage != null)
return _bitmapImage;
if (FileImage != null)
{
var stream = new MemoryStream(FileImage); //Fileimage=TIFF read from file.
_bitmapImage = new Bitmap(stream);
return _bitmapImage;
}
return null;
}
set
{
if (value != null)
{
ImageCodecInfo codecInfo = GetImageCodecInfo("TIFF");
... implementation to set the bitmap image.

You may want to try specifying your PixelFormat when you create the new object.
For example:
var destBmp = new Bitmap((int)pageWidth, (int)pageHeight, PixelFormat.Format24bppRgb);

Related

Can't convert Bitmap to IplImage

I'm trying to make an image cropping tool.
I am following this https://www.codeproject.com/Articles/703519/Cropping-Particular-Region-In-Image-Using-Csharp
But since it's a bit old and the plugin/DLL's it uses have changed I have been trying to adapt his code from OpenCvSharp 2.0 to OpenCvSharp 2.4
When I'm converting the bitmaps to IplImages and using Cv.Mul() it gives me this error:
Attempted to read or write protected memory. This is often an indication that other memory is corrupt
I have never used OpenCvSharp other ways of creating the IplImage even reading the IplImage from a written image.
Code:
public static IplImage BitmapToIplImage(Bitmap bitmap)
{
IplImage tmp, tmp2;
System.Drawing.Rectangle bRect = new System.Drawing.Rectangle(new System.Drawing.Point(0, 0), new System.Drawing.Size((int)bitmap.Width, (int)bitmap.Height));
BitmapData bmData = bitmap.LockBits(bRect, ImageLockMode.ReadWrite, bitmap.PixelFormat);
tmp = Cv.CreateImage(Cv.Size(bitmap.Width, bitmap.Height), BitDepth.U8, 3);
tmp2 = Cv.CreateImage(Cv.Size(bitmap.Width, bitmap.Height), BitDepth.U8, 1);
byte[] data = new byte[Math.Abs(bmData.Stride * bmData.Height)];
tmp.SetData(bmData.Scan0, data.Length);
bitmap.UnlockBits(bmData);
// Cv.CvtColor(tmp, tmp2, ColorConversion.RgbToGray);
return tmp;
}
private void CropImage()
{
IplImage ipl = Cv.CreateImage(new CvSize(curBmp.Width, curBmp.Height), BitDepth.U8, 3);
Graphics ga = Graphics.FromImage(curBmp);
ga.FillRectangle(new SolidBrush(System.Drawing.Color.Black), new System.Drawing.Rectangle(0, 0, curBmp.Width, curBmp.Height));
SolidBrush brush = new SolidBrush(System.Drawing.Color.FromArgb(1, 1, 1));
curGraphics.FillClosedCurve(brush, imagePoints.ToArray());
Cv.Mul(BitmapToIplImage(curOgBmp), BitmapToIplImage(curBmp), ipl, 1);
ComputeCrop();
Stream s = null;
ipl.ToStream(s, ".png", null);
curBmp = new Bitmap(s);
RefreshImageViewer();
}
-----------------------------EDIT-----------------------------------------
I tried to follow what Markus posted, and I got it to work without any errors in the code.
Although the image cropped is a bit strange here are the methods I use, plus the RefreshImageViewer that is how I put the bitmap in the image control.
I have been trying to see if I missed something for hours, but I think not.
Output example: Imgur image link
Code:
public void RefreshImageViewer()
{
bmpSource = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
curBmp.GetHbitmap(),
IntPtr.Zero,
System.Windows.Int32Rect.Empty,
BitmapSizeOptions.FromWidthAndHeight(curBmp.Width, curBmp.Height));
imageViewer.Source = bmpSource;
curGraphics = Graphics.FromImage(curBmp);
}
private void CropImage()
{
Graphics Ga = Graphics.FromImage(curBmp);
//the black image
Ga.FillRectangle(new SolidBrush(System.Drawing.Color.Black), new System.Drawing.Rectangle(0, 0, curBmp.Width, curBmp.Height));
//draw from the last point to first point
Ga.DrawLine(new System.Drawing.Pen(System.Drawing.Color.Red, 3), imagePoints[imagePoints.Count - 1], imagePoints[0]);
//all of the rgb values are being set 1 inside the polygon
SolidBrush Brush = new SolidBrush(System.Drawing.Color.FromArgb(1, 1, 1));
//we have to prepare one mask of Multiplying operation for cropping region
curGraphics.FillPolygon(Brush, imagePoints.ToArray());
Mat accc = (BitmapToMat(curOgBmp).Mul(BitmapToMat(curBmp))).ToMat();
System.Drawing.Rectangle r = ComputeCrop();
curBmp = accc.ToBitmap().Clone(r, curOgBmp.PixelFormat);
RefreshImageViewer();
}
private System.Drawing.Rectangle ComputeCrop()
{
int smallestX = curBmp.Width, biggestX = 0, biggestY = 0, smallestY = curBmp.Height;
for (int i = 0; i < imagePoints.Count; i++)
{
biggestX = Math.Max(biggestX, imagePoints[i].X);
smallestX = Math.Min(smallestX, imagePoints[i].X);
biggestY = Math.Max(biggestY, imagePoints[i].Y);
smallestY = Math.Min(smallestY, imagePoints[i].Y);
}
System.Drawing.Rectangle rectCrop = new System.Drawing.Rectangle(smallestX, smallestY, biggestX - smallestX, biggestY - smallestY);
return rectCrop;
}
public static Mat BitmapToMat(Bitmap bitmap)
{
Mat tmp, tmp2;
System.Drawing.Rectangle bRect = new System.Drawing.Rectangle(new System.Drawing.Point(0, 0), new System.Drawing.Size((int)bitmap.Width, (int)bitmap.Height));
BitmapData bmData = bitmap.LockBits(bRect, ImageLockMode.ReadWrite, bitmap.PixelFormat);
tmp2 = new Mat(new OpenCvSharp.Size(bitmap.Width, bitmap.Height), MatType.CV_8U);
tmp = new Mat(bitmap.Height, bitmap.Width, MatType.CV_8UC3, bmData.Scan0);
bitmap.UnlockBits(bmData);
return tmp;
}
“IplImage” ist the old image container from OpenCv 1. As Andreas already mentioned, today you should use “Mat” instead. Have also a look here : Difference between cvMat, Mat and IpImage
Unfortunately, your code sample is not complete, hence I corrected the two methods from the original project ( https://www.codeproject.com/Articles/703519/Cropping-Particular-Region-In-Image-Using-Csharp).
The following methods are tested and work as intended in the original project in combination with the latest OpenCVSharp version (v4.x) . It should be very simple now to convert the changes to your code.
public static Mat BitmapToIplImage(Bitmap bitmap)
{
Mat tmp, tmp2;
Rectangle bRect = new Rectangle(new System.Drawing.Point(0, 0), new System.Drawing.Size((int)bitmap.Width, (int)bitmap.Height));
BitmapData bmData = bitmap.LockBits(bRect, ImageLockMode.ReadWrite, bitmap.PixelFormat);
tmp2 = new Mat(new Size(bitmap.Width, bitmap.Height), MatType.CV_8U);
tmp = new Mat(bitmap.Height, bitmap.Width, MatType.CV_8UC3, bmData.Scan0);
bitmap.UnlockBits(bmData);
return tmp;
}
private void crop()
{
timer1.Stop();
Graphics Ga = Graphics.FromImage(bmp);
//the black image
Ga.FillRectangle(new SolidBrush(Color.Black), new Rectangle(0, 0, bmp.Width, bmp.Height));
//draw from the last point to first point
Ga.DrawLine(new Pen(Color.Red, 3), polygonPoints[polygonPoints.Count - 1], polygonPoints[0]);
//all of the rgb values are being set 1 inside the polygon
SolidBrush Brush = new SolidBrush(Color.FromArgb(1, 1, 1));
//we have to prepare one mask of Multiplying operation for cropping region
G.FillClosedCurve(Brush, polygonPoints.ToArray());
var accc= (BitmapToIplImage(Source).Mul(BitmapToIplImage(bmp))).ToMat();
computecrop();
croplast = accc.ToBitmap().Clone(rectcrop, Source.PixelFormat);//just show cropped region part of image
pictureBox2.Image = croplast; // crop region of image
}

Error in Quantization of Image using NQUANT

I recently acquired the NuGet Package Nquant.
I plan to use this to reduce the file size of the bitmap and save it into PNG. But I get this error:
The image you are attempting to quantize does not contain a 32 bit ARGB palette. This image has a bit depth of 8 with 256 colors.
Does anyone here has used Nquant? And have you encountered this error and how did you fix it?
My code for your reference:
var bitmap = new Bitmap(width, jbgsize / height, PixelFormat.Format8bppIndexed);
ColorPalette pal = bitmap.Palette;
for (int i = 0; i <= 255; i++)
{
// create greyscale color table
pal.Entries[i] = Color.FromArgb(i, i, i);
}
bitmap.Palette = pal; // you need to re-set this property to force the new ColorPalette
var bitmap_data = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed);
Marshal.Copy(output, 0, bitmap_data.Scan0, output.Length);
bitmap.UnlockBits(bitmap_data);
MemoryStream stream = new MemoryStream();
var quantizer = new WuQuantizer();
using(var bmp = new Bitmap(bitmap))
{
using (var quantized = quantizer.QuantizeImage(bitmap))
{
quantized.Save(stream, ImageFormat.Png);
}
}
byteArray = stream.ToArray();
return byteArray.Concat(output).ToArray();
You can convert your image to Format32bppPArgb and then Quantize it.
This is my working example of that decrease image size for ~3 times.
public static byte[] CompressImageStream(byte[] imageStream)
{
using (var ms = new MemoryStream(imageStream))
using (var original = new Bitmap(ms))
using (var clonedWith32PixelsFormat = new Bitmap(
original.Width,
original.Height,
PixelFormat.Format32bppPArgb))
{
using (Graphics gr = Graphics.FromImage(clonedWith32PixelsFormat))
{
gr.DrawImage(
original,
new Rectangle(0, 0, clonedWith32PixelsFormat.Width, clonedWith32PixelsFormat.Height));
}
using (Image compressedImage = new WuQuantizer().QuantizeImage(clonedWith32PixelsFormat))
{
return ImageToByteArray(compressedImage);
}
}
}
public static byte[] ImageToByteArray(Image image)
{
if (image == null)
{
throw new ArgumentNullException(nameof(image));
}
using (var stream = new MemoryStream())
{
image.Save(stream, ImageFormat.Png);
return stream.ToArray();
}
}

How To split a big image into smaller A4 size pieces using DrawImage method of Graphics class

DrawImage method of Graphics class is not creating high quality images. In this method I am splitting a master image into multiple image but the code generating first image in very low quality. But it is generating full black images for remaining height.
public static Bitmap[] Split(byte[] ByteImage)
{
// MasterImage: there is no problem in master image. it is saving it in good quality.
MemoryStream ms = new MemoryStream(ByteImage);
System.Drawing.Image MasterImage = System.Drawing.Image.FromStream(ms);
MasterImage.Save(HttpContext.Current.Server.MapPath("../../../App_Shared/Reports/Temp/MasterImage.Bmp"), ImageFormat.Bmp);
//Split master image into multiple image according to height / 1000
Int32 ImageHeight = 1000, ImageWidth = MasterImage.Width, MasterImageHeight = MasterImage.Height;
int PageCount = 0;
Int32 TotalPages = MasterImage.Height / 1000;
Bitmap[] imgs = new Bitmap[TotalPages];
for (int y = 0; y + 1000 < MasterImageHeight; y += 1000, PageCount++)
{
imgs[PageCount] = new Bitmap(ImageWidth, ImageHeight, PixelFormat.Format32bppPArgb);
using (Graphics gr = Graphics.FromImage(imgs[PageCount]))
{
gr.CompositingQuality = CompositingQuality.HighQuality;
gr.InterpolationMode = InterpolationMode.HighQualityBicubic;
gr.SmoothingMode = SmoothingMode.HighQuality;
//First image now working with this code line
gr.DrawImage(MasterImage, new System.Drawing.Rectangle(0, y, ImageWidth, ImageHeight),new System.Drawing.Rectangle(0, 0, ImageWidth, ImageHeight), GraphicsUnit.Pixel); //new System.Drawing.Rectangle(new Point(0, y), new Size(ImageWidth, ImageHeight)));
//gr.DrawImage(MasterImage, new System.Drawing.Rectangle(0, y, ImageWidth, ImageHeight)); //new System.Drawing.Rectangle(new Point(0, y), new Size(ImageWidth, ImageHeight)));
string FilePath = HttpContext.Current.Server.MapPath("../../../App_Shared/Reports/Temp/Image" + PageCount.ToString() + ".bmp");
imgs[PageCount].Save(FilePath, System.Drawing.Imaging.ImageFormat.Bmp);
//Here it is saving images. I got first image with very poor quality but remaining in total balck color.
gr.Dispose();
}
}
return imgs;
}
As #HansPassant mentioned the source and target rectangle are reversed.
You could also change the structure of your splitting a bit so it could work a bit more flexible, and it might have a better readability at a later time.
class Program
{
static IList<Bitmap> SplitImage(Bitmap sourceBitmap, int splitHeight)
{
Size dimension = sourceBitmap.Size;
Rectangle sourceRectangle = new Rectangle(0, 0, dimension.Width, splitHeight);
Rectangle targetRectangle = new Rectangle(0, 0, dimension.Width, splitHeight);
IList<Bitmap> results = new List<Bitmap>();
while (sourceRectangle.Top < dimension.Height)
{
Bitmap pageBitmap = new Bitmap(targetRectangle.Size.Width, sourceRectangle.Bottom < dimension.Height ?
targetRectangle.Size.Height
:
dimension.Height - sourceRectangle.Top, PixelFormat.Format32bppArgb);
using (Graphics g = Graphics.FromImage(pageBitmap))
{
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBilinear;
g.DrawImage(sourceBitmap, targetRectangle, sourceRectangle, GraphicsUnit.Pixel);
}
sourceRectangle.Y += sourceRectangle.Height;
results.Add(pageBitmap);
}
return results;
}
static void Main(string[] args)
{
string sourceFilename = Environment.CurrentDirectory + #"\testimage.jpg";
Bitmap sourceBitmap = (Bitmap)Image.FromFile(sourceFilename);
var images = SplitImage(sourceBitmap, 79);
int len = images.Count;
for (int x = len; --x >= 0; )
{
var bmp = images[x];
string filename = "Images-" + x + ".bmp";
bmp.Save(Environment.CurrentDirectory + #"\" + filename, ImageFormat.Bmp);
images.RemoveAt(x);
bmp.Dispose();
Console.WriteLine("Saved " + filename);
}
Console.WriteLine("Done with the resizing");
}
}
This would also dynamically size the last image in case the page is less than your specified bitmap height at the end :)

How to detect a memory leak

I have doubts that this part of code causes memory leak:
public FileResult ShowCroppedImage(int id, int size)
{
string path = "~/Uploads/Photos/";
string sourceFile = Server.MapPath(path) + id + ".jpg";
MemoryStream stream = new MemoryStream();
var bitmap = imageManipulation.CropImage(sourceFile, size, size);
bitmap.Save(stream, System.Drawing.Imaging.ImageFormat.Jpeg);
Byte[] bytes = stream.ToArray();
return File(bytes, "image/png");
}
How could I make a test to see if this piece of code is the cause?
EDIT:
public Image CropImage(string sourceFile, int newWidth, int newHeight)
{
Image img = Image.FromFile(sourceFile);
Image outimage;
int sizeX = newWidth;
int sizeY = newHeight;
MemoryStream mm = null;
double ratio = 0;
int fromX = 0;
int fromY = 0;
if (img.Width < img.Height)
{
ratio = img.Width / (double)img.Height;
newHeight = (int)(newHeight / ratio);
fromY = (img.Height - img.Width) / 2;
}
else
{
ratio = img.Height / (double)img.Width;
newWidth = (int)(newWidth / ratio);
fromX = (img.Width - img.Height) / 2;
}
if (img.Width == img.Height)
fromX = 0;
Bitmap result = new Bitmap(sizeX, sizeY);
//use a graphics object to draw the resized image into the bitmap
Graphics grPhoto = Graphics.FromImage(result);
//set the resize quality modes to high quality
grPhoto.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
grPhoto.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
grPhoto.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
//draw the image into the target bitmap
//now do the crop
grPhoto.DrawImage(
img,
new System.Drawing.Rectangle(0, 0, newWidth, newHeight),
new System.Drawing.Rectangle(fromX, fromY, img.Width, img.Height),
System.Drawing.GraphicsUnit.Pixel);
// Save out to memory and get an image from it to send back out the method.
mm = new MemoryStream();
result.Save(mm, System.Drawing.Imaging.ImageFormat.Jpeg);
img.Dispose();
result.Dispose();
grPhoto.Dispose();
outimage = Image.FromStream(mm);
return outimage;
}
I would write it as
public FileResult ShowCroppedImage(int id, int size)
{
string path = "~/Uploads/Photos/";
string sourceFile = Server.MapPath(path) + id + ".jpg";
using (MemoryStream stream = new MemoryStream())
{
using (Bitmap bitmap = imageManipulation.CropImage(sourceFile, size, size))
{
bitmap.Save(stream, System.Drawing.Imaging.ImageFormat.Jpeg);
Byte[] bytes = stream.ToArray();
return File(bytes, "image/png");
}
}
}
to ensure that stream.Dispose & bitmap.Dispose are called.
Might want to call stream.dispose(); after Byte[] bytes = stream.ToArray();.
Given the question was how to detect memory leaks/usage, I'd recommend writing a method that calls your function recording the memory usage before and after:
public void SomeTestMethod()
{
var before = System.GC.GetTotalMemory(false);
// call your method
var used = before - System.GC.GetTotalMemory(false);
var unreclaimed = before - System.GC.GetTotalMemory(true);
}
Before will measure the memory usage before your function runs. The used variable will hold how much memory your function used before the garbage collector was run and unreclaimed will tell you how many bytes your function used even after trying to clean up your objects.
I suspect used will be high and unreclaimed will not - putting a using around your memory stream as the other posters suggest should make them closer although bear in mind you still have a byte array holding on to memory.

Need C# function to convert grayscale TIFF to black & white (monochrome/1BPP) TIFF

I need a C# function that will take a Byte[] of an 8 bit grayscale TIFF, and return a Byte[] of a 1 bit (black & white) TIFF.
I'm fairly new to working with TIFFs, but the general idea is that we need to convert them from grayscale or color to black and white/monochrome/binary image format.
We receive the images via a WCF as a Byte[], then we need to make this conversion to black & white in order to send them to a component which does further processing. We do not plan at this point, to ever save them as files.
For reference, in our test client, this is how we create the Byte[]:
FileStream fs = new FileStream("test1.tif", FileMode.Open, FileAccess.Read);
this.image = new byte[fs.Length];
fs.Read(this.image, 0, System.Convert.ToInt32(fs.Length));
fs.Close();
--------update---------
I think there may be more than 1 good answer here, but we ended up using the code from the CodeProject site with the following method added to overload the convert function to accept Byte[] as well as bitmap:
public static Byte[] ConvertToBitonal(Byte[] original)
{
Bitmap bm = new Bitmap(new System.IO.MemoryStream(original, false));
bm = ConvertToBitonal(bm);
System.IO.MemoryStream s = new System.IO.MemoryStream();
bm.Save(s, System.Drawing.Imaging.ImageFormat.Tiff);
return s.ToArray();
}
There is an article on CodeProject here that describes what you need.
#neodymium has a good answer, but GetPixel/SetPixel will kill performance. Bob Powell has a great method.
C#:
private Bitmap convertTo1bpp(Bitmap img)
{
BitmapData bmdo = img.LockBits(new Rectangle(0, 0, img.Width, img.Height),
ImageLockMode.ReadOnly,
img.PixelFormat);
// and the new 1bpp bitmap
Bitmap bm = new Bitmap(img.Width, img.Height, PixelFormat.Format1bppIndexed);
BitmapData bmdn = bm.LockBits(new Rectangle(0, 0, bm.Width, bm.Height),
ImageLockMode.ReadWrite,
PixelFormat.Format1bppIndexed);
// scan through the pixels Y by X
for(int y = 0; y < img.Height; y++)
{
for(int x = 0; x < img.Width; x++)
{
// generate the address of the colour pixel
int index = y * bmdo.Stride + x * 4;
// check its brightness
if(Color.FromArgb(Marshal.ReadByte(bmdo.Scan0, index + 2),
Marshal.ReadByte(bmdo.Scan0, index + 1),
Marshal.ReadByte(bmdo.Scan0, index)).GetBrightness() > 0.5F)
{
setIndexedPixel(x, y, bmdn, true); // set it if its bright.
}
}
}
// tidy up
bm.UnlockBits(bmdn);
img.UnlockBits(bmdo);
return bm;
}
private void setIndexedPixel(int x, int y, BitmapData bmd, bool pixel)
{
int index = y * bmd.Stride + (x >> 3);
byte p = Marshal.ReadByte(bmd.Scan0, index);
byte mask = (byte)(0x80 >> (x & 0x7));
if (pixel)
{
p |= mask;
}
else
{
p &= (byte)(mask ^ 0xFF);
}
Marshal.WriteByte(bmd.Scan0, index, p);
}
might want to check out 'Craigs Utility Library' I believe he has that functionality in place.
Craig's Utility Library
My company's product, dotImage, will do this.
Given an image, you can convert from multi-bit to single bit using several methods including simple threshold, global threshold, local threshold, adaptive threshold, dithering (ordered and Floyd Steinberg), and dynamic threshold. The right choice depends on the type of the input image (document, image, graph).
The typical code looks like this:
AtalaImage image = new AtalaImage("path-to-tiff", null);
ImageCommand threshold = SomeFactoryToConstructAThresholdCommand();
AtalaImage finalImage = threshold.Apply(image).Image;
SomeFactoryToConstructAThresholdCommand() is a method that will return a new command that will process the image. It could be as simple as
return new DynamicThresholdCommand();
or
return new GlobalThresholdCommand();
And generally speaking, if you're looking to convert an entire multi-page tiff to black and white, you would do something like this:
// open a sequence of images
FileSystemImageSource source = new FileSystemImageSource("path-to-tiff", true);
using (FileStream outstm = new FileStream("outputpath", FileMode.Create)) {
// make an encoder and a threshold command
TiffEncoder encoder = new TiffEncoder(TiffCompression.Auto, true);
// dynamic is good for documents -- needs the DocumentImaging SDK
ImageCommand threshold = new DynamicThreshold();
while (source.HasMoreImages()) {
// get next image
AtalaImage image = source.AcquireNext();
AtalaImage final = threshold.Apply(image).Image;
try {
encoder.Save(outstm, final, null);
}
finally {
// free memory from current image
final.Dispose();
// release the source image back to the image source
source.Release(image);
}
}
}
First, you would need to know how an X,Y pixel location maps to an index value in you array.
This will depend upon how your Byte[] was constructed.
You need to know the details of your image format - for example, what is the stride?
I don't see 8 bit grayscale TIFF in the PixelFormat enumeration. If it was there, it would tell you what you need to know.
Then, iterate through each pixel and look at its color value.
You need to decide on a threshold value - if the color of the pixel is above the threshold, make the new color white; otherwise, make it black.
If you want to simulate grayscale shading with 1BPP, you could look at more advanced techniques, such as dithering.
Something like this might work, I haven't tested it. (Should be easy to C# it.)
Dim bmpGrayscale As Bitmap = Bitmap.FromFile("Grayscale.tif")
Dim bmpMonochrome As New Bitmap(bmpGrayscale.Width, bmpgrayscale.Height, Imaging.PixelFormat.Format1bppIndexed)
Using gfxMonochrome As Graphics = Graphics.FromImage(bmpMonochrome)
gfxMonochrome.Clear(Color.White)
End Using
For y As Integer = 0 To bmpGrayscale.Height - 1
For x As Integer = 0 To bmpGrayscale.Width - 1
If bmpGrayscale.GetPixel(x, y) <> Color.White Then
bmpMonochrome.SetPixel(x, y, Color.Black)
End If
Next
Next
bmpMonochrome.Save("Monochrome.tif")
This might be a better way still:
Using bmpGrayscale As Bitmap = Bitmap.FromFile("Grayscale.tif")
Using bmpMonochrome As New Bitmap(bmpGrayscale.Width, bmpgrayscale.Height, Imaging.PixelFormat.Format1bppIndexed)
Using gfxMonochrome As Graphics = Graphics.FromImage(bmpMonochrome)
gfxMonochrome.CompositingQuality = Drawing2D.CompositingQuality.HighQuality
gfxMonochrome.SmoothingMode = Drawing2D.SmoothingMode.HighQuality
gfxMonochrome.DrawImage(bmpGrayscale, new Rectangle(0, 0, bmpMonochrome.Width, bmpMonochrome.Height)
End Using
bmpMonochrome.Save("Monochrome.tif")
End Using
End Using
I believe the term you are looking for is "resampling".
pixel by pixel manipulation is extremly slow. 40 times slower than System.DrawImage.
System.Draw image is half solution, corrupts the picture (300dpi-->96dpi) and produces at 300dpi source 200-400kb large result files.
public static Image GetBlackAndWhiteImage(Image SourceImage)
{
Bitmap bmp = new Bitmap(SourceImage.Width, SourceImage.Height);
using (Graphics gr = Graphics.FromImage(bmp)) // SourceImage is a Bitmap object
{
var gray_matrix = new float[][] {
new float[] { 0.299f, 0.299f, 0.299f, 0, 0 },
new float[] { 0.587f, 0.587f, 0.587f, 0, 0 },
new float[] { 0.114f, 0.114f, 0.114f, 0, 0 },
new float[] { 0, 0, 0, 1, 0 },
new float[] { 0, 0, 0, 0, 1 }
};
var ia = new System.Drawing.Imaging.ImageAttributes();
ia.SetColorMatrix(new System.Drawing.Imaging.ColorMatrix(gray_matrix));
ia.SetThreshold(float.Parse(Settings.Default["Threshold"].ToString())); // Change this threshold as needed
var rc = new Rectangle(0, 0, SourceImage.Width, SourceImage.Height);
gr.DrawImage(SourceImage, rc, 0, 0, SourceImage.Width, SourceImage.Height, GraphicsUnit.Pixel, ia);
}
return bmp;
}
The perfect way is just simply convert to CCITT decoded tif, that contains only BW. Much more efficent method with 30-50kb result file, 300dpi also remains correct as well:
public void toCCITT(string tifURL)
{
byte[] imgBits = File.ReadAllBytes(tifURL);
using (MemoryStream ms = new MemoryStream(imgBits))
{
using (Image i = Image.FromStream(ms))
{
EncoderParameters parms = new EncoderParameters(1);
ImageCodecInfo codec = ImageCodecInfo.GetImageDecoders()
.FirstOrDefault(decoder => decoder.FormatID == ImageFormat.Tiff.Guid);
parms.Param[0] = new EncoderParameter(Encoder.Compression, (long)EncoderValue.CompressionCCITT4);
i.Save(#"c:\test\result.tif", codec, parms);
}
}
}
Good Luck Bro,
I've tested this code and worked fine for me:
//You should use System.Linq for this to work
public static ImageCodecInfo TiffCodecInfo => ImageCodecInfo.GetImageDecoders().
FirstOrDefault(decoder => decoder.FormatID == ImageFormat.Tiff.Guid);
//Encapsulate this in a try catch block for possible exceptions
public static Bitmap ConvertToBitonal(Bitmap original)
{
EncoderParameters encoderParameters;
MemoryStream ms = new MemoryStream();
Bitmap result;
encoderParameters = new EncoderParameters(1);
encoderParameters.Param[0] = new EncoderParameter(Encoder.ColorDepth, 1L);
original.Save(ms, TiffCodecInfo, encoderParameters);
result = new Bitmap(Image.FromStream(ms));
ms.Dispose();
return result;
}

Categories

Resources