Fast work with Bitmaps in C# - c#

I need to access each pixel of a Bitmap, work with them, then save them to a Bitmap.
Using Bitmap.GetPixel() and Bitmap.SetPixel(), my program runs slowly.
How can I quickly convert Bitmap to byte[] and back?
I need a byte[] with length = (4 * width * height), containing RGBA data of each pixel.

You can do it a couple of different ways. You can use unsafe to get direct access to the data, or you can use marshaling to copy the data back and forth. The unsafe code is faster, but marshaling doesn't require unsafe code. Here's a performance comparison I did a while back.
Here's a complete sample using lockbits:
/*Note unsafe keyword*/
public unsafe Image ThresholdUA(float thresh)
{
Bitmap b = new Bitmap(_image);//note this has several overloads, including a path to an image
BitmapData bData = b.LockBits(new Rectangle(0, 0, _image.Width, _image.Height), ImageLockMode.ReadWrite, b.PixelFormat);
byte bitsPerPixel = GetBitsPerPixel(bData.PixelFormat);
/*This time we convert the IntPtr to a ptr*/
byte* scan0 = (byte*)bData.Scan0.ToPointer();
for (int i = 0; i < bData.Height; ++i)
{
for (int j = 0; j < bData.Width; ++j)
{
byte* data = scan0 + i * bData.Stride + j * bitsPerPixel / 8;
//data is a pointer to the first byte of the 3-byte color data
//data[0] = blueComponent;
//data[1] = greenComponent;
//data[2] = redComponent;
}
}
b.UnlockBits(bData);
return b;
}
Here's the same thing, but with marshaling:
/*No unsafe keyword!*/
public Image ThresholdMA(float thresh)
{
Bitmap b = new Bitmap(_image);
BitmapData bData = b.LockBits(new Rectangle(0, 0, _image.Width, _image.Height), ImageLockMode.ReadWrite, b.PixelFormat);
/* GetBitsPerPixel just does a switch on the PixelFormat and returns the number */
byte bitsPerPixel = GetBitsPerPixel(bData.PixelFormat);
/*the size of the image in bytes */
int size = bData.Stride * bData.Height;
/*Allocate buffer for image*/
byte[] data = new byte[size];
/*This overload copies data of /size/ into /data/ from location specified (/Scan0/)*/
System.Runtime.InteropServices.Marshal.Copy(bData.Scan0, data, 0, size);
for (int i = 0; i < size; i += bitsPerPixel / 8 )
{
double magnitude = 1/3d*(data[i] +data[i + 1] +data[i + 2]);
//data[i] is the first of 3 bytes of color
}
/* This override copies the data back into the location specified */
System.Runtime.InteropServices.Marshal.Copy(data, 0, bData.Scan0, data.Length);
b.UnlockBits(bData);
return b;
}

You can use Bitmap.LockBits method. Also if you want to use parallel task execution, you can use the Parallel class in System.Threading.Tasks namespace. Following links have some samples and explanations.
http://csharpexamples.com/fast-image-processing-c/
http://msdn.microsoft.com/en-us/library/dd460713%28v=vs.110%29.aspx
http://msdn.microsoft.com/tr-tr/library/system.drawing.imaging.bitmapdata%28v=vs.110%29.aspx

If you're on C# 8.0 I'll suggest to use the new Span<T> for higher efficiency.
Here's a rough implementation
public unsafe class FastBitmap : IDisposable
{
private Bitmap _bmp;
private ImageLockMode _lockmode;
private int _pixelLength;
private Rectangle _rect;
private BitmapData _data;
private byte* _bufferPtr;
public int Width { get => _bmp.Width; }
public int Height { get => _bmp.Height; }
public PixelFormat PixelFormat { get => _bmp.PixelFormat; }
public FastBitmap(Bitmap bmp, ImageLockMode lockMode)
{
_bmp = bmp;
_lockmode = lockMode;
_pixelLength = Image.GetPixelFormatSize(bmp.PixelFormat) / 8;
_rect = new Rectangle(0, 0, Width, Height);
_data = bmp.LockBits(_rect, lockMode, PixelFormat);
_bufferPtr = (byte*)_data.Scan0.ToPointer();
}
public Span<byte> this[int x, int y]
{
get
{
var pixel = _bufferPtr + y * _data.Stride + x * _pixelLength;
return new Span<byte>(pixel, _pixelLength);
}
set
{
value.CopyTo(this[x, y]);
}
}
public void Dispose()
{
_bmp.UnlockBits(_data);
}
}

You want LockBits. You can then extract the bytes you want from the BitmapData object it gives you.

