I am writing to a Graphics object dynamically and don't know the actual size of the final image until all output is passed.
So, I create a large image and create Graphics object from it:
int iWidth = 600;
int iHeight = 2000;
bmpImage = new Bitmap(iWidth, iHeight);
graphics = Graphics.FromImage(bmpImage);
graphics.Clear(Color.White);
How can I find the actual size of written content, so I will be able to create a new bitmap with this size and copy the content to it.
It is really hard to calculate the content size before drawing it and want to know if there is any other solution.
The best solution is probably to keep track of the maximum X and Y values that get used as you draw, though this will be an entirely manual process.
Another option would be to scan full rows and columns of the bitmap (starting from the right and the bottom) until you encounter a non-white pixel, but this will be a very inefficient process.
int width = 0;
int height = 0;
for(int x = bmpImage.Width - 1, x >= 0, x++)
{
bool foundNonWhite = false;
width = x + 1;
for(int y = 0; y < bmpImage.Height; y++)
{
if(bmpImage.GetPixel(x, y) != Color.White)
{
foundNonWhite = true;
break;
}
}
if(foundNonWhite) break;
}
for(int y = bmpImage.Height - 1, x >= 0, x++)
{
bool foundNonWhite = false;
height = y + 1;
for(int x = 0; x < bmpImage.Width; x++)
{
if(bmpImage.GetPixel(x, y) != Color.White)
{
foundNonWhite = true;
break;
}
}
if(foundNonWhite) break;
}
Again, I don't recommend this as a solution, but it will do what you want without your having to keep track of the coordinate space that you actually use.
Just check the value of these properties
float width = graphics.VisibleClipBounds.Width;
float height = graphics.VisibleClipBounds.Height;
A RectangleF structure that represents a bounding rectangle for the visible clipping region of this Graphics.
Related
I have a video stream from a camera to an Image in a WPF. I am trying to access the WritableBitMap Image pixel by pixel before displaying it. As a test I am trying to set the whole image to white or black. In both cases however, I get the AccessViolationException error.
I checked other posts and it seems that this error is very wide and not specific to my case. I can't seem to know why I am not getting this working.
So what is the best way to play with the pixels in my case? or why this is not working? Any help is appreciated
private async void UpdateMasterCameraPreview(IdsFrame frame)
{
if (masterImage != null)
frame.Image.CopyTo(masterImage);
else
masterImage = frame.Image.ToWriteableBitmap();
//BitmapImage temp = ConvertWriteableBitmapToBitmapImage(masterImage);
WriteableBitmap temp = masterImage;
// Here I get the exception, on every pixel access
for (int y = 0; y < temp.PixelHeight; y++)
for (int x = 0; x < temp.PixelWidth; x++)
temp.SetPixel(x, y, 255);
masterImage = temp;
masterImage.Lock();
masterImage.AddDirtyRect(new Int32Rect(0, 0, masterImage.PixelWidth, masterImage.PixelHeight));
masterImage.Unlock();
if (OnMasterFrameCaptured != null)
OnMasterFrameCaptured(this, new CameraFrameCapturedArgs(CameraType.Master, masterImage));
}
You have swapped X and Y, i represents height, j represents width, then you shouldcall SetPixel like:
temp.SetPixel(j, i, 255);
On cases like this is better to use meaningful names for variables, like X and Y.
I ended up using the answer of this post. I now can edit raw pixel data of any WriteableBitmap image before sending it to image control in WPF. Below is what I exactly used but here I just convert every frame to some transparency under a condition:
public void ConvertImage(ref WriteableBitmap Wbmp)
{
int width = Wbmp.PixelWidth;
int height = Wbmp.PixelHeight;
int stride = Wbmp.BackBufferStride;
int bytesPerPixel = (Wbmp.Format.BitsPerPixel + 7) / 8;
unsafe
{
byte* pImgData = (byte*)Wbmp.BackBuffer;
// set alpha to transparent for any pixel with red < 0x88 and invert others
int cRowStart = 0;
int cColStart = 0;
for (int row = 0; row < height; row++)
{
cColStart = cRowStart;
for (int col = 0; col < width; col++)
{
byte* bPixel = pImgData + cColStart;
UInt32* iPixel = (UInt32*)bPixel;
if (bPixel[2 /* bgRa */] < 0x44)
{
// set to 50% transparent
bPixel[3 /* bgrA */] = 0x7f;
}
else
{
// invert but maintain alpha
*iPixel = *iPixel ^ 0x00ffffff;
}
cColStart += bytesPerPixel;
}
cRowStart += stride;
}
}
}
And the routine of using it is like this:
masterImage.Lock();
ConvertImage(ref masterImage);
masterImage.AddDirtyRect(new Int32Rect(0, 0, masterImage.PixelWidth, masterImage.PixelHeight));
masterImage.Unlock();
I develop a screen sharing app and i would like to make it as efficient as posibble so im trying to send only the differences between the screen shots.
So, suppose we have this image for example:its a 32bpprgba image with transpert parts around.
I would like to store each one of the blocks here as a rectangle in a List and get them bounds. It may sounds very complex but actually it just requires a little logic.
This is my code so far:
private unsafe List<Rectangle> CodeImage(Bitmap bmp)
{
List<Rectangle> rec = new List<Rectangle>();
Bitmap bmpRes = new Bitmap(bmp.Width, bmp.Height);
BitmapData bmData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, bmp.PixelFormat);
IntPtr scan0 = bmData.Scan0;
int stride = bmData.Stride;
int nWidth = bmp.Width;
int nHeight = bmp.Height;
int minX = int.MaxValue; ;
int minY = int.MaxValue;
int maxX = 0;
bool found = false;
for (int y = 0; y < bmp.Height; y++)
{
byte* p = (byte*)scan0.ToPointer();
p += y * stride;
for (int x = 0; x < bmp.Width; x++)
{
if (p[3] != 0) //Check if pixel is not transparent;
{
found = true;
if (x < minX)
minX = x;
if (x > maxX)
maxX = x;
if (y < minY)
minY = y;
}
else
{
if (found)
{
int height = getBlockHeight(stride, scan0, maxX, minY);
found = false;
Rectangle temp = new Rectangle(minX, minY, maxX - minX, height);
rec.Add(temp);
y += minY;
break;
}
}
p += 4;//add to the pointer 4 bytes;
}
}
return rec;
}
as you see im trying to scan the image using the height and width, and when i found a pixel i send it to GetBlockHeight function to get it's height:
public unsafe int getBlockHeight(int stride, IntPtr scan, int x, int y1)
{
int height = 0; ;
for (int y = y1; y < 1080; y++)
{
byte* p = (byte*)scan.ToPointer();
p += (y * stride) + (x * 4);
if (p[3] != 0) //Check if pixel is not transparent;
{
height++;
}
}
return height;
}
But im just not getting the result... i think there's somthing with the logic here... can anyone light my eyes? i know it requires a bit time and thinking but i would very very appreciate anyone who could help a little.
In your current algorithm, after successfully matching a rectangle, you increase y with its height and break out of the inner loop. This means you can only detect data for one rectangle per horizontal line.
If I were you I'd think about the following things, before jumping back into the code:
Save the complete image as a PNG file, and look at its size. Is further processing really required?
Are these rectangles accurate? Will there be scenario's in which you would be constantly sending the contents of the entire screen anyway?
If you're developing for Windows, you might be able to hook into the procedure that invalidates areas on the screen, in which case you wouldn't have to determine these rectangles yourself. I don't know about other OSes
Also I personally wouldn't try to solve the rectangle-detection algorithm in a "nesty" for-loop, but go with something like this:
public void FindRectangles(Bitmap bitmap, Rectangle searchArea, List<Rectangle> results)
{
// Find the first non-transparent pixel within the search area.
// Ensure that it is the pixel with the lowest y-value possible
Point p;
if (!TryFindFirstNonTransparent(bitmap, searchArea, out p))
{
// No non-transparent pixels in this area
return;
}
// Find its rectangle within the area
Rectangle r = GetRectangle(bitmap, p, searchArea);
results.Add(r);
// No need to search above the rectangle we just found
Rectangle left = new Rectangle(searchArea.Left, r.Top, r.Left - searchArea.Left, searchArea.Bottom - r.Top);
Rectangle right = new Rectangle(r.Right, r.Top, searchArea.Right - r.Right, searchArea.Bottom - r.Top);
Rectangle bottom = new Rectangle(r.Left, r.Bottom, r.Width, searchArea.Bottom - r.Bottom);
FindRectangles(bitmap, left, results);
FindRectangles(bitmap, right, results);
FindRectangles(bitmap, bottom, results);
}
public Rectangle GetRectangle(Bitmap bitmap, Point p, Rectangle searchArea)
{
int right = searchArea.Right;
for (int x = p.X; x < searchArea.Right; x++)
{
if (IsTransparent(x, p.Y))
{
right = x - 1;
break;
}
}
int bottom = searchArea.Bottom;
for (int y = p.Y; y < searchArea.Bottom; y++)
{
if (IsTransparent(p.X, y))
{
bottom = y - 1;
break;
}
}
return new Rectangle(p.X, p.Y, right - p.X, bottom - p.Y);
}
This approach, when fully implemented, should give you a list of rectangles (although it will occasionally split a rectangle in two).
(Of course instead of providing the bitmap, you'd pass the pointer to the pixel data with some metadata instead)
I am trying to compare images in folder(s) and storing diffrence on a 3rd folder.
While I am trying to compare between Image-1 and Image-2, the difference should be Image-3.
Difference between first two images (Image-3) is not showing full, only partial is showing, and I am getting GDI+ error
Code is
// Create the difference image.
bool are_identical = true;
Color eq_color = Color.White;
Color ne_color = Color.Red;
for (int x = 0; x < wid; x++)
{
for (int y = 0; y < hgt; y++)
{
if (bm1.GetPixel(x, y).Equals(bm2.GetPixel(x, y)))
{
bm3.SetPixel(x, y, eq_color);
}
else
{
bm3.SetPixel(x, y, ne_color);
are_identical = false;
bm3.Save(#"C:\XPS Files\DiffrenceofImages\" + new1[i]);
}
}
}
GDI+ error occurs at
bm3.save(#"C:\XPS Files\DiffrenceofImages\" + new1[i]);
So the difference of image shows partial (until loop completed) e.g. if difference of 2 image is a circle, I get 80% of that circle.
You should save the image outside the loop. If the pixels on the right are equal, the image isn't saved anymore. So you get a partial image.
// Create the difference image.
bool are_identical = true;
Color eq_color = Color.White;
Color ne_color = Color.Red;
for (int x = 0; x < wid; x++)
{
for (int y = 0; y < hgt; y++)
{
if (bm1.GetPixel(x, y).Equals(bm2.GetPixel(x, y)))
{
bm3.SetPixel(x, y, eq_color);
}
else
{
bm3.SetPixel(x, y, ne_color);
are_identical = false;
}
}
}
bm3.Save(#"C:\XPS Files\DiffrenceofImages\" + new1[i]);
If you want to speed it up, you could check here: Fast work with Bitmaps in C#
This is what i did in form1 constructor:
Bitmap bmp2 = new Bitmap(#"e:\result1001.jpg");
CropImageWhiteAreas.ImageTrim(bmp2);
bmp2.Save(#"e:\result1002.jpg");
bmp2.Dispose();
The class CropImageWhiteAreas:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
namespace Test
{
class CropImageWhiteAreas
{
public static Bitmap ImageTrim(Bitmap img)
{
//get image data
BitmapData bd = img.LockBits(new Rectangle(Point.Empty, img.Size),
ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
int[] rgbValues = new int[img.Height * img.Width];
Marshal.Copy(bd.Scan0, rgbValues, 0, rgbValues.Length);
img.UnlockBits(bd);
#region determine bounds
int left = bd.Width;
int top = bd.Height;
int right = 0;
int bottom = 0;
//determine top
for (int i = 0; i < rgbValues.Length; i++)
{
int color = rgbValues[i] & 0xffffff;
if (color != 0xffffff)
{
int r = i / bd.Width;
int c = i % bd.Width;
if (left > c)
{
left = c;
}
if (right < c)
{
right = c;
}
bottom = r;
top = r;
break;
}
}
//determine bottom
for (int i = rgbValues.Length - 1; i >= 0; i--)
{
int color = rgbValues[i] & 0xffffff;
if (color != 0xffffff)
{
int r = i / bd.Width;
int c = i % bd.Width;
if (left > c)
{
left = c;
}
if (right < c)
{
right = c;
}
bottom = r;
break;
}
}
if (bottom > top)
{
for (int r = top + 1; r < bottom; r++)
{
//determine left
for (int c = 0; c < left; c++)
{
int color = rgbValues[r * bd.Width + c] & 0xffffff;
if (color != 0xffffff)
{
if (left > c)
{
left = c;
break;
}
}
}
//determine right
for (int c = bd.Width - 1; c > right; c--)
{
int color = rgbValues[r * bd.Width + c] & 0xffffff;
if (color != 0xffffff)
{
if (right < c)
{
right = c;
break;
}
}
}
}
}
int width = right - left + 1;
int height = bottom - top + 1;
#endregion
//copy image data
int[] imgData = new int[width * height];
for (int r = top; r <= bottom; r++)
{
Array.Copy(rgbValues, r * bd.Width + left, imgData, (r - top) * width, width);
}
//create new image
Bitmap newImage = new Bitmap(width, height, PixelFormat.Format32bppArgb);
BitmapData nbd
= newImage.LockBits(new Rectangle(0, 0, width, height),
ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
Marshal.Copy(imgData, 0, nbd.Scan0, imgData.Length);
newImage.UnlockBits(nbd);
return newImage;
}
}
}
I also tried before it Peter solution.
In both the result is(This is a screenshot of my facebook after uploaded the image) still the white areas around:
You can the rectangle around the image i just uploaded and see what i mean by white area around.
If I understand correctly, you have found a sample code snippet that uses LockBits(), but you are not sure how it works or how to modify it to suit your specific need. So I will try to answer from that perspective.
First, a wild guess (since you didn't include the implementation of the LockBitmap class you're using in the first example): the LockBitmap class is some kind of helper class that is supposed to encapsulate the work of calling LockBits() and using the result, including providing versions of GetPixel() and SetPixel() which are presumably much faster than calling those methods on a Bitmap object directly (i.e. access the buffer obtained by calling LockBits()).
If that's the case, then modifying the first example to suit your need is probably best:
public void Change(Bitmap bmp)
{
Bitmap newBitmap = new Bitmap(bmp.Width, bmp.Height, bmp.PixelFormat);
LockBitmap source = new LockBitmap(bmp),
target = new LockBitmap(newBitmap);
source.LockBits();
target.LockBits();
Color white = Color.FromArgb(255, 255, 255, 255);
for (int y = 0; y < source.Height; y++)
{
for (int x = 0; x < source.Width; x++)
{
Color old = source.GetPixel(x, y);
if (old != white)
{
target.SetPixel(x, y, old);
}
}
}
source.UnlockBits();
target.UnlockBits();
newBitmap.Save("d:\\result.png");
}
In short: copy the current pixel value to a local variable, compare that value to the white color value, and if it is not the same, go ahead and copy the pixel value to the new bitmap.
Some variation on the second code example should work as well. The second code example does explicitly what is (I've assumed) encapsulated inside the LockBitmap class that the first code example uses. If for some reason, the first approach isn't suitable for your needs, you can follow the second example.
In that code example you provide, most of the method there is just handling the "grunt work" of locking the bitmap so that the raw data can be accessed, and then iterating through that raw data.
It computes the oRow and nRow array offsets (named for "old row" and "new row", I presume) based on the outer y loop, and then accesses individual pixel data by computing the offset within a given row based on the inner x loop.
Since you want to do essentially the same thing, but instead of converting the image to grayscale, you just want to selectively copy all non-white pixels to the new bitmap, you can (should be able to) simply modify the body of the inner x loop. For example:
byte red = oRow[x * pixelSize + 2],
green = oRow[x * pixelSize + 1],
blue = oRow[x * pixelSize];
if (red != 255 || green != 255 || blue != 255)
{
nRow[x * pixelSize + 2] = red;
nRow[x * pixelSize + 1] = green;
nRow[x * pixelSize] = blue;
}
The above would entirely replace the body of the inner x loop.
One caveat: do note that when using the LockBits() approach, knowing the pixel format of the bitmap is crucial. The example you've shown assumes the bitmaps are in 24 bpp format. If your own bitmaps are in this format, then you don't need to change anything. But if they are in a different format, you'll need to adjust the code to suit that. For example, if your bitmap is in 32 bpp format, you need to pass the correct PixelFormat value to the LockBits() method calls, and then set pixelSize to 4 instead of 3 as the code does now.
Edit:
You've indicated that you would like to crop the new image so that it is the minimize size required to contain all of the non-white pixels. Here is a version of the first example above that should accomplish that:
public void Change(Bitmap bmp)
{
LockBitmap source = new LockBitmap(bmp);
source.LockBits();
Color white = Color.FromArgb(255, 255, 255, 255);
int minX = int.MaxValue, maxX = int.MinValue,
minY = int.MaxValue, maxY = int.MinValue;
// Brute-force scan of the bitmap to find image boundary
for (int y = 0; y < source.Height; y++)
{
for (int x = 0; x < source.Width; x++)
{
if (source.GetPixel(x, y) != white)
{
if (x < minX) minX = x;
if (x > maxX) maxX = x;
if (y < minY) minY = y;
if (y > maxY) maxY = y;
}
}
}
Bitmap newBitmap = new Bitmap(maxX - minx + 1, maxY - minY + 1, bmp.PixelFormat);
LockBitmap target = new LockBitmap(newBitmap);
target.LockBits();
for (int y = 0; y < target.Height; y++)
{
for (int x = 0; x < target.Width; x++)
{
target.SetPixel(x, y, source.GetPixel(x + minX, y + minY));
}
}
source.UnlockBits();
target.UnlockBits();
newBitmap.Save("d:\\result.png");
}
This example includes an initial scan of the original bitmap, after locking it, to find the minimum and maximum coordinate values for any non-white pixel. Having done that, it uses the results of that scan to determine the dimensions of the new bitmap. When copying the pixels, it restricts the x and y loops to the dimensions of the new bitmap, adjusting the x and y values to map from the location in the new bitmap to the given pixel's original location in the old one.
Note that since the initial scan determines where the non-white pixels are, there's no need to check again when actually copying the pixels.
There are more efficient ways to scan the bitmap than the above. This version simply looks at every single pixel in the original bitmap, keeping track of the min and max values for each coordinate. I'm guessing this will be fast enough for your purposes, but if you want something faster, you can change the scan so that it scans for each min and max in sequence:
Scan each row from y of 0 to determine the first row with a non-white pixel. This is the min y value.
Scan each row from y of source.Height - 1 backwards, to find the max y value.
Having found the min and max y values, now scan the columns from x of 0 to find the min x and from source.Width - 1 backwards to find the max x.
Doing it that way involves a lot more code and is probably harder to read and understand, but would involve inspecting many fewer pixels in most cases.
Edit #2:
Here is a sample of the output of the second code example:
Note that all of the white border of the original bitmap (shown on the left side) has been cropped out, leaving only the smallest subset of the original bitmap that can contain all of the non-white pixels (shown on the right side).
I am trying to create a binary image in C#/WPF using the WriteableBitmap class, with the BlackWhite Format which is 1 bit per pixel.
However, it seems the my image is very distorted when finished. Using different formats (such as brg32) works just fine. The pixel data is stored in a BitArray. The images vary from 1000x1000 to 3000x3000 pixels.
Here is the code I am current using:
unsafe
{
int colorOffset = 0;
int pixelOffset = 0;
byte color = 0;
int pBackBuffer = (int)_image.BackBuffer;
for (int y = 0; y < mapData.Height; y++)
{
for (int x = 0; x < mapData.Width; x++)
{
if (mapData.Data[y * mapData.Height + x])
{
//Set the pixel to white
color += 1;
}
//Shift the pixel position by 1
color = (byte)(color << 1);
//If 8 pixels have been written, write it to the backbuffer
if (++colorOffset == 8)
{
pixelOffset = ((y * mapData.Height) + x) / 8;
*(byte*)(pBackBuffer + pixelOffset) = color;
color = 0;
colorOffset = 0;
}
}
}
//Update the image
_image.AddDirtyRect(new Int32Rect(0, 0, mapData.Width, mapData.Height));
}
As you can see, I am writing 8 pixels / bits , and then copying it to the back buffer. Perhaps someone who has a bit more knowledge in this topic could help. I've also tried directly copying the BitArray to a byte array, then copy the byte array to the backbuffer (and using the WritePixels function as well), both of which have not helped.
Regards,
Dan
It seems like you are not using BackBufferStride property to compute address of the next line of pixels. Also note that you are missing some pixels if map width is not a multiple of 8. I didn't test the code, but i would have written it like this:
unsafe
{
int colorOffset = 0;
int pixelOffset = 0;
byte color = 0;
byte* pBackBuffer = (byte*)_image.BackBuffer;
for(int y = 0; y < mapData.Height; y++)
{
// get a pointer to first pixel in a line y
byte* pixLine = pBackBuffer;
for(int x = 0; x < mapData.Width; x++)
{
// fix #1: offset = y * width + x, not y * height + x
var mapOffset = y * mapData.Width + x;
if (mapData.Data[mapOffset])
{
//Set the pixel to white
color += 1;
}
//Shift the pixel position by 1
color = (byte)(color << 1);
//If 8 pixels have been written, write it to the backbuffer
if(++colorOffset == 8)
{
*pixLine++ = color;
color = 0;
colorOffset = 0;
}
}
// fix #2: copy any pixels left
if(colorOffset != 0)
{
*pixLine++ = color;
colorOffset = 0;
color = 0;
}
// fix #3: next line offset = previous line + stride, they are aligned
pBackBuffer += _image.BackBufferStride;
}
//Update the image
_image.AddDirtyRect(new Int32Rect(0, 0, mapData.Width, mapData.Height));
}