How could I generate a System.Drawing.Image that contains the differences between the pixels of two other images?
Something similar to GitHub does, but written in C#
The algorithm that GiHub uses is implemented in javascript. There is a context-blender project that replicates Photoshop blend modes.
Do you know if is translated to C# or a similar algorithm that has the same quality level? I need to manage also transparent images (with alpha channel).
Here's a quick and dirty implementation:
void Main()
{
var a = (Bitmap)Image.FromFile("image1.png");
var b = (Bitmap)Image.FromFile("image2.png");
var diff = PixelDiff(a, b);
}
unsafe Bitmap PixelDiff(Bitmap a, Bitmap b)
{
Bitmap output = new Bitmap(a.Width, a.Height, PixelFormat.Format32bppArgb);
Rectangle rect = new Rectangle(Point.Empty, a.Size);
using (var aData = a.LockBitsDisposable(rect, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb))
using (var bData = b.LockBitsDisposable(rect, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb))
using (var outputData = output.LockBitsDisposable(rect, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb))
{
byte* aPtr = (byte*)aData.Scan0;
byte* bPtr = (byte*)bData.Scan0;
byte* outputPtr = (byte*)outputData.Scan0;
int len = aData.Stride * aData.Height;
for (int i = 0; i < len; i++)
{
// For alpha use the average of both images (otherwise pixels with the same alpha won't be visible)
if ((i + 1) % 4 == 0)
*outputPtr = (byte)((*aPtr + *bPtr) / 2);
else
*outputPtr = (byte)~(*aPtr ^ *bPtr);
outputPtr++;
aPtr++;
bPtr++;
}
}
return output;
}
static class Extensions
{
public static DisposableImageData LockBitsDisposable(this Bitmap bitmap, Rectangle rect, ImageLockMode flags, PixelFormat format)
{
return new DisposableImageData(bitmap, rect, flags, format);
}
public class DisposableImageData : IDisposable
{
private readonly Bitmap _bitmap;
private readonly BitmapData _data;
internal DisposableImageData(Bitmap bitmap, Rectangle rect, ImageLockMode flags, PixelFormat format)
{
bitmap.CheckArgumentNull("bitmap");
_bitmap = bitmap;
_data = bitmap.LockBits(rect, flags, format);
}
public void Dispose()
{
_bitmap.UnlockBits(_data);
}
public IntPtr Scan0
{
get { return _data.Scan0; }
}
public int Stride
{
get { return _data.Stride;}
}
public int Width
{
get { return _data.Width;}
}
public int Height
{
get { return _data.Height;}
}
public PixelFormat PixelFormat
{
get { return _data.PixelFormat;}
}
public int Reserved
{
get { return _data.Reserved;}
}
}
}
Notes:
this implementation assumes that both images have the same size, which might not be the case... taking different sizes into account is possible of course, just a little harder.
the LockBitsDisposable method is just a convenience, if you prefer you can use the standard LockBits method (but don't forget to unlock the bits when you're done)
A quick google search yielded this:
http://www.bryancook.net/2009/10/find-differences-between-images-c.html
If your're going to be using ARGB rather then RGB, it'll probably need a bit of editing. If you wanted to get that 'inverted difference' effect, like in the Github link posted, you could find the differance between RGB colors and use that for each pixel in the difference image, etc.
Related
I am work on an ASP.NetCore 5 project that requires users to upload document using a normal file picker, so i have to check if the document the user is uploading is blurry before resampling it and saving it to a database.
I did some research and found out that imagemagick.net would help me accomplish all of that, but can't seem to find a way around it
I just googled a bit around and fiddled some Stackoverflow / Blog answers to this together:
public static class ImageExtensions
{
// as seen here: https://pyimagesearch.com/2015/09/07/blur-detection-with-opencv/
public static bool IsBlurry(this Image image, double threshold = 100.0)
{
var mat = GetMatFromSDImage(image);
var varianceOfLaplacian = VarianceOfLaplacian(mat);
return varianceOfLaplacian < threshold;
}
// as seen here: https://stackoverflow.com/questions/58005091/how-to-get-the-variance-of-laplacian-in-c-sharp
private static double VarianceOfLaplacian(Mat mat)
{
using var laplacian = new Mat();
CvInvoke.Laplacian(mat, laplacian, DepthType.Cv64F);
var mean = new MCvScalar();
var stddev = new MCvScalar();
CvInvoke.MeanStdDev(laplacian, ref mean, ref stddev);
return stddev.V0 * stddev.V0;
}
// as found here https://stackoverflow.com/questions/40384487/system-drawing-image-to-emgu-cv-mat
private static Mat GetMatFromSDImage(Image image)
{
int stride = 0;
Bitmap bmp = new Bitmap(image);
System.Drawing.Rectangle rect = new System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height);
System.Drawing.Imaging.BitmapData bmpData = bmp.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadWrite, bmp.PixelFormat);
System.Drawing.Imaging.PixelFormat pf = bmp.PixelFormat;
if (pf == System.Drawing.Imaging.PixelFormat.Format32bppArgb)
{
stride = bmp.Width * 4;
}
else
{
stride = bmp.Width * 3;
}
Image<Bgra, byte> cvImage = new Image<Bgra, byte>(bmp.Width, bmp.Height, stride, (IntPtr)bmpData.Scan0);
bmp.UnlockBits(bmpData);
return cvImage.Mat;
}
}
I noted the origin of the snippets, as nothing has been implemented by me here ;) It does require the Emgu.CV package not imagemagick.
I hope this helps, it passed the few tests, i just did.
I'm working on an image processing project, and I've read that the fastest way to manipulate a bitmap image is to copy it from a byte array using Marshal.Copy(). However, for whatever reason, nothing is being copied from my byte array to my Bitmap, and there's not a clear reason why. This is the code I'm using to copy into my Bitmap:
public void UpdateImage()
{
var data = image.LockBits(
new Rectangle(Point.Empty, image.Size),
ImageLockMode.WriteOnly,
PixelFormat.Format32bppArgb);
Marshal.Copy(backBuffer, 0, data.Scan0, backBuffer.Length);
image.UnlockBits(data);
Console.WriteLine("UpdateImage");//For debugging purposes
}
I'm attempting to fill the image with complete black, and looking at the data of the backBuffer, it appears to be correct, and as expected, where as when I check any pixel of 'image' it is completely blank. I have no idea why nothing is happening. Any advice would be much appreciated!
Edit: I apologize, I'm a bit new around here, let me provide some more information. Specifically, I am working on some GPU accelerated image processing using Cloo/OpenCL. I wanted to fill the screen with black to make sure that I am doing things correctly, although I am evidently not. Here is the entire class file I'm using:
public class RenderTarget
{
public GraphicsDevice GraphicsDevice;
private byte[] backBuffer;
public Bitmap image;
private ComputeKernel fillKernel;
private ComputeProgram fillProgram;
public void UpdateImage()
{
var data = image.LockBits(
new Rectangle(Point.Empty, image.Size),
ImageLockMode.WriteOnly,
PixelFormat.Format32bppArgb);
Marshal.Copy(backBuffer, 0, data.Scan0, backBuffer.Length);
image.UnlockBits(data);
Console.WriteLine("UpdateImage");
}
//Test method ONLY
public void FillScreen(Color color)
{
if (fillProgram == null)//temporary, all kernels should be compiled on start up. In fact, these probably should be static
{
string fillText = #"
kernel void fillScreen(global uchar* data_out, int from, int to, uchar a, uchar r, uchar g, uchar b){
for (int i = from; i < to; i += 4){
data_out[i] = a;
data_out[i + 1] = r;
data_out[i + 2] = g;
data_out[i + 3] = b;
}
}";
fillProgram = new ComputeProgram(GraphicsDevice.context, fillText);
fillProgram.Build(null, null, null, IntPtr.Zero);
fillKernel = fillProgram.CreateKernel("fillScreen");
}
ComputeBuffer<byte> backBufferBuffer = new ComputeBuffer<byte>(GraphicsDevice.context, ComputeMemoryFlags.ReadOnly | ComputeMemoryFlags.UseHostPointer, backBuffer);
fillKernel.SetMemoryArgument(0, backBufferBuffer);
for (int i = 0; i < backBuffer.Length / 10000; i++)
{
fillKernel.SetValueArgument<int>(1, i * 10000);
fillKernel.SetValueArgument<int>(2, (i + 1) * 10000);
fillKernel.SetValueArgument<byte>(3, color.A);
fillKernel.SetValueArgument<byte>(4, color.R);
fillKernel.SetValueArgument<byte>(5, color.G);
fillKernel.SetValueArgument<byte>(6, color.B);
GraphicsDevice.queue.ExecuteTask(fillKernel, null);
}
GraphicsDevice.queue.ReadFromBuffer(backBufferBuffer, ref backBuffer, false, null);
GraphicsDevice.queue.Finish();
}
public RenderTarget(int Width, int Height, GraphicsDevice device)
{
image = new Bitmap(Width, Height);
backBuffer = new byte[4 * Width * Height];
GraphicsDevice = device;
//Fill the screen with black
FillScreen(Color.Black);
UpdateImage();
Console.WriteLine(image.GetPixel(0, 0).A);
}
}
I have checked to make absolutely sure that the backBuffer is correct. (The values I expected were 255, 0, 0, 0 for the first four elements of the backBuffer).
Okay, I figured out what was going wrong. I had the format in the backBuffer wrong. I was expecting it to be ARGB when it should be ordered RGBA. So, it was a problem with my code in 'fillText'.
I am working on a game for learning purposes, I want to make it only with the .NET-Framework and a Windows Forms project in C#.
I want to get the 'screen' (Something that can be displayed on the window) as an int[]. Modify the array and reapply the altered array to the 'screen' in a buffered manner (So that it doesn't flicker).
I am currently using a Panel, which I draw a Bitmap on with Graphics. The Bitmap is converted to an int[] which I then can modify and reapply to the Bitmap and redraw. It works, but is very slow, especially because I have to scale up the image every frame because my game is only 300x160 and the screen 900x500.
Build up:
// Renders 1 frame
private void Render()
{
// Buffer setup
_bufferedContext = BufferedGraphicsManager.Current;
_buffer = _bufferedContext.Allocate(panel_canvas.CreateGraphics(), new Rectangle(0, 0, _scaledWidth, _scaledHeight));
_screen.clear();
// Get position of player on map
_xScroll = _player._xMap - _screen._width / 2;
_yScroll = _player._yMap - _screen._height / 2;
// Indirectly modifies the int[] '_pixels'
_level.render(_xScroll, _yScroll, _screen);
_player.render(_screen);
// Converts the int[] into a Bitmap (unsafe method is faster)
unsafe
{
fixed (int* intPtr = &_screen._pixels[0])
{
_screenImage = new Bitmap(_trueWidth, _trueHeight, _trueWidth * 4, PixelFormat.Format32bppRgb, new IntPtr(intPtr));
}
}
// Draw generated image on buffer
Graphics g = _buffer.Graphics;
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;
g.DrawImage(_screenImage, new Rectangle(0, 0, 900, 506));
// Update panel buffered
_buffer.Render();
}
Is there a faster way without external libraries to make this work?
I'm not to sure about the unsafe code , But I do know about the buffered graphics manager. I think you should create a class for it instead of creating a new one every time.As well as having all of your sprites widths and heights be determined at the load instead of scaling them. That sped up my small game engine a good bit.
class Spritebatch
{
private Graphics Gfx;
private BufferedGraphics bfgfx;
private BufferedGraphicsContext cntxt = BufferedGraphicsManager.Current;
public Spritebatch(Size clientsize, Graphics gfx)
{
cntxt.MaximumBuffer = new Size(clientsize.Width + 1, clientsize.Height + 1);
bfgfx = cntxt.Allocate(gfx, new Rectangle(Point.Empty, clientsize));
Gfx = gfx;
}
public void Begin()
{
bfgfx.Graphics.Clear(Color.Black);
}
public void Draw(Sprite s)
{
bfgfx.Graphics.DrawImageUnscaled(s.Texture, new Rectangle(s.toRec.X - s.rotationOffset.Width,s.toRec.Y - s.rotationOffset.Height,s.toRec.Width,s.toRec.Height));
}
public void drawImage(Bitmap b, Rectangle rec)
{
bfgfx.Graphics.DrawImageUnscaled(b, rec);
}
public void drawImageClipped(Bitmap b, Rectangle rec)
{
bfgfx.Graphics.DrawImageUnscaledAndClipped(b, rec);
}
public void drawRectangle(Pen p, Rectangle rec)
{
bfgfx.Graphics.DrawRectangle(p, rec);
}
public void End()
{
bfgfx.Render(Gfx);
}
}
This is a example of what I used. It's set up to mimic the Spritebatch in Xna. Drawing the images Unscaled will really increase the speed of it.Also creating one instance of the buffered graphics and Context will be faster then creating a new one every time you have to render. So I would advise you to change the line g.DrawImage(_screenImage, new Rectangle(0, 0, 900, 506)); to DrawImageUnscaled(_screenImage, new Rectangle(0, 0, 900, 506));
Edited : Example of how to scale code on sprite load
public Sprite(Bitmap texture, float x, float y, int width, int height)
{
//texture is the image you originally start with.
Bitmap b = new Bitmap(width, height);
// Create a bitmap with the desired width and height
using (Graphics g = Graphics.FromImage(b))
{
g.DrawImage(texture, 0, 0, width, height);
}
// get the graphics from the new image and draw the old image to it
//scaling it to the proper width and height
Texture = b;
//set Texture which is the final picture to the sprite.
//Uppercase Texture is different from lowercase
Scaling of the image is expensive enough, even when is done without any interpolation. To speed up the things, you should minimize memory allocations: when you create brand new Bitmap every frame, it leads to object creation and pixmap buffer allocation. This fact negates all the benefits you get from BufferedGraphics. I advise you to do the following:
Create the Bitmap instance of required size (equal to screen size) only once, outside of Render method.
Use direct access to bitmap data through LockBits method, and try to implement the scaling be hand using nearest pixel.
Of course, using some sort of hardware acceleration for scaling operation is the most preferred option (for example, in opengl all images are usually drawn using textured rectangles, and rendering such rectangles implicitly involves the process of "scaling" when texture sampling is performed).
I'm wondering why do you call this "very slow", because I did some tests and the performance doesn't seem bad. Also have you measured the performance of your rendering code into int[] '_pixels' (unfortunately you haven't provided that code) separately from the bitmap operations, because it might be the slow part.
About your concrete question. As others mentioned, using preallocated buffered graphics and bitmap objects would speed up it a bit.
But do you really need that int[] buffer? BufferedGraphics is already backed internally with a bitmap, so what really happens is:
(1) You fill the int[] buffer
(2) int[] buffer is copied to the new/preallocated Bitmap
(3) Bitmap from step 2 is copied (applying scale) to the BufferedGraphics internal bitmap (via DrawImage)
(4) BufferedGraphics internal bitmap is copied to the screen (via Render)
As you can see, there are a lot of copy operations. The intended usage of BufferedGraphics is:
(1) You fill the BufferedGraphics internal bitmap via drawing methods of the BufferedGraphics.Graphics property. If setup, the Graphics will do the scaling (as well other transformations) for you.
(2) BufferedGraphics internal bitmap is copied to the screen (via Render)
I don't know what your drawing code is doing, but if you can afford it, this definitely should provide the best performance.
Here is my quick and dirty test in case you are interested in:
using System;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Threading;
using System.Windows.Forms;
namespace Test
{
enum RenderMode { NewBitmap, PreallocatedBitmap, Graphics }
class Screen
{
Control canvas;
public Rectangle area;
int[,] pixels;
BitmapData info;
Bitmap bitmap;
BufferedGraphics buffer;
float scaleX, scaleY;
public RenderMode mode = RenderMode.NewBitmap;
public Screen(Control canvas, Size size)
{
this.canvas = canvas;
var bounds = canvas.DisplayRectangle;
scaleX = (float)bounds.Width / size.Width;
scaleY = (float)bounds.Height / size.Height;
area.Size = size;
info = new BitmapData { Width = size.Width, Height = size.Height, PixelFormat = PixelFormat.Format32bppRgb, Stride = size.Width * 4 };
pixels = new int[size.Height, size.Width];
bitmap = new Bitmap(size.Width, size.Height, info.PixelFormat);
buffer = BufferedGraphicsManager.Current.Allocate(canvas.CreateGraphics(), bounds);
buffer.Graphics.InterpolationMode = InterpolationMode.NearestNeighbor;
ApplyMode();
}
public void ApplyMode()
{
buffer.Graphics.ResetTransform();
if (mode == RenderMode.Graphics)
buffer.Graphics.ScaleTransform(scaleX, scaleY);
}
public void FillRectangle(Color color, Rectangle rect)
{
if (mode == RenderMode.Graphics)
{
using (var brush = new SolidBrush(color))
buffer.Graphics.FillRectangle(brush, rect);
}
else
{
rect.Intersect(area);
if (rect.IsEmpty) return;
int colorData = color.ToArgb();
var pixels = this.pixels;
for (int y = rect.Y; y < rect.Bottom; y++)
for (int x = rect.X; x < rect.Right; x++)
pixels[y, x] = colorData;
}
}
public unsafe void Render()
{
if (mode == RenderMode.NewBitmap)
{
var bounds = canvas.DisplayRectangle;
using (var buffer = BufferedGraphicsManager.Current.Allocate(canvas.CreateGraphics(), bounds))
{
Bitmap bitmap;
fixed (int* pixels = &this.pixels[0, 0])
bitmap = new Bitmap(info.Width, info.Height, info.Stride, info.PixelFormat, new IntPtr(pixels));
buffer.Graphics.InterpolationMode = InterpolationMode.NearestNeighbor;
buffer.Graphics.DrawImage(bitmap, bounds);
buffer.Render();
}
}
else
{
if (mode == RenderMode.PreallocatedBitmap)
{
fixed (int* pixels = &this.pixels[0, 0])
{
info.Scan0 = new IntPtr(pixels); info.Reserved = 0;
bitmap.LockBits(area, ImageLockMode.WriteOnly | ImageLockMode.UserInputBuffer, info.PixelFormat, info);
bitmap.UnlockBits(info);
}
buffer.Graphics.DrawImage(bitmap, canvas.DisplayRectangle);
}
buffer.Render();
}
}
}
class Game
{
[STAThread]
public static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
var game = new Game();
game.Run();
}
Form form;
Control canvas;
Screen screen;
Level level;
Player player;
private Game()
{
form = new Form();
canvas = new Control { Parent = form, Bounds = new Rectangle(0, 0, 900, 506) };
form.ClientSize = canvas.Size;
screen = new Screen(canvas, new Size(300, 160));
level = new Level { game = this };
player = new Player { game = this };
}
private void Run()
{
bool toggleModeRequest = false;
canvas.MouseClick += (sender, e) => toggleModeRequest = true;
var worker = new Thread(() =>
{
int frameCount = 0;
Stopwatch drawT = new Stopwatch(), applyT = new Stopwatch(), advanceT = Stopwatch.StartNew(), renderT = Stopwatch.StartNew(), infoT = Stopwatch.StartNew();
while (true)
{
if (advanceT.ElapsedMilliseconds >= 3)
{
level.Advance(); player.Advance();
advanceT.Restart();
}
if (renderT.ElapsedMilliseconds >= 8)
{
frameCount++;
drawT.Start(); level.Render(); player.Render(); drawT.Stop();
applyT.Start(); screen.Render(); applyT.Stop();
renderT.Restart();
}
if (infoT.ElapsedMilliseconds >= 1000)
{
double drawS = drawT.ElapsedMilliseconds / 1000.0, applyS = applyT.ElapsedMilliseconds / 1000.0, totalS = drawS + applyS;
var info = string.Format("Render using {0} - Frames:{1:n0} FPS:{2:n0} Draw:{3:p2} Apply:{4:p2}",
screen.mode, frameCount, frameCount / totalS, drawS / totalS, applyS / totalS);
form.BeginInvoke(new Action(() => form.Text = info));
infoT.Restart();
}
if (toggleModeRequest)
{
toggleModeRequest = false;
screen.mode = (RenderMode)(((int)screen.mode + 1) % 3);
screen.ApplyMode();
frameCount = 0; drawT.Reset(); applyT.Reset();
}
}
});
worker.IsBackground = true;
worker.Start();
Application.Run(form);
}
class Level
{
public Game game;
public int pos = 0; bool right = true;
public void Advance() { Game.Advance(ref pos, ref right, 0, game.screen.area.Right - 1); }
public void Render()
{
game.screen.FillRectangle(Color.SaddleBrown, new Rectangle(0, 0, pos, game.screen.area.Height));
game.screen.FillRectangle(Color.DarkGreen, new Rectangle(pos, 0, game.screen.area.Right, game.screen.area.Height));
}
}
class Player
{
public Game game;
public int x = 0, y = 0;
public bool right = true, down = true;
public void Advance()
{
Game.Advance(ref x, ref right, game.level.pos, game.screen.area.Right - 5, 2);
Game.Advance(ref y, ref down, 0, game.screen.area.Bottom - 1, 2);
}
public void Render() { game.screen.FillRectangle(Color.Yellow, new Rectangle(x, y, 4, 4)); }
}
static void Advance(ref int pos, ref bool forward, int minPos, int maxPos, int delta = 1)
{
if (forward) { pos += delta; if (pos < minPos) pos = minPos; else if (pos > maxPos) { pos = maxPos; forward = false; } }
else { pos -= delta; if (pos > maxPos) pos = maxPos; else if (pos < minPos) { pos = minPos; forward = true; } }
}
}
}
I was looking for the fastest way to convert a Bitmap to 8bpp.
I found 2 ways:
1.
public static System.Drawing.Image ConvertTo8bpp(Bitmap oldbmp)
{
using (var ms = new MemoryStream())
{
oldbmp.Save(ms, ImageFormat.Gif);
ms.Position = 0;
return System.Drawing.Image.FromStream(ms);
}
}
2. http://www.wischik.com/lu/programmer/1bpp.html
But:
1. Results in a very low quality result (bad pallet)
and 2 gives me a Bitmap with negative stride, when I try to lockbits and copy the data to a byte array I get an exception: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadWrite, bmp.PixelFormat);
this.stride = bmpData.Stride;
this.bytesPerPixel = GetBytesPerPixel(bmp.PixelFormat);
int length = bmpData.Stride * bmp.Height;
if (this.stride < 0)
this.data = new byte[-length];
else
this.data = new byte[length];
Marshal.Copy(bmpData.Scan0, data, 0, length);
//Unlock the bitmap
bmp.UnlockBits(bmpData);
How can I make 2 gives a positive stride? Or how can I copy data using lockbits of a negative stride??
Copy 1 row at a time, calculating the starting pointer for a row as ((byte*)scan0 + (y * stride)). The code will be identical for either positive or negative stride.
The problem here is that Scan0 points to the beginning of the first scan line, not the beginning of the first byte of data. In a bottom-up bitmap, the first scan line is Stride bytes from the end of the bitmap data.
When you call Marshal.Copy to copy the data from Scan0, it tries to copy (Height*Stride) bytes, starting from position ((Height-1)*Stride). Clearly, that's going to run off into the weeds.
If you just want to copy the bitmap data, you have to calculate the starting address with Scan0 - (Height-1)*Stride. That will start you at the beginning of the bitmap data. You can pass that computed address to Marshal.Copy.
If you want to copy the scan lines in order (i.e. top, next, next, ... bottom), then you have to copy a line at a time: copy Stride bytes from Scan0, then add Stride (which is negative), copy that line, etc. Rick Brewster had the right answer there: https://stackoverflow.com/a/10360753/56778
I don't know why there is something strange about the Bitmap created by the FromHbitmap method, but I do know that you can fix it by using Bitmap bmpClone = (Bitmap)bmp.Clone(); and doing the LockBits on bmpClone.
Also, I found that if you use bmp.Clone(), you cannot Dispose() of bmp until you have finished with the clone.
This also works and let's you dispose of the negative stride image sooner rather than later:
Bitmap bmp = null;
using (Bitmap bmpT = CopyToBpp(bmpO, 1))
{
bmp = new Bitmap(bmpT);
}
From the C# documentation on BitmapData: The stride is the width of a single row of pixels (a scan line), rounded up to a four-byte boundary. If the stride is positive, the bitmap is top-down. If the stride is negative, the bitmap is bottom-up
I managed to resolve this issue without creating entirely new Bitmap object, by using LockBits() with ImageLockMode.UserInputBuffer.
Attached the code I came up with, feel free to use.
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
public static class BitmapExtensions
{
public static ProperBitmapData LockBitsProper(this Bitmap bitmap, ImageLockMode flags)
{
Rectangle bitmapBounds = new Rectangle(0, 0, bitmap.Width, bitmap.Height);
return bitmap.LockBitsProper(bitmapBounds, flags, bitmap.PixelFormat);
}
public static ProperBitmapData LockBitsProper(this Bitmap bitmap, Rectangle rect, ImageLockMode flags, PixelFormat format)
{
BitmapData bmpData = bitmap.LockBits(rect, flags, format);
int byteCount;
try
{
byteCount = Math.Abs(bmpData.Stride) * bmpData.Height;
if (bmpData.Stride > 0) return new ProperBitmapData(bitmap, bmpData, byteCount, IntPtr.Zero);
}
catch
{
bitmap.UnlockBits(bmpData);
throw;
}
// in case Stride is negative
bitmap.UnlockBits(bmpData);
// When Stride is negative, the LockBits locks the wrong memory area which results in AccessViolationException even when reading the right place
// starting with Scan0 + (Height - 1) * Stride (also not properly documented).
// This is a workaround to it using a user allocated area overload.
// For some reason, in Windows Vista (SP0) Stride is (almost?) always negative, while in >=Windows 7 it is positive more often.
// Some useful documentation: https://learn.microsoft.com/en-us/windows/win32/api/gdiplusheaders/nf-gdiplusheaders-bitmap-lockbits
IntPtr userAllocatedArea = Marshal.AllocHGlobal(byteCount);
try
{
// Actually when Stride is negative, Scan0 have to point to where the last row will be written.
// This is not properly documented anywhere, and discovered just by trial and error.
bmpData.Scan0 = (IntPtr)((long)userAllocatedArea - (bmpData.Height - 1) * bmpData.Stride);
bmpData = bitmap.LockBits(rect, ImageLockMode.UserInputBuffer | flags, format, bmpData);
try
{
return new ProperBitmapData(bitmap, bmpData, byteCount, userAllocatedArea);
}
catch
{
bitmap.UnlockBits(bmpData);
throw;
}
}
catch
{
Marshal.FreeHGlobal(userAllocatedArea);
throw;
}
}
}
public class ProperBitmapData : IDisposable
{
private Bitmap _bitmap;
private BitmapData _bitmapData;
private int _byteCount;
private IntPtr _userAllocatedBuffer;
public int Width => _bitmapData.Width;
public int Height => _bitmapData.Height;
public int Stride => _bitmapData.Stride;
public PixelFormat PixelFormat => _bitmapData.PixelFormat;
public IntPtr Scan0 => (_userAllocatedBuffer != IntPtr.Zero) ? _userAllocatedBuffer : _bitmapData.Scan0;
public int Reserved => _bitmapData.Reserved;
public int ByteCount => _byteCount;
public ProperBitmapData(Bitmap bitmap, BitmapData bitmapData, int byteCount, IntPtr userAllocatedBuffer)
{
_bitmap = bitmap;
_bitmapData = bitmapData;
_byteCount = byteCount;
_userAllocatedBuffer = userAllocatedBuffer;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
_bitmap?.UnlockBits(_bitmapData);
_bitmap = null;
_bitmapData = null;
if (_userAllocatedBuffer != IntPtr.Zero)
{
Marshal.FreeHGlobal(_userAllocatedBuffer);
_userAllocatedBuffer = IntPtr.Zero;
}
}
~ProperBitmapData()
{
Dispose(false);
}
}
Example of usage:
using (ProperBitmapData bmpData = bitmap.LockBitsProper(ImageLockMode.ReadOnly))
{
// Beware that bmpData.Scan0 here always points to the start of the allocated memory block.
this.data = new byte[bmpData.ByteCount];
Marshal.Copy(bmpData.Scan0, data, 0, bmpData.ByteCount);
}
I'm guessing the exception you're getting is due to
this.data = new byte[-length];
And then trying to copy data into a byte array of negative size (I don't see how that even compiles really...).
hi i send some c# code this is get from imagealg.dll file the class name is diff code is given below how to get description for this code.
public sealed class Diff
{
// Fields
private int diff;
private Bitmap overlayImage;
private Bitmap overlayImage1;
// Methods
public Diff()
{
}
public Diff(Bitmap overlayImage, Bitmap overlayImage1)
{
this.overlayImage = overlayImage;
this.overlayImage1 = overlayImage1;
}
public int Apply(Bitmap srcImg, Bitmap dstImg)
{
int width = srcImg.Width;
int height = srcImg.Height;
int num3 = dstImg.Width;
int num4 = dstImg.Height;
PixelFormat format = (srcImg.PixelFormat == PixelFormat.Format8bppIndexed) ? PixelFormat.Format8bppIndexed : PixelFormat.Format24bppRgb;
BitmapData data = srcImg.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, format);
BitmapData ovrData = dstImg.LockBits(new Rectangle(0, 0, num3, num4), ImageLockMode.ReadOnly, format);
this.ProcessFilter(data, ovrData, format);
dstImg.UnlockBits(ovrData);
srcImg.UnlockBits(data);
return this.diff;
}
public void ApplyInPlace(Bitmap img)
{
int width = img.Width;
int height = img.Height;
if (((img.PixelFormat != this.overlayImage.PixelFormat) || (width != this.overlayImage.Width)) || (height != this.overlayImage.Height))
{
throw new ArgumentException();
}
if ((img.PixelFormat != PixelFormat.Format8bppIndexed) && (img.PixelFormat != PixelFormat.Format24bppRgb))
{
throw new ArgumentException();
}
BitmapData bitmapdata = img.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
img.UnlockBits(bitmapdata);
}
public int difference()
{
return this.diff;
}
private unsafe void ProcessFilter(BitmapData data, BitmapData ovrData, PixelFormat fmt)
{
int width = data.Width;
int height = data.Height;
int num3 = (fmt == PixelFormat.Format8bppIndexed) ? 1 : 3;
int num4 = width * num3;
int num5 = data.Stride - num4;
byte* numPtr = (byte*) data.Scan0.ToPointer();
byte* numPtr2 = (byte*) ovrData.Scan0.ToPointer();
this.diff = 0;
for (int i = 0; i < height; i++)
{
int num8 = 0;
while (num8 < num4)
{
int num6 = numPtr[0] - numPtr2[0];
string str = Convert.ToString(numPtr[0]);
string str2 = Convert.ToString(numPtr2[0]);
if (num6 != 0)
{
this.diff++;
}
num8++;
numPtr++;
numPtr2++;
}
numPtr += num5;
numPtr2 += num5;
}
}
// Properties
public Bitmap OverlayImage
{
get
{
return this.overlayImage;
}
set
{
this.overlayImage = value;
}
}
}
It sounds like you've recently received and/or been asked to use a library containing code that you don't quite understand. Unfortunately, if the source isn't properly documented, you only have two choices:
Go back to whomever or wherever you got the code from and ask them for proper documentation. Every piece of source code that you receive should come with documentation.
Study the code carefully and try to figure out what it's doing and how you are supposed to use it. This is somewhat closer to "reverse-engineering" the library (except that you have the source), and it's usually reserved as a last-ditch effort.
In fact, if any of us were to try and answer this question, that's all we could do is read and interpret the code you've posted. Is there something specific that you don't understand and are seeking clarification about?
And finally, remember this as a lesson to yourself. Whenever you write code, make sure that you take the time to document it properly so you don't put someone else who tries to use your code in the same situation that you're in now.
You'll be happy to know that the code doesn't do anything particularly significant. The Apply method sets up and calls ProcessFilter, which does a comparison of the two images and returns a count of the number of pixels that differ between the two.
The ApplyInPlace method does nothing.
And I feel sorry for the poor programmer who inherits this code and has to maintain it, what with all those useful variable names like num4 and num6.
It is my fondest wish that the programmer who brought this abomination into the world has been permanently banned from ever touching another keyboard.