There is another way that is way faster and much more convenient. If you have a look at the Bitmap constructors you will find one that takes and IntPtr as the last parameter. That IntPtr is for holding pixel data. So how do you use it?
Dim imageWidth As Integer = 1920
Dim imageHeight As Integer = 1080
Dim fmt As PixelFormat = PixelFormat.Format32bppRgb
Dim pixelFormatSize As Integer = Image.GetPixelFormatSize(fmt)
Dim stride As Integer = imageWidth * pixelFormatSize
Dim padding = 32 - (stride Mod 32)
If padding < 32 Then stride += padding
Dim pixels((stride \ 32) * imageHeight) As Integer
Dim handle As GCHandle = GCHandle.Alloc(pixels, GCHandleType.Pinned)
Dim addr As IntPtr = Marshal.UnsafeAddrOfPinnedArrayElement(pixels, 0)
Dim bitmap As New Bitmap(imageWidth, imageHeight, stride \ 8, fmt, addr)
What you have now is a simple Integer array and a Bitmap referencing the same memory. Any changes you make to the Integer array will be directly affecting the Bitmap. Let us try this with a simple brightness transform.
Public Sub Brightness(ByRef pixels() As Integer, ByVal scale As Single)
Dim r, g, b As Integer
Dim mult As Integer = CInt(1024.0f * scale)
Dim pixel As Integer
For i As Integer = 0 To pixels.Length - 1
pixel = pixels(i)
r = pixel And 255
g = (pixel >> 8) And 255
b = (pixel >> 16) And 255
'brightness calculation
'shift right by 10 <=> divide by 1024
r = (r * mult) >> 10
g = (g * mult) >> 10
b = (b * mult) >> 10
'clamp to between 0 and 255
If r < 0 Then r = 0
If g < 0 Then g = 0
If b < 0 Then b = 0
r = (r And 255)
g = (g And 255)
b = (b And 255)
pixels(i) = r Or (g << 8) Or (b << 16) Or &HFF000000
Next
End Sub
You may notice that I have used a little trick to avoid doing floating point math within the loop. This improves performance quite a bit.
And when you are done you need to clean up a little of course...
addr = IntPtr.Zero
If handle.IsAllocated Then
handle.Free()
handle = Nothing
End If
bitmap.Dispose()
bitmap = Nothing
pixels = Nothing
I have ignored the alpha component here but you are free to use that as well. I have thrown together a lot of bitmap editing tools this way. It is much faster and more reliable than Bitmap.LockBits() and best of all, it requires zero memory copying to start editing your bitmap.

Building on #notJim answer (and with help from https://web.archive.org/web/20141229164101/http://bobpowell.net/lockingbits.aspx), I developed the following that makes my life a lot easier in that I end up with an array of arrays that allows me to jump to a pixel by its x and y coordinates. Of course, the x coordinate needs to be corrected for by the number of bytes per pixel, but that is an easy extension.
Dim bitmapData As Imaging.BitmapData = myBitmap.LockBits(New Rectangle(0, 0, myBitmap.Width, myBitmap.Height), Imaging.ImageLockMode.ReadOnly, myBitmap.PixelFormat)
Dim size As Integer = Math.Abs(bitmapData.Stride) * bitmapData.Height
Dim data(size - 1) As Byte
Marshal.Copy(bitmapData.Scan0, data, 0, size)
Dim pixelArray(myBitmap.Height)() As Byte
'we have to load all the opacity pixels into an array for later scanning by column
'the data comes in rows
For y = myBitmap.Height - 1 To 0 Step -1
Dim rowArray(bitmapData.Stride) As Byte
Array.Copy(data, y * bitmapData.Stride, rowArray, 0, bitmapData.Stride)
'For x = myBitmap.Width - 1 To 0 Step -1
' Dim i = (y * bitmapData.Stride) + (x * 4)
' Dim B = data(i)
' Dim G = data(i + 1)
' Dim R = data(i + 2)
' Dim A = data(i + 3)
'Next
pixelArray(y) = rowArray
Next

Try this C# solution.
Create a winforms app for testing.
Add a Button and a PictureBox, and a click event and a form closing event.
Use the following code for your form:
public partial class Form1 : Form
{
uint[] _Pixels { get; set; }
Bitmap _Bitmap { get; set; }
GCHandle _Handle { get; set; }
IntPtr _Addr { get; set; }
public Form1()
{
InitializeComponent();
int imageWidth = 100; //1920;
int imageHeight = 100; // 1080;
PixelFormat fmt = PixelFormat.Format32bppRgb;
int pixelFormatSize = Image.GetPixelFormatSize(fmt);
int stride = imageWidth * pixelFormatSize;
int padding = 32 - (stride % 32);
if (padding < 32)
{
stride += padding;
}
_Pixels = new uint[(stride / 32) * imageHeight + 1];
_Handle = GCHandle.Alloc(_Pixels, GCHandleType.Pinned);
_Addr = Marshal.UnsafeAddrOfPinnedArrayElement(_Pixels, 0);
_Bitmap = new Bitmap(imageWidth, imageHeight, stride / 8, fmt, _Addr);
pictureBox1.Image = _Bitmap;
}
private void button1_Click(object sender, EventArgs e)
{
for (int i = 0; i < _Pixels.Length; i++)
{
_Pixels[i] = ((uint)(255 | (255 << 8) | (255 << 16) | 0xff000000));
}
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
_Addr = IntPtr.Zero;
if (_Handle.IsAllocated)
{
_Handle.Free();
}
_Bitmap.Dispose();
_Bitmap = null;
_Pixels = null;
}
}
Now, any edits you make to the array will automatically update the Bitmap.
You will need to call the refresh method on the picturebox to see these changes.

