Break cycles without leaks - c#

I have simple code that trying to find sequence of pixel on the screen.
How two break two cycles without memory leaks in function FindPixelSequence
when if (isEqual) occures?
How to improve existing code?
namespace FindColor
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
PlayWithColor PWC = new PlayWithColor();
List<System.Drawing.Color> pixels { get; set; }
public MainWindow()
{
InitializeComponent();
pixels = new List<System.Drawing.Color>();
pixels.Add(System.Drawing.Color.FromArgb(0, 0, 0, 0));
pixels.Add(System.Drawing.Color.FromArgb(0, 153, 255, 0));
pixels.Add(System.Drawing.Color.FromArgb(0, 153, 255, 0));
pixels.Add(System.Drawing.Color.FromArgb(0, 128, 214, 0));
}
private void Button_Click(object sender, RoutedEventArgs e)
{
using (var bitmap = PWC.TakeScreen())
{
PWC.FindPixelSequence(bitmap, pixels);
}
}
}
class ColorEqualityComparer : IEqualityComparer<Color>
{
public bool Equals(Color b1, Color b2)
{
return b1.R == b2.R && b1.G == b2.G && b1.B == b2.B;
}
public int GetHashCode(Color obj)
{
throw new NotImplementedException();
}
}
public class PlayWithColor
{
[System.Runtime.InteropServices.DllImport("user32.dll")]
public static extern bool SetCursorPos(int x, int y);
private Bitmap bmpScreenshot { get; set; }
public Bitmap TakeScreen()
{
bmpScreenshot = new Bitmap(Screen.PrimaryScreen.Bounds.Width,
Screen.PrimaryScreen.Bounds.Height,
PixelFormat.Format32bppArgb);
var gfxScreenshot = Graphics.FromImage(bmpScreenshot);
int sourceX = Screen.PrimaryScreen.Bounds.X;
int sourceY = Screen.PrimaryScreen.Bounds.Y;
gfxScreenshot.CopyFromScreen(sourceX,
sourceY,
0,
0,
Screen.PrimaryScreen.Bounds.Size,
CopyPixelOperation.SourceCopy);
return bmpScreenshot;
}
public System.Drawing.Point? FindPixelSequence(Bitmap bitmap, List<Color> pixels)
{
Stopwatch watch = new Stopwatch();
List<Color> currentPixels;
watch.Start();
BitmapData data = bitmap.LockBits(new System.Drawing.Rectangle(0, 0, bitmap.Width, bitmap.Height),
ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
unsafe
{
byte* ptrSrc = (byte*)data.Scan0;
for (int y = 0; y < data.Height; y = y + 1)
{
for (int x = 0; x < data.Width; x = x + 1)
{
currentPixels = new List<Color>();
for (int i = 0; i < pixels.Count; i++)
{
byte r0 = ptrSrc[2];
byte g0 = ptrSrc[1];
byte b0 = ptrSrc[0];
Color currentPixel = Color.FromArgb(0, r0, g0, b0);
ptrSrc += 4;
currentPixels.Add(currentPixel);
}
ptrSrc -= (4 * (pixels.Count - 1));
bool isEqual = currentPixels.SequenceEqual(pixels, new ColorEqualityComparer());
if (isEqual)
{
SetCursorPos(x, y);
//how return coords of x and y from there?
}
}
}
}
bitmap.UnlockBits(data);
watch.Stop();
Debug.WriteLine(watch.ElapsedMilliseconds);
}
}
}

for the unsafe code surround it with a try finally , to always unlock the bitmap and release any memory you allocated internally
1.
unsafe
{
Stopwatch watch = new Stopwatch();
watch.Start();
try
{
....
return new Point(x,y); // To return x,y corrdinates
}
finally
{
bitmap.UnlockBits(data);
watch.Stop();
}
}
to simply the code I would use a 3rd party image tool software library for c# you have AForge.NET

Related

WriteableBitmap performance: Debugging faster then Without Debugging

I have this code to measure the performance to draw a single pixel in a WriteableBitmap, and I notice in my system a considerable performance difference between these two starting modes, both in Release configuration.
When starting in Debugging, the speed is ~ 8.6K pixels/second, and when starting without Debugging ~ 2.6K. I was expecting the opposite
I wonder if I'm missing something or what is possible to do to achieve the same performance when starting in Debugging, or it is a bug.
class App1
{
[STAThread]
static void Main()
{
new Window1().Show();
new Application().Run();
}
}
class Image1 : Image
{
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e) { }
}
class Window1 : Window
{
WriteableBitmap writeableBitmap;
Stopwatch clock1;
long t1;
int frames_counter;
bool frame;
int px;
int py;
Int32Rect single_pixel = new Int32Rect(0, 0, 1, 1);
byte[] single_pixel_array;
readonly byte[] red_collor = new byte[] { 0, 0, 255, 0 }; // B G R
readonly byte[] blue_collor = new byte[] { 255, 0, 0, 0 }; // B G R
public Window1()
{
Height = 150;
Width = 350;
Loaded += Window1_Loaded;
}
private void Window1_Loaded(object sender, RoutedEventArgs e)
{
writeableBitmap = new WriteableBitmap((int)ActualWidth, (int)ActualHeight, 96, 96, PixelFormats.Bgr32, null);
Content = new Image1
{
Stretch = Stretch.None,
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Top,
Source = writeableBitmap
};
single_pixel_array = red_collor;
frame = true;
px = 0;
py = 0;
var timer2 = new DispatcherTimer(new TimeSpan(0, 0, 0, 1), DispatcherPriority.Background, Callback2, Dispatcher.CurrentDispatcher);
var timer1 = new DispatcherTimer();
timer1.Tick += new EventHandler(Callback1);
timer1.Start();
clock1 = Stopwatch.StartNew();
t1 = clock1.ElapsedTicks;
}
private void Callback2(object sender, EventArgs e)
{
var t2 = clock1.ElapsedTicks;
var fps = 10000000F / (t2 - t1) * frames_counter;
Title = $"{fps:F1} fps";
frames_counter = 0;
t1 = t2;
}
private void Callback1(object sender, EventArgs e)
{
writeableBitmap.WritePixels(single_pixel, single_pixel_array, 4, px, py);
px++;
if (px > 100)
{
px = 0;
py++;
}
if (py > 100)
{
py = 0;
single_pixel_array = frame ? blue_collor : red_collor;
frame = !frame;
}
frames_counter++;
}
} enter code here

