I am trying to write a grayscale code.I wrote that basicly but I coud not understand how can I use Marshal or lockbits in this code for getting faster.(I chose this method due to using pointers more complicated for me)I wrote somethings but I know that there are too many or missing codes.I know I should lockbits instead of getpixel for making faster but I am so confused.It is complicated for me and probably I am using them at wrong places.
EDIT:I am trying to turn the photo to the gray color.It is compiling but it is not getting gray and I do not understand what is wrong with the code.
EDIT:I know the ansver is exist below but I want to learn writing this with Lockbites and Marshal.Copy funtions.
Here is the code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace dnm2709normalimg
{
class GrayFilter
{
public static Bitmap DoGray(Bitmap bmp)
{
for (int i = 0; i < bmp.Height; i )
{
for (int j = 0; j < bmp.Width; j )
{
int value = (bmp.GetPixel(j, i).R bmp.GetPixel(j, i).G bmp.GetPixel(j, i).B) / 3;
Color clr;
clr = Color.FromArgb(value, value, value);
BitmapData bmpData =bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height),ImageLockMode.ReadOnly, bmp.PixelFormat);
// Get the address of the first line.
IntPtr ptr = bmpData.Scan0;
// Declare an array to hold the bytes of the bitmap.
int bytes = Math.Abs(bmpData.Stride) * bmp.Height;
byte[] rgbValues = new byte[bytes];
// Copy the RGB values into the array.
Marshal.Copy(ptr, rgbValues, 0, bytes);
// Set every third value to 255. A 24bpp bitmap will look red.
for (int counter = 2; counter < rgbValues.Length; counter = 3)
rgbValues[counter] = 255;
// Copy the RGB values back to the bitmap
Marshal.Copy(rgbValues, 0, ptr, bytes);
bmp.UnlockBits(bmpData);
}
}
return bmp;
}
}
}
CodePudding user response:
First of all, you are looping over each pixel, and processing the entire image each time. So remove the outer loops.
// Set every third value to 255. A 24bpp bitmap will look red.
for (int counter = 2; counter < rgbValues.Length; counter = 3)
is incorrect. It presumes that each row will contain an exact number of pixels, with no bytes left over. Due to alignment the image may contain some extra bytes for each row, and that is why we need a "stride" and not just the width bytes per pixels.
Also, in your example, there is no calculation of the grayscale value. This should be some kind of averaging of each color channel, typically some weighting is used to put greater emphasis on the green channel.
So code to convert to grayscale should look something like this:
for(var y = 0; y < bmp.Height; y ){
var row = y * bmpData.Stride;
for(var x = 0; x < bmp.Width; x ){
var i = row x * bytesPerPixel;
var grayscaleValue = rgbValues[i] * 0.11 rgbValues[i 1]*0.59 rgbValues[i 2] * 0.3;
rgbValues[i] = grayscaleValue ;
rgbValues[i 1] = grayscaleValue ;
rgbValues[i 2] = grayscaleValue ;
}
}
Also note that this does not really care if the rgbValues
is a pointer, or a copied array. The difficult part is not using a pointer/unsafe code, but in getting the indices correct.
CodePudding user response:
If you access raw data as @JonasH did in his answer, then you must know what the actual PixelFormat
of your input Bitmap
is. His answer works for 24bpp RGB images.
A more general answer that works for all bitmaps:
private const float RLum = 0.299f;
private const float GLum = 0.587f;
private const float BLum = 0.114f;
public static Bitmap DoGray(Bitmap bmp)
{
using var result = new Bitmap(bmp.Width, bmp.Height);
using (Graphics g = Graphics.FromImage(result))
{
// Grayscale color matrix
var colorMatrix = new ColorMatrix(new float[][]
{
new float[] { RLum, RLum, RLum, 0, 0 },
new float[] { GLum, GLum, GLum, 0, 0 },
new float[] { BLum, BLum, BLum, 0, 0 },
new float[] { 0, 0, 0, 1, 0 },
new float[] { 0, 0, 0, 0, 1 }
});
using (var attrs = new ImageAttributes())
{
attrs.SetColorMatrix(colorMatrix);
g.DrawImage(bmp, new Rectangle(0, 0, result.Width, result.Height), 0, 0, result.Width, result.Height, GraphicsUnit.Pixel, attrs);
}
}
return result;
}
But if you don't mind using a library (shameless self promotion alert) it offers you fast bitmap data manipulation that hides the complexity of interpreting the actual pixel format so you can use it like this:
public static Bitmap DoGray(Bitmap bmp)
{
using var result = new Bitmap(bmp.Width, bmp.Height);
using IReadableBitmapData src = bmp.GetReadableBitmapData();
using IWritableBitmapData dst = result.GetWritableBitmapData();
IReadableBitmapDataRow rowSrc = src.FirstRow;
IWritableBitmapDataRow rowDst = dst.FirstRow;
do
{
for (int x = 0; x < src.Width; x )
rowDst[x] = rowSrc[x].ToGray();
} while (rowSrc.MoveNextRow() && rowDst.MoveNextRow());
return result;
}
But in fact, the library has a ToGrayscale extension method that is even much faster because it uses parallel processing (the test cases are taken from here):
==[Performance Test Results]================================================
Test Time: 2,000 ms
Warming up: Yes
Test cases: 3
Calling GC.Collect: Yes
Forced CPU Affinity: No
Cases are sorted by fulfilled iterations (the most first)
--------------------------------------------------
1. ImageExtensions.ToGrayscale: 4,549 iterations in 2,000.20 ms. Adjusted for 2,000 ms: 4,548.55
2. Sequential processing: 1,651 iterations in 2,001.54 ms. Adjusted for 2,000 ms: 1,649.73 (-2,898.82 / 36.27 %)
3. Graphics.DrawImage(..., ImageAttributes): 964 iterations in 2,000.17 ms. Adjusted for 2,000 ms: 963.92 (-3,584.63 / 21.19 %)
Update: All three options above return a new Bitmap
with 32bpp ARGB pixel format. To make a bitmap grayscale in place, you can use the MakeGrayscale
extension method from the mentioned library.
The 2nd example can be easily rewritten to modify the original Bitmap
in place, just obtain an IReadWriteBitmapData
by the GetReadWriteBitmapData
extension method. The link actually contains an example about how to make a Bitmap
grayscale in place.