Related

C# Creating PixelFormat.Format32bppArgb skewing image

I am trying to combine 3 grayscale bitmaps into one color bitmap. All three grayscale images are the same size (this is based off of data from the Hubble). My logic is:
Load "blue" image and convert to PixelFormat.Format24bppRgb. Based off of that create a new byte array that is 4 times as large as the blue data array length/3 (so it will be one byte for blue, one byte for green, one byte for red, one byte for alpha per pixel since my system is little endian). Populate the "blue" bytes of the array from the "blue" bytes of the blue image (and in this first loop set the alpha byte to 255). I then load the green and red bitmaps, convert them to PixelFormat.Format24bppRgb, and pull the g/r value and add it to the correct place in the data array. The final data array then has the bgra bytes set correctly from what I can tell.
When I have the data array populated, I have used it to:
Create a PixelFormats.Bgra32 BitmapSource then convert that to a Bitmap.
Create a PixelFormat.Format32bppArgb Bitmap using the Bitmap constructor (width, height, stride, PixelForma, IntPtr)
Create a PixelFormat.Format32bppArgb Bitmap using pointers
All three ways of creating a return bitmap result in the image being "skewed" (sorry, I don't know of a better word).
The actual output (of all three ways of generating the final bitmap) is: Actual output
The desired output is something like (this was done in photoshop so it is slightly different): Desired output
The three file names (_blueFileName, _greenFileName, _redFileName) are set in the constructor and I check to make sure the files exist before creating the class. I can post that code if anyone wants it.
Can anyone tell me what I am doing wrong? I am guessing that is is due to the stride or something like that?
Note: I can't post the links to the images I am using as input as I don't have 10 reputation points. Maybe I could send the links via email or something if someone wants them as well.
Here is my code (with some stuff commented out, the comments describe what happens if each commented out block is used instead):
public Bitmap Merge()
{
// Load original "blue" bitmap.
Bitmap tblueBitmap = (Bitmap)Image.FromFile(_blueFileName);
int width = tblueBitmap.Width;
int height = tblueBitmap.Height;
// Convert to 24 bpp rgb (which is bgr on little endian machines)
Bitmap blueBitmap = new Bitmap(width, height, PixelFormat.Format24bppRgb);
using (Graphics gr = Graphics.FromImage(blueBitmap))
{
gr.DrawImage(tblueBitmap, 0, 0, width, height);
}
tblueBitmap.Dispose();
// Lock and copy to byte array.
BitmapData blueData = blueBitmap.LockBits(new Rectangle(0, 0, blueBitmap.Width, blueBitmap.Height), ImageLockMode.ReadOnly,
blueBitmap.PixelFormat);
int numbBytes = blueData.Stride*blueBitmap.Height;
byte[] blueBytes = new byte[numbBytes];
Marshal.Copy(blueData.Scan0, blueBytes, 0, numbBytes);
blueBitmap.UnlockBits(blueData);
blueData = null;
blueBitmap.Dispose();
int mult = 4;
byte[] data = new byte[(numbBytes/3)*mult];
int count = 0;
// Copy every third byte starting at 0 to the final data array (data).
for (int i = 0; i < data.Length / mult; i++)
{
// Check for overflow
if (blueBytes.Length <= count*3 + 2)
{
continue;
}
// First pass, set Alpha channel.
data[i * mult + 3] = 255;
// Set blue byte.
data[i*mult] = blueBytes[count*3];
count++;
}
// Cleanup.
blueBytes = null;
int generation = GC.GetGeneration(this);
GC.Collect(generation);
Bitmap tgreenBitmap = (Bitmap)Image.FromFile(_greenFileName);
Bitmap greenBitmap = new Bitmap(width, height, PixelFormat.Format24bppRgb);
using (Graphics gr = Graphics.FromImage(greenBitmap))
{
gr.DrawImage(tgreenBitmap, 0, 0, width, height);
}
tgreenBitmap.Dispose();
BitmapData greenData = greenBitmap.LockBits(new Rectangle(0, 0, greenBitmap.Width, greenBitmap.Height), ImageLockMode.ReadOnly,
greenBitmap.PixelFormat);
numbBytes = greenData.Stride * greenBitmap.Height;
byte[] greenBytes = new byte[numbBytes];
Marshal.Copy(greenData.Scan0, greenBytes, 0, numbBytes);
greenBitmap.UnlockBits(greenData);
greenData = null;
greenBitmap.Dispose();
count = 0;
for (int i = 0; i < data.Length / mult; i++)
{
if (greenBytes.Length <= count * 3 + 1)
{
continue;
}
// Set green byte
data[i * mult + 1] = greenBytes[count * 3 + 1];
count++;
}
greenBytes = null;
generation = GC.GetGeneration(this);
GC.Collect(generation);
Bitmap tredBitmap = (Bitmap)Image.FromFile(_redFileName);
Bitmap redBitmap = new Bitmap(width, height, PixelFormat.Format24bppRgb);
using (Graphics gr = Graphics.FromImage(redBitmap))
{
gr.DrawImage(tredBitmap, 0, 0, width, height);
}
tredBitmap.Dispose();
BitmapData redData = redBitmap.LockBits(new Rectangle(0, 0, redBitmap.Width, redBitmap.Height), ImageLockMode.ReadOnly,
redBitmap.PixelFormat);
numbBytes = redData.Stride * redBitmap.Height;
byte[] redBytes = new byte[numbBytes];
Marshal.Copy(redData.Scan0, redBytes, 0, numbBytes);
redBitmap.UnlockBits(redData);
redData = null;
redBitmap.Dispose();
count = 0;
for (int i = 0; i < data.Length / mult; i++)
{
if (redBytes.Length <= count * 3+2)
{
count++;
continue;
}
// set red byte
data[i * mult + 2] = redBytes[count * 3 + 2];
count++;
}
redBytes = null;
generation = GC.GetGeneration(this);
GC.Collect(generation);
int stride = (width*32 + 7)/8;
var bi = BitmapSource.Create(width, height, 96, 96, PixelFormats.Bgra32, null, data, stride);
// uncomment out below to see what a bitmap source to bitmap does. So far, it is exactly the same as
// the uncommented out lines below.
// ---------------------------------------------------------------------------------------------------
//return BitmapImage2Bitmap(bi);
unsafe
{
fixed (byte* p = data)
{
IntPtr ptr = (IntPtr)p;
// Trying the commented out lines returns the same bitmap as the uncommented out lines.
// ------------------------------------------------------------------------------------
byte* p2 = (byte*)ptr;
Bitmap retBitmap = new Bitmap(width, height, PixelFormat.Format32bppArgb);
BitmapData fData = retBitmap.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadWrite,
PixelFormat.Format32bppArgb);
unsafe
{
for (int i = 0; i < fData.Height; i++)
{
byte* imgPtr = (byte*)(fData.Scan0 + (fData.Stride * i));
for (int x = 0; x < fData.Width; x++)
{
for (int ii = 0; ii < 4; ii++)
{
*imgPtr++ = *p2++;
}
//*imgPtr++ = 255;
}
}
}
retBitmap.UnlockBits(fData);
//Bitmap retBitmap = new Bitmap(width, height, GetStride(width, PixelFormat.Format32bppArgb),
// PixelFormat.Format32bppArgb, ptr);
return retBitmap;
}
}
}
private Bitmap BitmapImage2Bitmap(BitmapSource bitmapSrc)
{
using (MemoryStream outStream = new MemoryStream())
{
BitmapEncoder enc = new BmpBitmapEncoder();
enc.Frames.Add(BitmapFrame.Create(bitmapSrc));
enc.Save(outStream);
Bitmap bitmap = new Bitmap(outStream);
return new Bitmap(bitmap);
}
}
private int GetStride(int width, PixelFormat pxFormat)
{
int bitsPerPixel = ((int)pxFormat >> 8) & 0xFF;
int validBitsPerLine = width * bitsPerPixel;
int stride = ((validBitsPerLine + 31) / 32) * 4;
return stride;
}
You are missing the gap between the lines. The Stride value is not the amount of data in a line, it's the distance between the start of one line to the next. There may be a gap at the end of each line to align the next line on an even address boundary.
The Stride value can even be negative, then the image is stored upside down in memory. To get the data without the gaps and to handle all cases you need to copy one line at a time:
BitmapData blueData = blueBitmap.LockBits(new Rectangle(0, 0, blueBitmap.Width, blueBitmap.Height), ImageLockMode.ReadOnly, blueBitmap.PixelFormat);
int lineBytes = blueBitmap.Width * 3;
int numbBytes = lineBytes * blueBitmap.Height;
byte[] blueBytes = new byte[numbBytes];
for (int y = 0; y < blueBitmap.Height; y++) {
Marshal.Copy(blueData.Scan0 + y * blueData.Stride, blueBytes, y * lineBytes, lineBytes);
}
blueBitmap.UnlockBits(blueData);
blueBitmap.Dispose();