winforms keeps freezing -endless loop? how do i un-freeze?

I have a program which displays an image to the windows form, and places a message within the image as it paints it (this works fine) i then have a method which reads the message back. However, doing this causes the winforms screen to freeze! i must be getting stuck in an endless loop. The method does work as i do get the message back.... can anyone help un-freeze my program?
Code below:
public partial class MyImages : Form
{
//I have variables related to encoding and decoding here(deleted)
private const String MESSAGE = "2008-01-07";
Bitmap firstLoaded;
Bitmap theImage;
Bitmap imageEmbedded;
Boolean isGetMessage = false;
Boolean isEmbedImage = false;
Boolean isLoaded = false;
Graphics graphicsWindow; // reference to the graphic surface of this window
Graphics graphicsImage; // reference to in-memory surface
BitArray bitsOfMessage = new BitArray(8);
String bytesOfTheMessage = null;
public MyImages()
{
InitializeComponent();
this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.DoubleBuffer, true);
}
private void MyImages_Paint(object sender, PaintEventArgs e)
{
HandlePainting();
}
public void HandlePainting()
{
if (isLoaded == true)
{
theImage = new Bitmap(Width, Height); // bitmap for window surface copy
graphicsWindow = CreateGraphics(); // get our current window's surface
graphicsImage = Graphics.FromImage(theImage); // create surfaces from the bitmaps
graphicsImage.DrawImage(firstLoaded, 0, 0, Width, Height);
if (isEmbedImage == true)
{
theImage = embedMessageInImage(theImage);
}
else if (isGetMessage == true)
{
getEmbeddedMessage(imageEmbedded);
}
if (isGetMessage == false)
{
graphicsWindow.DrawImage(theImage, 0, 0);
}
else if (isGetMessage == true)
{
graphicsWindow.DrawImage(imageEmbedded, 0, 0);
}
}
}
private void toolStripMenuItemLoadImage_Click(object sender, EventArgs e)
{
using (OpenFileDialog ofd = new OpenFileDialog())
{
ofd.Title = "Load Image";
if (ofd.ShowDialog() == DialogResult.OK)
{
firstLoaded = new Bitmap(ofd.FileName);
this.Invalidate();
}
}
isLoaded = true;
}
private void toolStripMenuEmbedMessage_Click(object sender, EventArgs e)
{
isEmbedImage = true;
isGetMessage = false;
this.Invalidate();
}
private void toolStripMenuItemGetMessage_Click(object sender, EventArgs e)
{
isEmbedImage = false;
isGetMessage = true;
this.Invalidate();
}
public void convertToChar(int byteChar)
{
char val = Convert.ToChar(byteChar);
String nextChar = val.ToString();
bytesOfTheMessage += nextChar;
}
private Bitmap embedMessageInImage(Bitmap bmp)
{
//Embed message in this method (deleted)
//unlock the bitmaps
newBitmap.UnlockBits(newData);
bmp.Save("tina.bmp");
bmp.UnlockBits(originalData);
newBitmap.Save("tina7.bmp");
imageEmbedded = newBitmap;
return newBitmap;
}
}
private void getEmbeddedMessage(Bitmap bmp)
{
unsafe
{
//create an empty bitmap the same size as original
Bitmap newBitmap = new Bitmap(bmp.Width, bmp.Height);
//lock the original bitmap in memory
System.Drawing.Imaging.BitmapData originalData = bmp.LockBits(
new Rectangle(0, 0, bmp.Width, bmp.Height),
System.Drawing.Imaging.ImageLockMode.ReadOnly,
System.Drawing.Imaging.PixelFormat.Format24bppRgb);
//lock the new bitmap in memory
System.Drawing.Imaging.BitmapData newData = newBitmap.LockBits(
new Rectangle(0, 0, bmp.Width, bmp.Height),
System.Drawing.Imaging.ImageLockMode.WriteOnly,
System.Drawing.Imaging.PixelFormat.Format24bppRgb);
//set the number of bytes per pixel
int pixelSize = 3;
for (int y = 0; y < bmp.Height; y++)
{
//get the data from the original image
byte* originalImageRow = (byte*)originalData.Scan0 + (y * originalData.Stride);
//get the data from the new image
byte* newImageRow = (byte*)newData.Scan0 + (y * newData.Stride);
for (int x = 0; x < bmp.Width; x++)
{
byte b = (byte)(originalImageRow[x * pixelSize + 0]); // B
getEachBitOfMessage(b, BLUE);
byte g = (byte)(originalImageRow[x * pixelSize + 1]); // G
getEachBitOfMessage(g, GREEN);
byte r = ((byte)(originalImageRow[x * pixelSize + 2])); //R
getEachBitOfMessage(r, RED);
}
}
//unlock the bitmaps
newBitmap.UnlockBits(newData);
bmp.UnlockBits(originalData);
}
}
public byte changeEachBit(byte byteToManipulate, int colour, byte theMessage)
{
byte value = 0;
byte returnByte = 0;
if (colour == BLUE)
{
value= (byte)(theMessage & BValueMask);
value = (byte)(value>>5);
returnByte = (byte)(byteToManipulate & BlueMask);
returnByte = (byte)(returnByte | value);
}
else if (colour == GREEN)
{
value = (byte)(theMessage & GValueMask);
value = (byte)(value >> 3);
returnByte = (byte)(byteToManipulate & GreenMask);
returnByte = (byte)(returnByte | value);
}
else if (colour == RED)
{
value = (byte)(theMessage & RValueMask);
returnByte = (byte)(byteToManipulate & RedMask);
returnByte = (byte)(returnByte | value);
}
return returnByte;
}
public void getEachBitOfMessage(byte byteToManipulate, int colour)
{
//I Input bits into image here (deleted)
}
}
}
Let it freeze and click the Pause button on the top toolbar.
This will cause the debugger to break wherever execution may be, and you can then easily identify where it got stuck, and try and find out why as well (don't forget to watch values using the watch window or hovering them).

DrawImage fails to position sliced images correctly

For a couple of days now I've tried to figure out why my nine-slice code does not work as expected. As far as I can see, there seems to be an issue with the Graphics.DrawImage method which handles my nine slice images incorrectly. So my problem is how to compensate for the incorrect scaling that is performed when running my code on the compact framework. I might add that this code of course works perfectly when running in the full framework environment. The problem only occurs when scaling the image to a larger image not the other way around. Here is the snippet:
public class NineSliceBitmapSnippet
{
private Bitmap m_OriginalBitmap;
public int CornerLength { get; set; }
/// <summary>
/// Initializes a new instance of the NineSliceBitmapSnippet class.
/// </summary>
public NineSliceBitmapSnippet(Bitmap bitmap)
{
CornerLength = 5;
m_OriginalBitmap = bitmap;
}
public Bitmap ScaleSingleBitmap(Size size)
{
Bitmap scaledBitmap = new Bitmap(size.Width, size.Height);
int[] horizontalTargetSlices = Slice(size.Width);
int[] verticalTargetSlices = Slice(size.Height);
int[] horizontalSourceSlices = Slice(m_OriginalBitmap.Width);
int[] verticalSourceSlices = Slice(m_OriginalBitmap.Height);
using (Graphics graphics = Graphics.FromImage(scaledBitmap))
{
using (Brush brush = new SolidBrush(Color.Fuchsia))
{
graphics.FillRectangle(brush, new Rectangle(0, 0, size.Width, size.Height));
}
int horizontalTargetOffset = 0;
int verticalTargetOffset = 0;
int horizontalSourceOffset = 0;
int verticalSourceOffset = 0;
for (int x = 0; x < horizontalTargetSlices.Length; x++)
{
verticalTargetOffset = 0;
verticalSourceOffset = 0;
for (int y = 0; y < verticalTargetSlices.Length; y++)
{
Rectangle destination = new Rectangle(horizontalTargetOffset, verticalTargetOffset, horizontalTargetSlices[x], verticalTargetSlices[y]);
Rectangle source = new Rectangle(horizontalSourceOffset, verticalSourceOffset, horizontalSourceSlices[x], verticalSourceSlices[y]);
graphics.DrawImage(m_OriginalBitmap, destination, source, GraphicsUnit.Pixel);
verticalTargetOffset += verticalTargetSlices[y];
verticalSourceOffset += verticalSourceSlices[y];
}
horizontalTargetOffset += horizontalTargetSlices[x];
horizontalSourceOffset += horizontalSourceSlices[x];
}
}
return scaledBitmap;
}
public int[] Slice(int length)
{
int cornerLength = CornerLength;
if (length <= (cornerLength * 2))
throw new Exception("Image to small for sliceing up");
int[] slices = new int[3];
slices[0] = cornerLength;
slices[1] = length - (2 * cornerLength);
slices[2] = cornerLength;
return slices;
}
}
So, my question is, does anybody now how I could compensate the incorrect scaling?
/Dan
After some more trial and error I've finally found a solution to my problem. The scaling problems has always been to the top-center, right-center, bottom-center and left-center slices since they're always stretched in only one direction according to the logic of nine slice scaling. If I apply a temporarely square stretch to those slices before applying the correct stretch the final bitmap will be correct. Once again the problem is only visible in the .Net Compact Framework of a Windows CE device (Smart Device). Here's a snippet with code adjusting for the bug in CF. My only concern now is that the slices that get square stretched will take much more memory due to the correction code. On the other hand this step is only a short period of time so I might get away with it. ;)
public class NineSliceBitmapSnippet
{
private Bitmap m_OriginalBitmap;
public int CornerLength { get; set; }
public NineSliceBitmapSnippet(Bitmap bitmap)
{
CornerLength = 5;
m_OriginalBitmap = bitmap;
}
public Bitmap Scale(Size size)
{
if (m_OriginalBitmap != null)
{
return ScaleSingleBitmap(size);
}
return null;
}
public Bitmap ScaleSingleBitmap(Size size)
{
Bitmap scaledBitmap = new Bitmap(size.Width, size.Height);
int[] horizontalTargetSlices = Slice(size.Width);
int[] verticalTargetSlices = Slice(size.Height);
int[] horizontalSourceSlices = Slice(m_OriginalBitmap.Width);
int[] verticalSourceSlices = Slice(m_OriginalBitmap.Height);
using (Graphics graphics = Graphics.FromImage(scaledBitmap))
{
using (Brush brush = new SolidBrush(Color.Fuchsia))
{
graphics.FillRectangle(brush, new Rectangle(0, 0, size.Width, size.Height));
}
int horizontalTargetOffset = 0;
int verticalTargetOffset = 0;
int horizontalSourceOffset = 0;
int verticalSourceOffset = 0;
for (int x = 0; x < horizontalTargetSlices.Length; x++)
{
verticalTargetOffset = 0;
verticalSourceOffset = 0;
for (int y = 0; y < verticalTargetSlices.Length; y++)
{
Rectangle destination = new Rectangle(horizontalTargetOffset, verticalTargetOffset, horizontalTargetSlices[x], verticalTargetSlices[y]);
Rectangle source = new Rectangle(horizontalSourceOffset, verticalSourceOffset, horizontalSourceSlices[x], verticalSourceSlices[y]);
bool isWidthAffectedByVerticalStretch = (y == 1 && (x == 0 || x == 2) && destination.Height > source.Height);
bool isHeightAffectedByHorizontalStretch = (x == 1 && (y == 0 || y == 2) && destination.Width > source.Width);
if (isHeightAffectedByHorizontalStretch)
{
BypassDrawImageError(graphics, destination, source, Orientation.Horizontal);
}
else if (isWidthAffectedByVerticalStretch)
{
BypassDrawImageError(graphics, destination, source, Orientation.Vertical);
}
else
{
graphics.DrawImage(m_OriginalBitmap, destination, source, GraphicsUnit.Pixel);
}
verticalTargetOffset += verticalTargetSlices[y];
verticalSourceOffset += verticalSourceSlices[y];
}
horizontalTargetOffset += horizontalTargetSlices[x];
horizontalSourceOffset += horizontalSourceSlices[x];
}
}
return scaledBitmap;
}
private void BypassDrawImageError(Graphics graphics, Rectangle destination, Rectangle source, Orientation orientationAdjustment)
{
Size adjustedSize = Size.Empty;
switch (orientationAdjustment)
{
case Orientation.Horizontal:
adjustedSize = new Size(destination.Width, destination.Width);
break;
case Orientation.Vertical:
adjustedSize = new Size(destination.Height, destination.Height);
break;
default:
break;
}
using (Bitmap quadScaledBitmap = new Bitmap(adjustedSize.Width, adjustedSize.Height))
{
using (Graphics tempGraphics = Graphics.FromImage(quadScaledBitmap))
{
tempGraphics.Clear(Color.Fuchsia);
tempGraphics.DrawImage(m_OriginalBitmap, new Rectangle(0, 0, adjustedSize.Width, adjustedSize.Height), source, GraphicsUnit.Pixel);
}
graphics.DrawImage(quadScaledBitmap, destination, new Rectangle(0, 0, quadScaledBitmap.Width, quadScaledBitmap.Height), GraphicsUnit.Pixel);
}
}
public int[] Slice(int length)
{
int cornerLength = CornerLength;
if (length <= (cornerLength * 2))
throw new Exception("Image to small for sliceing up");
int[] slices = new int[3];
slices[0] = cornerLength;
slices[1] = length - (2 * cornerLength);
slices[2] = cornerLength;
return slices;
}
}

C# Very Simple Image Resizer

I am in need of a very simple c# image resizer. By simple, I mean simple. This is just a program that loops through a single directory and changes all the pictures in that directory to the same resolution. Here's what I have so far.
private void Form1_Load(object sender, EventArgs e)
{
string[] files = null;
int count = 0;
files = System.IO.Directory.GetFiles(#"C:\Users\..\..\ChristmasPicsResized");
foreach (string file in files)
{
System.Drawing.Bitmap bmp = System.Drawing.Bipmap.FromFile(file);
ResizeBitmap(bmp, 807, 605);
bmp.Save(file);
count++;
lblCount.Text = count.ToString();
}
}
public Bitmap ResizeBitmap(Bitmap b, int nWidth, int nHeight)
{
Bitmap result = new Bitmap(nWidth, nHeight);
using (Graphics g = Graphics.FromImage((Image)result))
g.DrawImage(b, 0, 0, nWidth, nHeight);
return result;
}
The problem I ran into is that the picture cannot be saved while it is open. I am unsure how to make this into a file stream. What should be a very simple app doesn't seem so simple to me. Any help please?
Try saving to a temp file, then delete the original file and rename the temp file to the original file name.
Have a look at C# Image to Byte Array and Byte Array to Image Converter Class
public byte[] imageToByteArray(System.Drawing.Image imageIn)
{
MemoryStream ms = new MemoryStream();
imageIn.Save(ms,System.Drawing.Imaging.ImageFormat.Gif);
return ms.ToArray();
}
and
public Image byteArrayToImage(byte[] byteArrayIn)
{
MemoryStream ms = new MemoryStream(byteArrayIn);
Image returnImage = Image.FromStream(ms);
return returnImage;
}
This way you can close the image after you have read it in, and can then save it over the existing one.
You could also render the resized images into a different folder to preserve the original, high-resolution images. Maybe you'll need them one day (i did that mistake once...).
I created this nuget package which does resizing for you:
http://nuget.org/packages/Simple.ImageResizer
Source and howto here:
https://github.com/terjetyl/Simple.ImageResizer
My C# Image Extension class:
namespace MycImageExtension
{
public static class Helper
{
public static Image Clip(this Image imgPhoto, int width, int height)
{
return Clip(imgPhoto, width, height, false);
}
public static Image ToImage(this byte[] ba)
{
var ms = new MemoryStream(ba);
return Image.FromStream(ms);
}
public static byte[] ToArray(this Image imgPhoto)
{
var ms = new MemoryStream();
imgPhoto.Save(ms, ImageFormat.Jpeg);
return ms.ToArray();
}
static ImageCodecInfo GetImageCodec(string mimetype)
{
foreach (ImageCodecInfo ici in ImageCodecInfo.GetImageEncoders())
{
if (ici.MimeType == mimetype) return ici;
}
return null;
}
public static Image Clip(this Image imgPhoto, int width, int height, bool stretch)
{
if (!stretch && (imgPhoto.Width <= width && imgPhoto.Height <= height))
return imgPhoto;
// detect if portrait
if (imgPhoto.Height > imgPhoto.Width)
{
// swap
int a = width;
width = height;
height = a;
}
var d = new Dimension(imgPhoto.Width, imgPhoto.Height);
double scale = d.NewSizeScaleFactor(new Dimension(width, height), stretch);
var newD = scale * d;
if (stretch)
{
if (!(newD.Width == width || newD.Height == height))
throw new Exception("Stretching algo has some error");
}
var bmPhoto = new Bitmap(imgPhoto, new Size(newD.Width, newD.Height));
return bmPhoto;
}
// using for crystal report
public static Image PadImage(this Image imgPhoto, int width, int height)
{
// detect if portrait
if (imgPhoto.Height > imgPhoto.Width)
{
// swap
int a = width;
width = height;
height = a;
}
var d = new Dimension(imgPhoto.Width, imgPhoto.Height);
double scale = d.NewSizeScaleFactor(new Dimension(width, height), true);
Dimension newSize = scale * d;
PadAt padAt;
int padNeeded;
newSize.GetPadNeeded(new Dimension(width, height), out padAt, out padNeeded);
int padLeft = 0, padTop = 0;
if (padAt == PadAt.Width)
padLeft = padNeeded;
else if (padAt == PadAt.Height)
padTop = padNeeded;
var bmPhoto = new Bitmap(width, height, PixelFormat.Format24bppRgb);
var grPhoto = Graphics.FromImage(bmPhoto);
grPhoto.Clear(Color.White);
grPhoto.InterpolationMode = InterpolationMode.HighQualityBicubic;
grPhoto.DrawImage(imgPhoto,
new Rectangle(padLeft, padTop, newSize.Width, newSize.Height),
new Rectangle(0, 0, imgPhoto.Width, imgPhoto.Height),
GraphicsUnit.Pixel);
grPhoto.Dispose();
return bmPhoto;
}
}
public enum PadAt { None = 0, Width = 1, Height }
public struct Dimension
{
public int Width { set; get; }
public int Height { set; get; }
public Dimension(int width, int height)
: this()
{
this.Width = width;
this.Height = height;
}
public override string ToString()
{
return string.Format("Width: {0} Height: {1}", Width, Height);
}
public static Dimension operator *(Dimension src, double scale)
{
return new Dimension((int)Math.Ceiling((scale * src.Width)), (int)Math.Ceiling((scale * src.Height)));
}
public static Dimension operator *(double scale, Dimension src)
{
return new Dimension((int)Math.Ceiling((scale * src.Width)), (int)Math.Ceiling((scale * src.Height)));
}
public double NewSizeScaleFactor(Dimension newSize, bool stretch)
{
if (!stretch
&&
(this.Width <= newSize.Width && this.Height <= newSize.Height))
return 1;
double widthScaleFactor = (double)newSize.Width / this.Width;
double heightScaleFactor = (double)newSize.Height / this.Height;
// return the lowest scale factor
if (widthScaleFactor < heightScaleFactor)
return widthScaleFactor;
else if (heightScaleFactor < widthScaleFactor)
return heightScaleFactor;
else
return widthScaleFactor; // can even use heightscalefactor
}
public Dimension Clip(Dimension newSize, bool stretch)
{
if (!stretch
&&
(this.Width <= newSize.Width && this.Height <= newSize.Height))
return new Dimension(this.Width, this.Height);
double smallestScaleFactor = NewSizeScaleFactor(newSize, stretch);
return new Dimension((int)(this.Width * smallestScaleFactor), (int)(this.Height * smallestScaleFactor));
}
// so crystal report images would have exact dimension
public void GetPadNeeded(Dimension newSize, out PadAt padAt, out int padNeeded)
{
if (this.Width == newSize.Width && this.Height == newSize.Height)
{
padAt = PadAt.None;
padNeeded = 0;
return;
}
if (this.Width > newSize.Width || this.Height > newSize.Height)
throw new Exception("Source cannot be bigger than the new size");
if (this.Width != newSize.Width && this.Height != newSize.Height)
throw new Exception("At least one side should be equal");
if (newSize.Width != this.Width)
{
padAt = PadAt.Width;
padNeeded = (newSize.Width - this.Width) / 2;
}
else if (newSize.Height != this.Width)
{
padAt = PadAt.Height;
padNeeded = (newSize.Height - this.Height) / 2;
}
else
{
throw new Exception("Some anomaly occured, contact the programmer");
}
}
// test the logic
static void X()
{
var ls = new Dimension[]
{
new Dimension(400, 400), // as is
new Dimension(640, 480), // as is
new Dimension(600, 480), // as is
new Dimension(800, 600), // as is
new Dimension(800, 400), // as is
new Dimension(1280, 960), // as is
new Dimension(1280, 961), // as is
new Dimension(1281, 960), // as is
new Dimension(1280, 900), // as is
new Dimension(1000, 960), // as is
new Dimension(1000, 970), // as is
new Dimension(1000, 900), // as is
new Dimension(1380, 960), // clip
new Dimension(1280, 1000), // clip
new Dimension(1380, 1300), // clip
new Dimension(1600, 1200), // clip
new Dimension(1600, 1000), // clip
new Dimension(1800, 1200), // clip
new Dimension(1800, 1000), // clip
new Dimension(1400, 1200), // clip
new Dimension(1400, 1000), // clip
new Dimension(960, 1280), // clip
new Dimension(960, 1300), // clip
new Dimension(970, 1280) // clip
};
foreach (var l in ls)
{
// saving to database...
double scale = l.NewSizeScaleFactor(new Dimension(1280, 960), false);
Dimension newSize = scale * l;
// ...saving to database
// display to crystal report...
double scaleA = l.NewSizeScaleFactor(new Dimension(800, 600), true);
Dimension newSizeA = scaleA * l;
PadAt padAt;
int padNeeded;
newSizeA.GetPadNeeded(new Dimension(800, 600), out padAt, out padNeeded);
// ...display to crystal report
Console.WriteLine("Pad {0} {1}", padAt, padNeeded);
Console.WriteLine();
}
Console.ReadLine();
}
}
}

Unsafe Per Pixel access, 30ms access for 1756000 pixels

So I've been sharing some thoughts on the above topic title on my website about fast, unsafe pixel access. A gentlemen gave me a rough example of how he'd do it in C++, but that doesn't help me in C# unless I can interop it, and the interop is fast as well. I had found a class in the internet that was written using MSDN help, to unsafely access pixels. The class is exceptionally fast, but it's not fast enough. Here's the class:
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;
namespace DCOMProductions.Desktop.ScreenViewer {
public unsafe class UnsafeBitmap {
Bitmap bitmap;
// three elements used for MakeGreyUnsafe
int width;
BitmapData bitmapData = null;
Byte* pBase = null;
public UnsafeBitmap(Bitmap bitmap) {
this.bitmap = new Bitmap(bitmap);
}
public UnsafeBitmap(int width, int height) {
this.bitmap = new Bitmap(width, height, PixelFormat.Format32bppArgb);
}
public void Dispose() {
bitmap.Dispose();
}
public Bitmap Bitmap {
get {
return (bitmap);
}
}
private Point PixelSize {
get {
GraphicsUnit unit = GraphicsUnit.Pixel;
RectangleF bounds = bitmap.GetBounds(ref unit);
return new Point((int)bounds.Width, (int)bounds.Height);
}
}
public void LockBitmap() {
GraphicsUnit unit = GraphicsUnit.Pixel;
RectangleF boundsF = bitmap.GetBounds(ref unit);
Rectangle bounds = new Rectangle((int)boundsF.X,
(int)boundsF.Y,
(int)boundsF.Width,
(int)boundsF.Height);
// Figure out the number of bytes in a row
// This is rounded up to be a multiple of 4
// bytes, since a scan line in an image must always be a multiple of 4 bytes
// in length.
width = (int)boundsF.Width * sizeof(Pixel);
if (width % 4 != 0) {
width = 4 * (width / 4 + 1);
}
bitmapData =
bitmap.LockBits(bounds, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
pBase = (Byte*)bitmapData.Scan0.ToPointer();
}
public Pixel GetPixel(int x, int y) {
Pixel returnValue = *PixelAt(x, y);
return returnValue;
}
public void SetPixel(int x, int y, Pixel colour) {
Pixel* pixel = PixelAt(x, y);
*pixel = colour;
}
public void UnlockBitmap() {
bitmap.UnlockBits(bitmapData);
bitmapData = null;
pBase = null;
}
public Pixel* PixelAt(int x, int y) {
return (Pixel*)(pBase + y * width + x * sizeof(Pixel));
}
}
}
Basically what I am doing is copying the entire screen and comparing each pixel to and old copy. On a 1680x1050 bitmap, this takes approximately 300 milliseconds using the following code.
private Bitmap GetInvalidFrame(Bitmap frame) {
Stopwatch sp = new Stopwatch();
sp.Start();
if (m_FrameBackBuffer == null) {
return frame;
}
Int32 pixelsToRead = frame.Width * frame.Height;
Int32 x = 0, y = 0;
UnsafeBitmap unsafeBitmap = new UnsafeBitmap(frame);
UnsafeBitmap unsafeBuffBitmap = new UnsafeBitmap(m_FrameBackBuffer);
UnsafeBitmap retVal = new UnsafeBitmap(frame.Width, frame.Height);
unsafeBitmap.LockBitmap();
unsafeBuffBitmap.LockBitmap();
retVal.LockBitmap();
do {
for (x = 0; x < frame.Width; x++) {
Pixel newPixel = unsafeBitmap.GetPixel(x, y);
Pixel oldPixel = unsafeBuffBitmap.GetPixel(x, y);
if (newPixel.Alpha != oldPixel.Alpha || newPixel.Red != oldPixel.Red || newPixel.Green != oldPixel.Green || newPixel.Blue != oldPixel.Blue) {
retVal.SetPixel(x, y, newPixel);
}
else {
// Skip pixel
}
}
y++;
} while (y != frame.Height);
unsafeBitmap.UnlockBitmap();
unsafeBuffBitmap.UnlockBitmap();
retVal.UnlockBitmap();
sp.Stop();
System.Diagnostics.Debug.WriteLine(sp.Elapsed.Milliseconds.ToString());
sp.Reset();
return retVal.Bitmap;
}
Is there any possible method/means/approach that I could speed this up to about 30ms? I can copy the screen in about 30ms using Graphics.CopyFromScreen(), so that produces approximately 30 frames each second. However, a program only runs as fast as its slower counterpart, so the 300ms delay in GetInvalidFrame, slows this down to about 1 - 3 frames each second. This isn't good for a meeting software.
Any advice, approaches, pointers in the right direction would be absolutely wonderful! Also, the code that is used to draw the bitmap on the client-side is below as well.
To comment on Dmitriy's answer/comment:
#region RootWorkItem
private ScreenClient m_RootWorkItem;
/// <summary>
/// Gets the RootWorkItem
/// </summary>
public ScreenClient RootWorkItem {
get {
if (m_RootWorkItem == null) {
m_RootWorkItem = new ScreenClient();
m_RootWorkItem.FrameRead += new EventHandler<FrameEventArgs>(RootWorkItem_FrameRead);
}
return m_RootWorkItem;
}
}
#endregion
private void RootWorkItem_FrameRead(Object sender, FrameEventArgs e) {
if (e.Frame != null) {
if (uxSurface.Image != null) {
Bitmap frame = (Bitmap)uxSurface.Image;
Graphics g = Graphics.FromImage(frame);
g.DrawImage(e.Frame, 0, 0); // Draw only updated pixels
uxSurface.Image = frame;
}
else {
uxSurface.Image = e.Frame; // Draw initial, full image
}
}
else {
uxSurface.Image = null;
}
}
Unsafe approach with usage of integers instead of Pixels and single loop:
private static Bitmap GetInvalidFrame(Bitmap oldFrame, Bitmap newFrame)
{
if (oldFrame.Size != newFrame.Size)
{
throw new ArgumentException();
}
Bitmap result = new Bitmap(oldFrame.Width, oldFrame.Height, oldFrame.PixelFormat);
Rectangle lockArea = new Rectangle(Point.Empty, oldFrame.Size);
PixelFormat format = PixelFormat.Format32bppArgb;
BitmapData oldData = oldFrame.LockBits(lockArea, ImageLockMode.ReadOnly, format);
BitmapData newData = newFrame.LockBits(lockArea, ImageLockMode.ReadOnly, format);
BitmapData resultData = result.LockBits(lockArea, ImageLockMode.WriteOnly, format);
int len = resultData.Height * Math.Abs(resultData.Stride) / 4;
unsafe
{
int* pOld = (int*)oldData.Scan0;
int* pNew = (int*)newData.Scan0;
int* pResult = (int*)resultData.Scan0;
for (int i = 0; i < len; i++)
{
int oldValue = *pOld++;
int newValue = *pNew++;
*pResult++ = oldValue != newValue ? newValue : 0 /* replace with 0xff << 24 if you need non-transparent black pixel */;
// *pResult++ = *pOld++ ^ *pNew++; // if you can use XORs.
}
}
oldFrame.UnlockBits(oldData);
newFrame.UnlockBits(newData);
result.UnlockBits(resultData);
return result;
}
I think you really can use XORed frames here and I hope that this can have better performance on both sides.
private static void XorFrames(Bitmap leftFrame, Bitmap rightFrame)
{
if (leftFrame.Size != rightFrame.Size)
{
throw new ArgumentException();
}
Rectangle lockArea = new Rectangle(Point.Empty, leftFrame.Size);
PixelFormat format = PixelFormat.Format32bppArgb;
BitmapData leftData = leftFrame.LockBits(lockArea, ImageLockMode.ReadWrite, format);
BitmapData rightData = rightFrame.LockBits(lockArea, ImageLockMode.ReadOnly, format);
int len = leftData.Height * Math.Abs(rightData.Stride) / 4;
unsafe
{
int* pLeft = (int*)leftData.Scan0;
int* pRight = (int*)rightData.Scan0;
for (int i = 0; i < len; i++)
{
*pLeft++ ^= *pRight++;
}
}
leftFrame.UnlockBits(leftData);
rightFrame.UnlockBits(rightData);
}
You can use this procedure on both sides in following way:
On server side you need to evaluate difference between old and new frame, send it to client and replace old frame by new. The server code should look something like this:
XorFrames(oldFrame, newFrame); // oldFrame ^= newFrame
Send(oldFrame); // send XOR of two frames
oldFrame = newFrame;
On client side you need to update your current frame with xor frame recieved from server:
XorFrames((Bitmap)uxSurface.Image, e.Frame);
Here: Utilizing the GPU with c# there are mentioned some librarys for using the GPU from C#.
Yes, you can do so by using unsafe code.
BitmapData d = l.LockBits(new Rectangle(0, 0, l.Width, l.Height), ImageLockMode.ReadOnly,l.PixelFormat);
IntPtr scan = d.Scan0;
unsafe
{
byte* p = (byte*)(void*)scan;
//dostuff
}
Check out http://www.codeproject.com/KB/GDI-plus/csharpgraphicfilters11.aspx for some basic examples of this kind of stuff. My code is based on that.
Note:
One of the reasons this will be much faster than yours is that you are separately comparing each channel instead of just comparing the entire byte using one operation. Similarly, changing PixelAt to give you a byte to facilitate this would probably give you an improvement.
Instead of checking each and every pixel, you can just perform a basic memory compare of the 2 bitmaps. In C, something like memcmp().
This would give you a much quicker test to let you know that the images are the same or not. Only when you know they are different do you need to resort to the more expensive code that will help you determine where they are different (if you even need to know that).
I am not a C# person though, so I don't know how easy it is to get access to the raw memory.
Was able to slice off about 60ms. I think this is going to require the GPU. I'm not seeing any solution to this utilizing the CPU, even by comparing more than one byte/pixel at a time, unless someone can whip up a code sample to show me otherwise. Still sits at about 200-260ms, far too slow for 30fps.
private static BitmapData m_OldData;
private static BitmapData m_NewData;
private static unsafe Byte* m_OldPBase;
private static unsafe Byte* m_NewPBase;
private static unsafe Pixel* m_OldPixel;
private static unsafe Pixel* m_NewPixel;
private static Int32 m_X;
private static Int32 m_Y;
private static Stopwatch m_Watch = new Stopwatch();
private static GraphicsUnit m_GraphicsUnit = GraphicsUnit.Pixel;
private static RectangleF m_OldBoundsF;
private static RectangleF m_NewBoundsF;
private static Rectangle m_OldBounds;
private static Rectangle m_NewBounds;
private static Pixel m_TransparentPixel = new Pixel() { Alpha = 0x00, Red = 0, Green = 0, Blue = 0 };
private Bitmap GetInvalidFrame(Bitmap frame) {
if (m_FrameBackBuffer == null) {
return frame;
}
m_Watch.Start();
unsafe {
m_OldBoundsF = m_FrameBackBuffer.GetBounds(ref m_GraphicsUnit);
m_OldBounds = new Rectangle((Int32)m_OldBoundsF.X, (Int32)m_OldBoundsF.Y, (Int32)m_OldBoundsF.Width, (Int32)m_OldBoundsF.Height);
m_OldData = m_FrameBackBuffer.LockBits(m_OldBounds, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
m_NewBoundsF = m_FrameBackBuffer.GetBounds(ref m_GraphicsUnit);
m_NewBounds = new Rectangle((Int32)m_NewBoundsF.X, (Int32)m_NewBoundsF.Y, (Int32)m_NewBoundsF.Width, (Int32)m_NewBoundsF.Height);
m_NewData = frame.LockBits(m_NewBounds, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
m_OldPBase = (Byte*)m_OldData.Scan0.ToPointer();
m_NewPBase = (Byte*)m_NewData.Scan0.ToPointer();
do {
for (m_X = 0; m_X < frame.Width; m_X++) {
m_OldPixel = (Pixel*)(m_OldPBase + m_Y * m_OldData.Stride + 1 + m_X * sizeof(Pixel));
m_NewPixel = (Pixel*)(m_NewPBase + m_Y * m_NewData.Stride + 1 + m_X * sizeof(Pixel));
if (m_OldPixel->Alpha == m_NewPixel->Alpha // AccessViolationException accessing Property in get {}
|| m_OldPixel->Red == m_NewPixel->Red
|| m_OldPixel->Green == m_NewPixel->Green
|| m_OldPixel->Blue == m_NewPixel->Blue) {
// Set the transparent pixel
*m_NewPixel = m_TransparentPixel;
}
}
m_Y++; //Debug.WriteLine(String.Format("X: {0}, Y: {1}", m_X, m_Y));
} while (m_Y < frame.Height);
}
m_Y = 0;
m_Watch.Stop();
Debug.WriteLine("Time elapsed: " + m_Watch.ElapsedMilliseconds.ToString());
m_Watch.Reset();
return frame;
}

Categories

Resources