creating a loop instead of repeating if statements - c#

I am having trouble wrapping my head around this and wanted some input on this. So I am reading in a scanned PDF document that has a QR code in it which is always located on the top left corner of the document.
Due to the fact that scanning files might change the orientation of the document I am checking the top left corner of the document to see if it has the QR code and if not I will rotate the document and check the left corner again. Purpose of this because in the QR code is on the left top corner then the document is in proper format for my requirements.
How could I change my following code so that it gets the document checks for a QR code - if not found rotate the whole document check again and continue until the QR code has been found. Also should I just rotate by 90 in a loop rather than 90 - 180 - 270.
using (var fullImg = new Bitmap(workGif))
{
var bandImg = fullImg.Clone(new System.Drawing.Rectangle(0, 0, 375, 375), fullImg.PixelFormat);
Bitmap result = fullImg;
if (Process(bandImg) == null)
{
fullImg.RotateFlip(RotateFlipType.Rotate270FlipNone);
bandImg = fullImg.Clone(new System.Drawing.Rectangle(0, 0, result.Width, result.Height), fullImg.PixelFormat);
if (Process(bandImg) == null)
{
fullImg.RotateFlip(RotateFlipType.Rotate90FlipNone);
bandImg = fullImg.Clone(new System.Drawing.Rectangle(0, 0, result.Width, result.Height), fullImg.PixelFormat);
if (Process(bandImg) == null)
{
fullImg.RotateFlip(RotateFlipType.Rotate180FlipNone);
bandImg = fullImg.Clone(new System.Drawing.Rectangle(0, 0, result.Width, result.Height), fullImg.PixelFormat);
}
}
}
bandImg.Save(#"C:\NewImageTest.png");
string QRinfo = Process(bandImg);
MessageBox.Show(QRinfo);
}
Process Method
I pass the image in this method to check and see if there is a QR code to be read.
public string Process(Bitmap bitmap)
{
var reader = new com.google.zxing.qrcode.QRCodeReader();
try
{
LuminanceSource source = new RGBLuminanceSource(bitmap, bitmap.Width, bitmap.Height);
var binarizer = new HybridBinarizer(source);
var binBitmap = new BinaryBitmap(binarizer);
return reader.decode(binBitmap).Text;
}
catch (Exception e)
{
return null;
}
}

Woudn't something like this work for you? There are only four possible orientations of the document, so you have to loop at most four times. Each loop you rotate the image by 90 degrees. Once you've established that the QR code is in the top-left corner, you can break out of the loop. Then you can process the QR code or do whatever you want with it.
public void Do(string workGif)
{
// ...
string qrInfo;
using (var fullImg = new Bitmap(workGif))
{
for (int i = 0; i < 4; i++)
{
// Does the image contain a QR code?
qrInfo = Process(fullImg);
if (qrInfo = null)
// No QR code found. Rotate the image.
fullImg.RotateFlip(RotateFlipType.Rotate90FlipNone);
else
// QR code found. Break out of the loop.
break;
}
if (qrInfo == null)
{
throw new InvalidOperationException(
"The document contains no QR code.");
}
}
MessageBox.Show(qrInfo);
// ...
}
You can move the code that takes the corner image of the source image to the Process method.
private Image GetCornerImage(Image sourceImage)
{
return sourceImage.Clone(new Rectangle(0, 0, 375, 375), sourceImage.PixelFormat);
}
public string Process(Bitmap bitmap)
{
var cornerImg = GetCornerImage(bitmap);
var reader = new com.google.zxing.qrcode.QRCodeReader();
LuminanceSource source = new RGBLuminanceSource(
cornerImg, cornerImg.Width, cornerImg.Height);
var binarizer = new HybridBinarizer(source);
var binBitmap = new BinaryBitmap(binarizer);
return reader.decode(binBitmap).Text;
}

This should work fine ;
using (var fullImg = new Bitmap(workGif))
{
var bandImg = fullImg.Clone(new System.Drawing.Rectangle(0, 0, 375, 375), fullImg.PixelFormat);
int i = 0;
while(Process(bandImg) == null)
{
if (i == 1)
fullImg.RotateFlip(RotateFlipType.Rotate270FlipNone);
else if (i == 2)
fullImg.RotateFlip(RotateFlipType.Rotate90FlipNone);
else if (i== 3)
fullImg.RotateFlip(RotateFlipType.Rotate180FlipNone);
/*
Another way in which Rotation Degree can be done
First time it rotate by 270, then by 180 & then by 90
int i must be initialized with 1
int degree_to_rotate = 360 - ((4 - i) * 90)
*/
bandImg = fullImg.Clone(new System.Drawing.Rectangle(0, 0, result.Width, result.Height), fullImg.PixelFormat);
i++;
}
bandImg.Save(#"C:\NewImageTest.png");
string QRinfo = Process(bandImg);
MessageBox.Show(QRinfo);
}

If you are doing the same checks in every rotation, there is no reason not to use a loop. Just make sure you keep track of the number of rotations performed or you will be stuck in an infinite loop.

Related

ARCore for Unity save camera image

Using ARCore for Unity, trying to save Frame.CameraImage.AcquireCameraImageBytes() as image and scanning the image for QR code. But the converted image is not in actual scale and it is repeating, so not able to deduct the QR code correctly.
Here is my code
void Update()
{
using (var image = Frame.CameraImage.AcquireCameraImageBytes())
{
if (image.IsAvailable)
{
byte[] m_EdgeImage = null;
Color32[] pixels = null;
IParser Parser = new ZXingParser();
if (_texture == null || m_EdgeImage == null || _texture.width != image.Width || _texture.height != image.Height)
{
_texture = new Texture2D(image.Width, image.Height, TextureFormat.RGBA32, false, false);
m_EdgeImage = new byte[image.Width * image.Height*4];
}
System.Runtime.InteropServices.Marshal.Copy(image.Y, m_EdgeImage, 0, image.Width * image.Height);
_texture.LoadRawTextureData(m_EdgeImage);
_texture.Apply();
ParserResult Result = Parser.Decode(pixels, _texture.width, _texture.height);
if (Result != null)
{
Debug.Log("QRCODE");
}
else
{
var encodedJpg = _texture.EncodeToJPG();
var path = Application.persistentDataPath;
File.WriteAllBytes(path + "/test.jpg", encodedJpg);
Debug.Log("NOQRCODE");
Application.Quit();
}
}
}
}
Here is the converted image
What is wrong here
An ARCore camera image with its data accessible from the CPU in YUV-420-888 formatCheck this. The buffer size is width*height*1.5 for YUV. You may need to convert YUV to RGB format.

drawing a rectangle for a bitmap clone

I have a bitmap in which i clone and specify a rectangle - the current rectangle has certain width and height values which i've used for checking the rectangle for a QR code. I noticed this checks the top left corner. I would i be able to alter this to check for top right corner, bottom right and left corners of the same size(width and height)?
Bitmap result = fullImg.Clone(new System.Drawing.Rectangle(0, 0, 375, 375), fullImg.PixelFormat);
Any help is greatly appreciated.
for (int pg = 0; pg < inputDocument.PageCount; pg++)
{
string workGif = workingFilename.Replace(".pdf", string.Format(".{0}.gif", pg + 1));
GhostscriptWrapper.GeneratePageThumb(workingFilename, workGif, pg + 1, 300, 300); // size (last two params) does not seem to have any effect
using (var fullImg = new Bitmap(workGif))
{
Bitmap result = fullImg.Clone(new System.Drawing.Rectangle(0, 0, 375, 375), fullImg.PixelFormat);
string QRinfo = Process(result);
MessageBox.Show(QRinfo);
string[] qcode = QRinfo.Split('/');
string gid = qcode[qcode.Count() - 1];
Guid pgGuid = new Guid(gid);
}
}
Process Method for qr
public string Process(Bitmap bitmap)
{
var reader = new com.google.zxing.qrcode.QRCodeReader();
try
{
LuminanceSource source = new RGBLuminanceSource(bitmap, bitmap.Width, bitmap.Height);
var binarizer = new HybridBinarizer(source);
var binBitmap = new BinaryBitmap(binarizer);
return reader.decode(binBitmap).Text;
}
catch (Exception e)
{
return e.Message;
}
}
If the QRCodes are ALWAYS on the corners, you can use a picturebox for the Bitmap, and then rotate it using the RotateFlip method:
Bitmap bp = new Bitmap("myImage.jpg");
pictureBox1.Image = bp;
bp.RotateFlip(RotateFlipType.Rotate90FlipNone);
pictureBox1.Invalidate();

refactoring code for better recursion

I am trying to write some code that will look into a pdf file and call the Process method (to find a qr code image in the pdf) if not found then it rotates the file and runs the Process method again. Currently I don't think that what I have actually checks the file after being rotated but checks the same original file in its original format. How can I pass in the rotated image properly into the Process method:
using (var fullImg = new Bitmap(workGif))
{
var bandImg = fullImg.Clone(new System.Drawing.Rectangle(0, 0, 375, 375), fullImg.PixelFormat);
Bitmap result = fullImg;
if (Process(bandImg) == null)
{
fullImg.RotateFlip(RotateFlipType.Rotate270FlipNone);
bandImg = fullImg.Clone(new System.Drawing.Rectangle(0, 0, 375, 375), fullImg.PixelFormat);
if (Process(bandImg) == null)
{
fullImg.RotateFlip(RotateFlipType.Rotate90FlipNone);
bandImg = fullImg.Clone(new System.Drawing.Rectangle(0, 0, 375, 375), fullImg.PixelFormat);
if (Process(bandImg) == null)
{
fullImg.RotateFlip(RotateFlipType.Rotate180FlipNone);
bandImg = fullImg.Clone(new System.Drawing.Rectangle(0, 0, 375, 375), fullImg.PixelFormat);
//Process(bandImg);
string QRinfo = Process(bandImg);
MessageBox.Show(QRinfo);
string[] qcode = QRinfo.Split('/');
string gid = qcode[qcode.Count() - 1];
Guid pgGuid = new Guid(gid);
var ar = dc.Assessments.FirstOrDefault(c => c.ID == pgGuid);
if (ar != null)
{
var p = inputDocument.Pages[pg];
string opdName = FILESTORELOCATION + pgGuid.ToString() + ".pdf";
PdfDocument opd = new PdfDocument(opdName);
opd.Pages.Add(p);
opd.Close();
ar.StoragePath = opdName;
ar.LastUploadedDT = DateTime.UtcNow;
ar.UploadedByUserID = uploadingUser;
dc.SubmitChanges();
}
}
}
}
Process Method:
public string Process(Bitmap bitmap)
{
var reader = new com.google.zxing.qrcode.QRCodeReader();
try
{
LuminanceSource source = new RGBLuminanceSource(bitmap, bitmap.Width, bitmap.Height);
var binarizer = new HybridBinarizer(source);
var binBitmap = new BinaryBitmap(binarizer);
return reader.decode(binBitmap).Text;
}
catch (Exception e)
{
return null;
}
}
If you wanted a recursive solution check this (untested code of course):
string ReadQrCode(Bitmap img, int n = 0) {
if(n == 4) return null;
var bandImg = img.Clone(new System.Drawing.Rectangle(0, 0, 375, 375),
img.PixelFormat);
string text = Process(bandImg);
if(text == null) {
img.RotateFlip(RotateFlipType.Rotate90FlipNone);
text = ReadQrCode(img, n + 1);
}
return Text;
}
Example:
string qrCode;
using (var fullImg = new Bitmap(workGif)) {
qrCode = ReadQrCode(bandImg);
}
EXPLANATION:
Recursion is not needed to solve this kind of problem actually, indeed, as Alexei Levenkov pointed out in his comment below, a loop is much easier and clear:
string text = null;
for(int i = 0; i < 4; i++) {
var bandImg = img.Clone(new System.Drawing.Rectangle(0, 0, 375, 375),
img.PixelFormat);
text = Process(bandImg)
if(text != null) break;
else img.RotateFlip(RotateFlipType.Rotate90FlipNone);
}
In the recursive solution n behaves like the counter does in the loop, this means that recursion will have a depth of four calls (at most) just like the loop will iterate four times (at most).

Marshal.Copy/UnlockBits hangs

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

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