fast way to create a big bitmap from an array of bitmap?

I have this code,
copy/paste in a new winform app and this will write a file on your desktop if you run it: test123abcd.png
Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
Dim SquareSize = 5
Dim GridX = 2500
Dim GridY = 2500
Dim SquareCount = GridX * GridY - 1
Dim sw As New Stopwatch
Dim Rect(4) As Rectangle
Rect(0) = New Rectangle(0, 3, 3, 1)
Rect(1) = New Rectangle(3, 0, 1, 3)
Rect(2) = New Rectangle(3, 3, 3, 1)
Rect(3) = New Rectangle(0, 0, 1, 3)
Dim fullsw = Stopwatch.StartNew
Using board = New Bitmap(SquareSize * (GridX + 1), SquareSize * (GridY + 1), Imaging.PixelFormat.Format32bppPArgb)
Using graph = Graphics.FromImage(board)
Using _board = New Bitmap(SquareSize, SquareSize, Imaging.PixelFormat.Format32bppPArgb)
Using g As Graphics = Graphics.FromImage(_board)
For i = 0 To SquareCount
g.Clear(If((i And 1) = 1, Color.Red, Color.Blue))
g.FillRectangles(Brushes.White, Rect)
sw.Start()
graph.DrawImageUnscaled(_board, ((i Mod GridX) * SquareSize), ((i \ GridY) * SquareSize))
sw.Stop()
Next
End Using
End Using
End Using
fullsw.Stop()
board.Save(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory) & "\test123abcd.png", Imaging.ImageFormat.Png)
End Using
MessageBox.Show("Full SW: " & fullsw.ElapsedMilliseconds & Environment.NewLine &
"DrawImageUnscaled SW: " & sw.ElapsedMilliseconds)
End Sub
about 40% to 45% of the time spent is on DrawImageUnscaled, about 23 seconds on my current computer while the whole thing take about 50 seconds
is there a way to speed up DrawImageUnscaled? (and maybe the whole thing?)
EDIT - question in vb.net, answer in c#
By assuming that the generation part (g.FillRectangles(Brushes.White, Rect), pretty time-consuming too) cannot be avoided, the best thing you can do is avoiding a second graph-generation process (also for board) and just copying the information from _board. Copying is much quicker than a new generation (as shown below), but you have the problem that the source information (_board) do not match the destination format (board by relying on .SetPixel) and thus you will have to create a function determining the current pixel (X/Y point) from the provided information (current rectangle).
Below you can see a simple code showing the time requirement differences between both approaches:
Dim SquareSize As Integer = 5
Dim _board As Bitmap = Bitmap.FromFile("in.png")
Dim board As Bitmap = New Bitmap(_board.Width * SquareSize, _board.Height * SquareSize)
For x As Integer = 0 To _board.Width - 1
For y As Integer = 0 To _board.Height - 1
board.SetPixel(x * SquareSize, y * SquareSize, _board.GetPixel(x, y))
Next
Next
board.Save("out1.png", Imaging.ImageFormat.Png)
board = New Bitmap(_board.Width, _board.Height)
Using board
Using graph = Graphics.FromImage(board)
Using _board
Using g As Graphics = Graphics.FromImage(_board)
For x As Integer = 0 To _board.Width - 1
For y As Integer = 0 To _board.Height - 1
graph.DrawImageUnscaled(_board, x, y)
Next
Next
End Using
End Using
End Using
board.Save("out2.png", Imaging.ImageFormat.Png)
End Using
Bear in mind that it is not a "properly-working code". Its whole point is showing how to copy pixels between bitmaps (by multiplying by a factor, just to get different outputs than inputs); and putting the DrawImageUnscaled method under equivalent conditions (although the output picture is, logically, different) to get a good feeling of the differences in time requirements between both methodologies.
As said via comment, this is all what I can do under the current conditions. I hope that will be enough to help you find the best solution.
wow I like unsafe code when it is worthed, solved my problem with c# in the end
here is the code, which is about 70x faster to the code in my question
using System;
using System.Drawing;
using System.Drawing.Imaging;
namespace BmpFile
{
public class BmpTest
{
private const int PixelSize = 4;
public static long Test(int GridX, int GridY, int SquareSize, Rectangle[][] Rect)
{
Bitmap bmp = new Bitmap(GridX * SquareSize, GridY * SquareSize, PixelFormat.Format32bppArgb);
BitmapData bmd = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height),
System.Drawing.Imaging.ImageLockMode.ReadWrite,
bmp.PixelFormat);
int Stride = bmd.Stride;
int Height = bmd.Height;
int Width = bmd.Width;
int RectFirst = Rect.GetUpperBound(0);
int RectSecond;
int Offset1, Offset2, Offset3;
int i, j, k, l, w, h;
int FullRow = SquareSize * Stride;
int FullSquare = SquareSize * PixelSize;
var sw = System.Diagnostics.Stopwatch.StartNew();
unsafe
{
byte* row = (byte*)bmd.Scan0;
//draw all rectangles
for (i = 0; i <= RectFirst; ++i)
{
Offset1 = ((i / GridX) * FullRow) + ((i % GridX) * FullSquare) + 3;
RectSecond = Rect[i].GetUpperBound(0);
for (j = 0; j <= RectSecond; ++j)
{
Offset2 = Rect[i][j].X * PixelSize + Rect[i][j].Y * Stride;
w=Rect[i][j].Width;
h=Rect[i][j].Height;
for (k = 0; k <= w; ++k)
{
Offset3 = k * PixelSize;
for (l = 0; l <= h; ++l)
{
row[Offset1 + Offset2 + Offset3 + (l * Stride)] = 255;
}
}
}
}
//invert color
for (int y = 0; y < Height; y++)
{
Offset1 = (y * Stride) + 3;
for (int x = 0; x < Width; x++)
{
if (row[Offset1 + x * PixelSize] == 255)
{
row[Offset1 + x * PixelSize] = 0;
}
else
{
row[Offset1 + x * PixelSize] = 255;
}
}
}
}
sw.Stop();
bmp.UnlockBits(bmd);
bmp.Save(Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + #"\test.png", ImageFormat.Png);
bmp.Dispose();
return sw.ElapsedMilliseconds;
}
}
}

