In order to make my question more understandable I give you pseudocode snippet:
// dx (i.e. offset value) can be arbitrary.
for(i = 0; i < bitmap.columnsCount - dx; i++)
{
// "=" means copy pixels from one column to another.
bitmap.column[i] = bitmap.column[i+dx];
}
How should I do this? Of course I can take raw pixels through LockBitmap and then somehow use MarshalCopy or unsafe section... But this is ugly and too complicated. Is there are better way?
I tried to find something similar to MoveBitmapRegion() method, but i can't. The idea of drawing bitmap to itself did not work:
Graphics g = Graphics.FromImage(bmp);
g.DrawImage(bmp, 0, 0, new Rectangle(dx, 0, bmp.Width - dx, bmp.Height), GraphicsUnit.Pixel);
Making a copy of the bitmap helps, but I think it's too expensive operation:
Graphics g = Graphics.FromImage(bmp);
g.DrawImage(new Bitmap(bmp), 0, 0, new Rectangle(dx, 0, bmp.Width - dx, bmp.Height), GraphicsUnit.Pixel);
Okay, just whipped this up really quickly at the end of the day so there may be some mistakes, but in the image I tested it seemed to work fine.
private static void CopyBmpRegion(Bitmap image, Rectangle srcRect, Point destLocation)
{
//do some argument sanitising.
if (!((srcRect.X >= 0 && srcRect.Y >= 0) && ((srcRect.X + srcRect.Width) <= image.Width) && ((srcRect.Y + srcRect.Height) <= image.Height)))
throw new ArgumentException("Source rectangle isn't within the image bounds.");
if ((destLocation.X < 0 || destLocation.X > image.Width) || (destLocation.Y < 0 || destLocation.Y > image.Height))
throw new ArgumentException("Destination must be within the image.");
// Lock the bits into memory
BitmapData bmpData = image.LockBits(new Rectangle(Point.Empty, image.Size), ImageLockMode.ReadWrite, image.PixelFormat);
int pxlSize = (bmpData.Stride / bmpData.Width); //calculate the pixel width (in bytes) of the current image.
int src = 0; int dest = 0; //source/destination pixels.
//account for the fact that not all of the source rectangle may be able to copy into the destination:
int width = (destLocation.X + srcRect.Width) <= image.Width ? srcRect.Width : (image.Width - (destLocation.X + srcRect.Width));
int height = (destLocation.Y + srcRect.Height) <= image.Height ? srcRect.Height : (image.Height - (destLocation.Y + srcRect.Height));
//managed buffer to hold the current pixel data.
byte[] buffer = new byte[pxlSize];
for (int x = 0; x < width; x++)
{
for (int y = 0; y < height; y++)
{
//calculate the start of the current source pixel and destination pixel.
src = ((srcRect.Y + y) * bmpData.Stride) + ((srcRect.X + x) * pxlSize);
dest = ((destLocation.Y + y) * bmpData.Stride) + ((destLocation.X + x) * pxlSize);
// Can replace this with unsafe code, but that's up to you.
Marshal.Copy(new IntPtr(bmpData.Scan0.ToInt32() + src), buffer, 0, pxlSize);
Marshal.Copy(buffer, 0, new IntPtr(bmpData.Scan0.ToInt32() + dest), pxlSize);
}
}
image.UnlockBits(bmpData); //unlock the data.
}
Essentially you describe the source rectangle of the area you wish to copy (in pixels, not in bytes) i.e Rectangle(0, 0, 100, 100) would describe a block 100x100 pixels wide starting at the top left corner of the image.
Next, you describe the upper left corner of the destination rectangle. As much of the source rectangle will be copied to the destination as possible, but if the image isn't wide/tall enough to accommodate the full rectangle, it will be clipped.
And example of usage would be the following:
Image img = Image.FromFile(#"C:\Users\254288b\Downloads\mozodojo-original-image.jpg");
CopyBmpRegion((Bitmap)img, new Rectangle(5, 5, 100, 100), new Point(100, 100));
img.Save(#"C:\Users\254288b\Downloads\mozodojo-new-image.jpg", ImageFormat.Jpeg);
Which had the following results:
mozodojo-original-image.jpg
mozodojo-new-image.jpg
See how it goes.
Related
I am trying to extract a specific area from a bitmap for further processing. In rare cases an error occurs when Marshal.Copy is called. This can be reproduced with the following example code:
Bitmap bitmap = new Bitmap(1741, 2141, PixelFormat.Format1bppIndexed);
int zoneWidth = 50;
int zoneHeight = 50;
int x = 168;
int y = bitmap.Height - zoneHeight;
Rectangle zone = new Rectangle(x, y, zoneWidth, zoneHeight);
BitmapData bitmapData = bitmap.LockBits(zone, ImageLockMode.ReadOnly, bitmap.PixelFormat);
int byteCount = Math.Abs(bitmapData.Stride) * bitmapData.Height;
byte[] pixels = new byte[byteCount];
Marshal.Copy(bitmapData.Scan0, pixels, 0, byteCount);
// some further processing
bitmap.UnlockBits(bitmapData);
In other posts I have read that Stride can be negative. That is not the case here.
Why does the error occur and how can I prevent it?
Edit 1:
I have implemented the second suggestion of JonasH. But that also fails with the AccessViolationException. Probably I did not do that correctly.
Bitmap bitmap = new Bitmap(1741, 2141, PixelFormat.Format1bppIndexed);
int zoneWidth = 50;
int zoneHeight = 50;
int zoneX = 168;
int zoneY = bitmap.Height - zoneHeight;
Rectangle zone = new Rectangle(zoneX, zoneY, zoneWidth, zoneHeight);
BitmapData bitmapData = bitmap.LockBits(zone, ImageLockMode.ReadOnly, bitmap.PixelFormat);
int rowSize = Math.Abs(bitmapData.Stride);
byte[] pixels = new byte[bitmapData.Height * rowSize];
IntPtr iptr = bitmapData.Scan0;
for (int y = 0; y < bitmapData.Height; y++)
{
Marshal.Copy(IntPtr.Add(iptr, y * rowSize),
pixels,
y * rowSize,
rowSize);
}
bitmap.UnlockBits(bitmapData);
This is probably because you are only locking part of the bitmap. Replace the zone with new Rectangle(0, 0, bitmap.Width , bitmap.Height); and I would expect your problem to disappear.
The alternative would be to restrict the copy-operation to the locked part of the bitmap, but that would require copying row by row and not the entire bitmap at once.
Copying row by row needs careful usage of offsets, you need to keep track of both the horizontal and vertical offsets of both the source and target data. I think something like this should work:
for (int y = 0; y < zoneHeight ; y++)
{
Marshal.Copy(
IntPtr.Add(iptr,(y + zoneY ) * bitmapData.Stride + zoneX)
pixels,
y * zoneWidth,
zoneWidth );
}
I have a problem. I need to perform this function with lockbits. Please I need help.
public void xPix(Bitmap bmp, int n, Color cx, Color nx)
{
try
{
for (int y = 0; y < bmp.Height; y++)
{
for (int x = 0; x < bmp.Width; x += (n * 2))
{
cx = bmp.GetPixel(x, y);
if (x + n <= bmp.Width - 1) nx = bmp.GetPixel(x + n, y);
bmp.SetPixel(x, y, nx);
if (x + n <= bmp.Width - 1) bmp.SetPixel(x + n, y, cx);
}
}
}
catch { }
}
There were lots of things that didn't make sense to me about your code. I fixed the pieces that were preventing an image from appearing and here is the result. I will explain my changes after the code.
public void xPix(Bitmap bmp, int n, Color cx, Color nx)
{
var img = bmp.LockBits(new Rectangle(Point.Empty, bmp.Size), System.Drawing.Imaging.ImageLockMode.ReadWrite, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
byte[] bmpBytes = new byte[Math.Abs(img.Stride) * img.Height];
System.Runtime.InteropServices.Marshal.Copy(img.Scan0, bmpBytes, 0, bmpBytes.Length);
for (int y = 0; y < img.Height; y++)
{
for (int x = 0; x < img.Width; x+=n*2)
{
cx = Color.FromArgb(BitConverter.ToInt32(bmpBytes, y * Math.Abs(img.Stride) + x * 4));
if (x + n <= img.Width - 1) nx = Color.FromArgb(BitConverter.ToInt32(bmpBytes, y * Math.Abs(img.Stride) + x * 4));
BitConverter.GetBytes(nx.ToArgb()).CopyTo(bmpBytes, y * Math.Abs(img.Stride) + x * 4);
if (x + n <= img.Width - 1) BitConverter.GetBytes(cx.ToArgb()).CopyTo(bmpBytes, y * Math.Abs(img.Stride) + (x + n) * 4);
}
}
System.Runtime.InteropServices.Marshal.Copy(bmpBytes, 0, img.Scan0, bmpBytes.Length);
bmp.UnlockBits(img);
}
protected override void OnClick(EventArgs e)
{
base.OnClick(e);
Bitmap bmp = new Bitmap(#"C:\Users\bluem\Downloads\Default.png");
for (int i = 0; i < bmp.Width; i++)
{
xPix(bmp, new Random().Next(20) + 1, System.Drawing.Color.White, System.Drawing.Color.Green);
}
Canvas.Image = bmp;
}
There's no such class as LockBitmap so I replaced it with the result of a call to Bitmap.LockBits directly.
The result of LockBits does not include functions for GetPixel and SetPixel, so I did what one normally does with the result of LockBits (see https://learn.microsoft.com/en-us/dotnet/api/system.drawing.bitmap.lockbits?view=netframework-4.7.2) and copied the data into a byte array instead.
When accessing the byte data directly, some math must be done to convert the x and y coordinates into a 1-dimensional coordinate within the array of bytes, which I did.
When accessing the byte data directly under the System.Drawing.Imaging.PixelFormat.Format32bppArgb pixel format, multiple bytes must be accessed to convert between byte data and a pixel color, which I did with BitConverter.GetBytes, BitConverter.ToInt32, Color.FromArgb and Color.ToArgb.
I don't think it's a good idea to be changing the Image in the middle of painting it. You should either be drawing the image directly during the Paint event, or changing the image outside the Paint event and allowing the system to draw it. So I used the OnClick of my form to trigger the function instead.
The first random number I got was 0, so I had to add 1 to avoid an endless loop.
The cx and nx parameters never seem to be used as inputs, so I put arbitrary color values in for them. Your x and y variables were not defined/declared anywhere.
If you want faster on-image-action, you can use Marshall.Copy method with Parallel.For
Why dont use GetPixel method? Because every time you call it, your ALL image is loaded to memory. GetPixel get one pixel, and UNLOAD all image. And in every iteration, ALL image is loaded to memory (for example, if u r working on 500x500 pix image, GetPixel will load 500x500 times whole pixels to memory). When you work on images with C# (CV stuff), work on raw bytes from memory.
I will show how to use with Lockbits in Binarization because its easy to explain.
int pixelBPP = Image.GetPixelFormatSize(resultBmp.PixelFormat) / 8;
unsafe
{
BitmapData bmpData = resultBmp.LockBits(new Rectangle(0, 0, resultBmp.Width, resultBmp.Height), ImageLockMode.ReadWrite, resultBmp.PixelFormat);
byte* ptr = (byte*)bmpData.Scan0; //addres of first line
int height = resultBmp.Height;
int width = resultBmp.Width * pixelBPP;
Parallel.For(0, height, y =>
{
byte* offset = ptr + (y * bmpData.Stride); //set row
for(int x = 0; x < width; x = x + pixelBPP)
{
byte value = (offset[x] + offset[x + 1] + offset[x + 2]) / 3 > threshold ? Byte.MaxValue : Byte.MinValue;
offset[x] = value;
offset[x + 1] = value;
offset[x + 2] = value;
if (pixelBPP == 4)
{
offset[x + 3] = 255;
}
}
});
resultBmp.UnlockBits(bmpData);
}
Now, example with Marshall.copy:
BitmapData bmpData = resultBmp.LockBits(new Rectangle(0, 0, resultBmp.Width, resultBmp.Height),
ImageLockMode.ReadWrite,
resultBmp.PixelFormat
);
int bytes = bmpData.Stride * resultBmp.Height;
byte[] pixels = new byte[bytes];
Marshal.Copy(bmpData.Scan0, pixels, 0, bytes); //loading bytes to memory
int height = resultBmp.Height;
int width = resultBmp.Width;
Parallel.For(0, height - 1, y => //seting 2s and 3s
{
int offset = y * stride; //row
for (int x = 0; x < width - 1; x++)
{
int positionOfPixel = x + offset + pixelFormat; //remember about pixel format!
//do what you want with pixel
}
}
});
Marshal.Copy(pixels, 0, bmpData.Scan0, bytes); //copying bytes to bitmap
resultBmp.UnlockBits(bmpData);
Remember, when you warking with RAW bytes very important is to remember about PixelFormat. If you work on RGBA image, you need to set up every channel. (for example offset + x + pixelFormat). I showed it in Binarization example, how to deak with RGBA image with raw data. If lockbits are not fast enough, use Marshall.Copy
This simple code gives me very bad result.
Image global_src_img = Image.FromFile(Application.StartupPath + "\\src.png");
Image src_img = ((Bitmap)global_src_img).Clone(new Rectangle(160, 29, 8, 14), PixelFormat.Format32bppArgb);
src_img.Save(Application.StartupPath + "\\src_clonned.png", ImageFormat.Png);
Bitmap trg_bmp = new Bitmap(100, 100);
Graphics gfx = Graphics.FromImage(trg_bmp);
gfx.FillRectangle(new Pen(Color.FromArgb(255, 0, 0, 0)).Brush, 0, 0, trg_bmp.Width, trg_bmp.Height);
gfx.DrawImageUnscaled(src_img, (trg_bmp.Width / 2) - (src_img.Width / 2), (trg_bmp.Height / 2) - (src_img.Height / 2));
gfx.Dispose();
trg_bmp.Save(Application.StartupPath + "\\trg.png", ImageFormat.Png);
It clones part of the big image with letters and writes it to another image.
Written image has extra pixels at outer borders that does not present in source image same as in cloned image.
How to avoid it?
This is cloned image (src_clonned.png) that later will be drawn on graphics.
This is saved resulting image (trg.png).
Draw directly on bitmap to avoid any pixels modifications with this:
public static void DrawImgOnImg(Image src, Bitmap trg, int x, int y)
{
for (int i = x; i < x + src.Width; i++)
for (int j = y; j < y + src.Height; j++)
{
Color src_px = ((Bitmap)src).GetPixel(i - x, j - y);
trg.SetPixel(i, j, src_px);
}
}
I want to compare two images, where only "date of print" is different, I wanted to crop 'date' area only. But I wanted to show full image without crop area, (not the crop area only)
Code I used for cropping
static void Main(string[] args)
{
Bitmap bmp = new Bitmap(#"C:\Users\Public\Pictures\Sample Pictures\1546.jpg");
Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
BitmapData rawOriginal = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
int origByteCount = rawOriginal.Stride * rawOriginal.Height;
byte[] origBytes = new Byte[origByteCount];
System.Runtime.InteropServices.Marshal.Copy(rawOriginal.Scan0, origBytes, 0, origByteCount);
//I want to crop a 100x100 section starting at 15, 15.
int startX = 15;
int startY = 15;
int width = 100;
int height = 100;
int BPP = 4; //4 Bpp = 32 bits, 3 = 24, etc.
byte[] croppedBytes = new Byte[width * height * BPP];
//Iterate the selected area of the original image, and the full area of the new image
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width * BPP; j += BPP)
{
int origIndex = (startX * rawOriginal.Stride) + (i * rawOriginal.Stride) + (startY * BPP) + (j);
int croppedIndex = (i * width * BPP) + (j);
//copy data: once for each channel
for (int k = 0; k < BPP; k++)
{
croppedBytes[croppedIndex + k] = origBytes[origIndex + k];
}
}
}
//copy new data into a bitmap
Bitmap croppedBitmap = new Bitmap(width, height);
BitmapData croppedData = croppedBitmap.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
System.Runtime.InteropServices.Marshal.Copy(croppedBytes, 0, croppedData.Scan0, croppedBytes.Length);
bmp.UnlockBits(rawOriginal);
croppedBitmap.UnlockBits(croppedData);
croppedBitmap.Save(#"C:\Users\Public\Pictures\Sample Pictures\AFTERCROP_CROP.jpg");
bmp.Save(#"C:\Users\Public\Pictures\Sample Pictures\AFTERCROP-ORIG.jpg");
}
Your code is a bit overcomplicated, and you seem to be confused about what cropping is - cropping means taking a part of the original picture. What you seem to want instead is to black out some part of the original picture:
The easiest way to accomplish this is by drawing a simple filled rectangle over the original image:
var bmp = Bitmap.FromFile(#"C:\Users\Public\Pictures\Sample Pictures\Chrysanthemum.jpg");
using (var gr = Graphics.FromImage(bmp))
{
gr.FillRectangle(Brushes.Black, 50, 50, 200, 200);
}
If you also want to preserve the original bitmap, you can just copy it over.
So I have a source bitmap that is fairly large, so I shrink it down by a scale of %25 to speed things along in image processing and what not. At the end, I have a group of Rectangles (around 2000) that correspond to sections of the scaled image. I am trying to rescale the Rectangles to match the same areas on the source, then saving that area as a cropped image.
This was my initial code that saved cropped images of the scaled image:
for (int i = 0; i < cells.Count; i++)
{
for (int j = 0; j < cells[i].Count; j++)
{
Cell cell = cells[i][j];
if (cell.width < 0 || cell.height < 0)
{
return;
}
Bitmap bitmap = new Bitmap(cell.width, cell.height);
using (Graphics c = Graphics.FromImage(bitmap))
{
c.DrawImage(inputBitmap, new Rectangle(0, 0, cell.width, cell.height), new Rectangle(cell.x1, cell.y1, cell.width, cell.height), GraphicsUnit.Pixel);
}
bitmap.Save(cellDirectory + "\\cell" + i.ToString("D2") + j.ToString("D2") + ".png", ImageFormat.Png);
}
}
This is my code changed to save the cropped image of the original bitmap:
for (int i = 0; i < cells.Count; i++)
{
for (int j = 0; j < cells[i].Count; j++)
{
Cell cell = cells[i][j];
if (cell.width < 0 || cell.height < 0)
{
return;
}
int x = cell.x1 * 4;
int y = cell.y1 * 4;
int width = cell.width * 4;
int height = cell.height * 4;
Bitmap bitmap = new Bitmap(width, height);
using (Graphics c = Graphics.FromImage(bitmap))
{
c.DrawImage(input, new Rectangle(0, 0, width, height), new Rectangle(x, y, width, height), GraphicsUnit.Pixel);
}
bitmap.Save(cellDirectory + "\\cell" + i.ToString("D2") + j.ToString("D2") + ".png", ImageFormat.Png);
}
}
The program with the first code finishes in about 20 seconds on average, but for some reason the second version takes over 6 minutes. My brain math might be lying to me, but that seems to be a disproportionate time increase.
The debugging that I have done so far has revealed to me that this line:
c.DrawImage(input, new Rectangle(0, 0, width, height), new Rectangle(x, y, width, height), GraphicsUnit.Pixel);
is taking longer to complete over time. I suspect that some sort of memory leak could be causing this, but I have tried manually calling Dispose on every object I can and nothing helped. Is there some kind of under-the-hood thing that I should be aware of that is causing this?
Your original method saves a file at the original resolution, while the new method increases both width and height by a factor of 4, which is a 16x increase in the image size. The time difference (6 minutes vs 20 seconds) is roughly proportional:
(6 * 60) / 20 = 18 times slower
4 * 4 = 16 times the image size