C# - reverse image bytes quickly?

I'm trying to determine the optimal way to flip an image across the Y axis. For every pixel, there are 4 bytes, and each set of 4 bytes needs to remain together in order but get shifted. Here's the best I've come up with so far.
This only takes .1-.2s for a 1280x960 image, but with video such performance is crippling. Any suggestions?
Initial implementation
private void ReverseFrameInPlace(int width, int height, int bytesPerPixel, ref byte[] framePixels)
{
System.Diagnostics.Stopwatch s = System.Diagnostics.Stopwatch.StartNew();
int stride = width * bytesPerPixel;
int halfStride = stride / 2;
int byteJump = bytesPerPixel * 2;
int length = stride * height;
byte pix;
for (int i = 0, a = stride, b = stride - bytesPerPixel;
i < length; i++)
{
if (b % bytesPerPixel == 0)
{
b -= byteJump;
}
if (i > 0 && i % halfStride == 0)
{
i = a;
a += stride;
b = a - bytesPerPixel;
if (i >= length)
{
break;
}
}
pix = framePixels[i];
framePixels[i] = framePixels[b];
framePixels[b++] = pix;
}
s.Stop();
System.Console.WriteLine("ReverseFrameInPlace: {0}", s.Elapsed);
}
Revision #1
Revised with indexes and Buffer.BlockCopy per SLaks and Alexei. Also added a Parallel.For since the indexes allow for it.
int[] pixelIndexF = null;
int[] pixelIndexB = null;
private void ReverseFrameInPlace(int width, int height, int bytesPerPixel, byte[] framePixels)
{
System.Diagnostics.Stopwatch s = System.Diagnostics.Stopwatch.StartNew();
if (pixelIndexF == null)// || pixelIndex.Length != (width * height))
{
int stride = width * bytesPerPixel;
int length = stride * height;
pixelIndexF = new int[width * height / 2];
pixelIndexB = new int[width * height / 2];
for (int i = 0, a = stride, b = stride, index = 0;
i < length; i++)
{
b -= bytesPerPixel;
if (i > 0 && i % (width / 2 )== 0)
{
//i = a;
i += width / 2;
a += stride;
b = a - bytesPerPixel;
if (index >= pixelIndexF.Length)
{
break;
}
}
pixelIndexF[index] = i * bytesPerPixel;
pixelIndexB[index++] = b;
}
}
Parallel.For(0, pixelIndexF.Length, new Action<int>(delegate(int i)
{
byte[] buffer = new byte[bytesPerPixel];
Buffer.BlockCopy(framePixels, pixelIndexF[i], buffer, 0, bytesPerPixel);
Buffer.BlockCopy(framePixels, pixelIndexB[i], framePixels, pixelIndexF[i], bytesPerPixel);
Buffer.BlockCopy(buffer, 0, framePixels, pixelIndexB[i], bytesPerPixel);
}));
s.Stop();
System.Console.WriteLine("ReverseFrameInPlace: {0}", s.Elapsed);
}
Revision #2
private void ReverseFrameInPlace(int width, int height, System.Drawing.Imaging.PixelFormat pixelFormat, byte[] framePixels)
{
System.Diagnostics.Stopwatch s = System.Diagnostics.Stopwatch.StartNew();
System.Drawing.Rectangle imageBounds = new System.Drawing.Rectangle(0,0,width, height);
//create destination bitmap, get handle
System.Drawing.Bitmap bitmap = new System.Drawing.Bitmap(width, height, pixelFormat);
System.Drawing.Imaging.BitmapData bitmapData = bitmap.LockBits(imageBounds, System.Drawing.Imaging.ImageLockMode.ReadWrite, bitmap.PixelFormat);
IntPtr ptr = bitmapData.Scan0;
//byte[] to bmap
System.Runtime.InteropServices.Marshal.Copy(framePixels, 0, ptr, framePixels.Length);
bitmap.UnlockBits(bitmapData);
//flip
bitmap.RotateFlip(System.Drawing.RotateFlipType.RotateNoneFlipX);
//get handle for bitmap to byte[]
bitmapData = bitmap.LockBits(imageBounds, System.Drawing.Imaging.ImageLockMode.ReadWrite, bitmap.PixelFormat);
ptr = bitmapData.Scan0;
System.Runtime.InteropServices.Marshal.Copy(ptr, framePixels, 0, framePixels.Length);
bitmap.UnlockBits(bitmapData);
s.Stop();
System.Console.WriteLine("ReverseFrameInPlace: {0}", s.Elapsed);
}
I faced almost the same issue but in my case I needed to flip the image for saving it to an .avi container.
I used the Array.Copy() method instead and suprisingly it seems faster than the others (at least, on my machine). The source image that I used was 720 x 576 pixels with 3 bytes per pixel. This method took between .001 - 0.01 seconds versus about 0.06 seconds for both your revisions.
private byte[] ReverseFrameInPlace2(int stride, byte[] framePixels)
{
System.Diagnostics.Stopwatch s = System.Diagnostics.Stopwatch.StartNew();
var reversedFramePixels = new byte[framePixels.Length];
var lines = framePixels.Length / stride;
for (var line = 0; line < lines; line++)
{
Array.Copy(framePixels, framePixels.Length - ((line + 1) * stride), reversedFramePixels, line * stride, stride);
}
s.Stop();
System.Console.WriteLine("ReverseFrameInPlace2: {0}", s.Elapsed);
return reversedFramePixels;
}
Try calling Buffer.BlockCopy on each range of 4 bytes; that should be faster.
You could parallelize execution on the CPU using any technique or use a pixel shader and do it on the GPU. If you only do that to display flipped video - you would best use DirectX and simply do a transformation on the GPU.
Couple more random things to try and measure:
pre-build array of indexes to copy to for a line (like [12,13,14,15, 8,9,10,11, 4,5,6,7, 0,1,2,3] instead of some complicated ifs executed on each line.
try copying to new destination instead of in-place.
Use one of the many transforms supplied by the .NET library:
http://msdn.microsoft.com/en-us/library/aa970271.aspx
Edit: Here's another example:
http://www.switchonthecode.com/tutorials/csharp-tutorial-image-editing-rotate
Another option might be to use the XNA framework to do manipulations on images. There is a small example of How to resize and save a Texture2D in XNA?. I have no idea how fast it is, but I could see how it should be pretty fast considering the functions are suppose to be used in games with high fps.

Help with Bitmap decoder

I've been working on a bitmap decoder, but my algorithm for processing the pixel data doesn't seem to be quite right:
public IntPtr ReadPixels(Stream fs, int offset, int width, int height, int bpp)
{
IntPtr bBits;
int pixelCount = bpp * width * height;
int Row = 0;
decimal value = ((bpp*width)/32)/4;
int RowSize = (int)Math.Ceiling(value);
int ArraySize = RowSize * Math.Abs(height);
int Col = 0;
Byte[] BMPData = new Byte[ArraySize];
BinaryReader r = new BinaryReader(fs);
r.BaseStream.Seek(offset, SeekOrigin.Begin);
while (Row < height)
{
Byte ReadByte;
if (!(Col >= RowSize))
{
ReadByte = r.ReadByte();
BMPData[(Row * RowSize) + Col] = ReadByte;
Col += 1;
}
if (Col >= RowSize)
{
Col = 0;
Row += 1;
}
}
bBits = System.Runtime.InteropServices.Marshal.AllocHGlobal(BMPData.Length);
System.Runtime.InteropServices.Marshal.Copy(BMPData, 0, bBits, BMPData.Length);
return bBits;
}
I can process only monochrome bitmaps and on some, parts of the bitmap is processed fine. None are compressed and they are rendered upside down and flipped around. I really could do with some help on this one.
decimal value = ((bpp*width)/32)/4;
int RowSize = (int)Math.Ceiling(value);
That isn't correct. Your RowSize variable is actually called "stride". You compute it like this:
int bytes = (width * bitsPerPixel + 7) / 8;
int stride = 4 * ((bytes + 3) / 4);
You are ignoring the stride.
Image rows can be padded to the left with additional Bytes to make their size divide by a number such as (1 = no padding, 2, 4, 8 = default for many images, 16, ...).
Also, images can be a rectangle region within a larger image, making the "padding" between lines in the smaller image even larger (since the stride is the larger image's stride). - In this case the image can also have an offset for its start point within the buffer.
Better practice is:
// Overload this method 3 time for different bit per SUB-pixel values (8, 16, or 32)
// = (byte, int, float)
// SUB-pixel != pixel (= 1 3 or 4 sub-pixels (grey or RGB or BGR or BGRA or RGBA or ARGB or ABGR)
unsafe
{
byte[] buffer = image.Buffer;
int stride = image.buffer.Length / image.PixelHeight;
// or int stride = image.LineSize; (or something like that)
fixed (float* regionStart = (float*)(void*)buffer) // or byte* or int* depending on datatype
{
for (int y = 0; y < height; y++) // height in pixels
{
// float* and float or byte* and byte or int* and int
float* currentPos
= regionStart + offset / SizeOf(float) + stride / SizeOf(float) * y;
for (int x = 0; x < width; x++) // width in pixels
{
for (int chan = 0; chan < channel; chan++) // 1, 3 or 4 channels
{
// DO NOT USE DECIMAL - you want accurate image values
// with best performance - primative types
// not a .NET complex type used for nice looking values for users e.g. 12.34
// instead use actual sub pixel type (float/int/byte) or double instead!
var currentValue = value;
currentPos++;
}
}
}
}
}
I find something I don't understand:
decimal value = ((bpp*width)/32)/4;
int RowSize = (int)Math.Ceiling(value);
RowSize, in my opinion, should be (bpp*width) / 8 + (bpp%8==0?0:1)

Getting RGB array from image in C#

I'm currently writing a C# implementation of a little program which I have written in Java.
I had used BufferedImage.getRGB(int startX, int startY, int w, int h, int[] rgbArray, int offset, int scansize) function in my Java app. But I couldn't exactly find a version of this in C# and I am not sure how to write it manually.
There's not a direct equivalent in the .NET Framework to this method. However, if your image is a System.Drawing.Bitmap, you can call the LockBits method, and this will return a BitmapData structure that contains the address of the first scanline. You can then use it to create what should be an API-compatible wrapper. I'm assuming you're using C# 3.5 or greater, so I'm using an extension method - if you're using an older flavor, change this to a regular method by dropping the 'this' from the Bitmap argument:
public static void getRGB(this Bitmap image, int startX, int startY, int w, int h, int[] rgbArray, int offset, int scansize)
{
const int PixelWidth = 3;
const PixelFormat PixelFormat = PixelFormat.Format24bppRgb;
// En garde!
if (image == null) throw new ArgumentNullException("image");
if (rgbArray == null) throw new ArgumentNullException("rgbArray");
if (startX < 0 || startX + w > image.Width) throw new ArgumentOutOfRangeException("startX");
if (startY < 0 || startY + h > image.Height) throw new ArgumentOutOfRangeException("startY");
if (w < 0 || w > scansize || w > image.Width) throw new ArgumentOutOfRangeException("w");
if (h < 0 || (rgbArray.Length < offset + h * scansize) || h > image.Height) throw new ArgumentOutOfRangeException("h");
BitmapData data = image.LockBits(new Rectangle(startX, startY, w, h), System.Drawing.Imaging.ImageLockMode.ReadOnly, PixelFormat);
try
{
byte[] pixelData = new Byte[data.Stride];
for (int scanline = 0; scanline < data.Height; scanline++)
{
Marshal.Copy(data.Scan0 + (scanline * data.Stride), pixelData, 0, data.Stride);
for (int pixeloffset = 0; pixeloffset < data.Width; pixeloffset++)
{
// PixelFormat.Format32bppRgb means the data is stored
// in memory as BGR. We want RGB, so we must do some
// bit-shuffling.
rgbArray[offset + (scanline * scansize) + pixeloffset] =
(pixelData[pixeloffset * PixelWidth + 2] << 16) + // R
(pixelData[pixeloffset * PixelWidth + 1] << 8) + // G
pixelData[pixeloffset * PixelWidth]; // B
}
}
}
finally
{
image.UnlockBits(data);
}
}
This wrapper can now be called like this:
Bitmap foo = Bitmap.FromFile(#"somefile.jpg") as Bitmap;
int[] rgbArray = new int[100];
foo.getRGB(1, 1, 10, 10, rgbArray, 0, 10);
Hope this helps, and welcome to .NET!
You'd use Bitmap.LockBits to get direct access to the pixels in a bitmap. Here's a sample implementation, it returns one scanline from the passed bitmap as an int[]:
int[] getRGB(Bitmap bmp, int line) {
var data = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height),
System.Drawing.Imaging.ImageLockMode.ReadOnly,
System.Drawing.Imaging.PixelFormat.Format32bppRgb);
try {
var ptr = (IntPtr)((long)data.Scan0 + data.Stride * (bmp.Height - line - 1));
var ret = new int[bmp.Width];
System.Runtime.InteropServices.Marshal.Copy(ptr, ret, 0, ret.Length * 4);
return ret;
}
finally {
bmp.UnlockBits(data);
}
}
I think the closest one is Bitmap.GetPixel(x,y) that return a single pixel color at a point.
In order to simulate the java function, you will need to write some helper.
You may need to check
Bitmap.GetPixel
Bitmap.LockBits
Also check Converting an array of Pixels to an image in C#.
It depends how fast you need to do it.
Bitmap has GetPixel() method which works fine for a pixel.
If you need to do fast image processing you need to use LockBits which you can find a sample here.
Bitmap img = (Bitmap) Image.FromFile(imageFileName);
BitmapData data = img.LockBits(new Rectangle(0,0,img.Width, img.Height), ImageLockMode.ReadWrite, img.PixelFormat);
byte* ptr = (byte*) data.Scan0;
for (int j = 0; j < data.Height; j++)
{
byte* scanPtr = ptr + (j * data.Stride);
for (int i = 0; i < data.width; i++, scanPtr+=NO_OF_CHANNELS)
{
for (int m = 0; m < NO_OF_CHANNELS; m++)
Console.WriteLine(*scanPtr); // value of each channel
}
}
img.UnlockBits(data);

Categories

Resources