I have 1000 Image of book page, Its text is dimmed as this piece
Now I tried to repair it to be more clearly to read, I use this code
private Bitmap repairImage(Bitmap bmp)
{
Color cc=Color.FromArgb(255, 251, 251, 251);
for (int x = 0; x < bmp.Width; x++)
{
for (int y = 0; y < bmp.Height; y++)
{
if (bmp.GetPixel(x, y).R>238)
{
bmp.SetPixel(x, y, Color.White);
}
else
{
bmp.SetPixel(x, y, Color.Black);
}
}
}
return bmp;
}
Due to the image dimensions is 1168 x 1807 it took a lot of time to finish repair, it exactly loops 2110576 cycles.
Is there any another way to solve this problem? Thanks.
The best way I can think of it to use the built-in ColorMatrix class to change the Gamma and the Contrast of the image.
Here is a result for a Gamma = 6.27 and a Contrast = +1.04:
Here is the code I used:
using System.Drawing.Imaging;
..
public static Bitmap ApplyGamma(Bitmap bmp0, float gamma, float contrast)
{
Bitmap bmp1 = new Bitmap(bmp0.Width, bmp0.Height);
using (Graphics g = Graphics.FromImage(bmp1))
{
ColorMatrix colorMatrix = new ColorMatrix(new float[][]
{
new float[] {contrast, 0, 0, 0, 0},
new float[] {0,contrast, 0, 0, 0},
new float[] {0, 0, contrast, 0, 0},
new float[] {0, 0, 0, 1, 0},
new float[] {0, 0, 0, 0, 1}
});
ImageAttributes attributes = new ImageAttributes();
attributes.SetColorMatrix(colorMatrix, ColorMatrixFlag.Default,
ColorAdjustType.Bitmap);
attributes.SetGamma(gamma, ColorAdjustType.Bitmap);
g.DrawImage(bmp0, new Rectangle(0, 0, bmp0.Width, bmp0.Height),
0, 0, bmp0.Width, bmp0.Height, GraphicsUnit.Pixel, attributes);
}
return bmp1;
}
The function uses two variables and two TrackBars along with two Labels:
float gamma = 1f ;
float contrast = 1f;
private void trackBar1_Scroll(object sender, EventArgs e)
{
gamma = 1f * trackBar1.Value / 100f;
label1.Text = gamma.ToString("#0.00");
pictureBox1.Image = ApplyGamma(originalImage, gamma, contrast);
}
private void trackBar2_Scroll(object sender, EventArgs e)
{
contrast = 1f * trackBar2.Value / 1000f;
label2.Text = contrast.ToString("#0.00");
pictureBox1.Image = ApplyGamma(originalImage, gamma, contrast);
}
Note that I am leaking the Bitmaps; it is just for testing ;-)
I use this (C++) code:
void picture::enhance_range()
{
int i,x,y,a0[4],min[4],max,n,c0,c1,q,c;
if (xs<1) return;
if (ys<1) return;
n=0; // dimensions to interpolate
if (pf==_pf_s ) { n=1; c0=0; c1=127*3; }
if (pf==_pf_u ) { n=1; c0=0; c1=255*3; }
if (pf==_pf_ss ) { n=2; c0=0; c1=32767; }
if (pf==_pf_uu ) { n=2; c0=0; c1=65535; }
if (pf==_pf_rgba) { n=4; c0=0; c1= 255; } // this is your image pixel format so ignore the other pf statements
// find min,max intensities
dec_color(a0,p[0][0],pf);
for (i=0;i<n;i++) min[i]=a0[i]; max=0;
for (y=0;y<ys;y++)
for (x=0;x<xs;x++)
{
dec_color(a0,p[y][x],pf); // this just unpack pixel color p[][] to a0[4]={r,g,b,a}
for (q=0,i=0;i<n;i++)
{
c=a0[i]; if (c<0) c=-c;
if (min[i]>c) min[i]=c;
if (max<c) max=c;
}
}
// change dynamic range to max
for (y=0;y<ys;y++)
for (x=0;x<xs;x++)
{
dec_color(a0,p[y][x],pf);
for (i=0;i<n;i++) a0[i]=c0+(((a0[i]-min[i])*(c1-c0))/(max-min[i]+1));
// for (i=0;i<n;i++) if (a0[i]<c0) a0[i]=c0; // clamp if needed
// for (i=0;i<n;i++) if (a0[i]>c1) a0[i]=c1; // clamp if needed
enc_color(a0,p[y][x],pf); // this just pack a0[4]={r,g,b,a} to pixel color p[][]
}
}
where:
pf is current pixel format in your case pf=_pf_rgba which is just enum constant
xs,ys is resolution of image
p[y][x] is direct pixel access to image
enc_color,dec_color just pack/unpack color components for desired pixel format
This is the result:
The main idea is to find minimal and maximal color value, Then enhance this dynamic range to maximum. For example (on grayscale colors) your image has:
min=181;
max=254;
So if you take each pixel and rescale to max <0,255> you need to do something like:
color=(color-min)*255/(max-min);
for each pixel of the image and that is all.
[Notes]
As #RosaGronchi mentioned Your current approach is slow due to use of getpixel,setpixel use scanlines instead (that should be few thousand times faster).
see GDI Bitmap and ScanLine[]
Also Another disadvantage of your approach is that you just binarise the image loosing all rendered anti-aliasing of text ...
Related
I have the following codes that transform an image's pixels:
Transforming using ColorMatrix
Bitmap bmpOut = null;
if (DisplayImage == null)
{
return;
}
bmpOut = GetBitmapFromImageSoure(OriginalImage.Source);
Bitmap originalImage = bmpOut;
Bitmap adjustedImage = new Bitmap(bmpOut.Width, bmpOut.Height);
float brightness = (float)BrightnessSlider.Value;
float contrast = (float)ContrastSlider.Value;
float gamma = (float)GammaSlider.Value;
float alpha = (float)AlphaSlider.Value;
float adjustedBrightness = brightness - 1.0f;
// create matrix that will brighten and contrast the image
float[][] ptsArray ={
new float[] {contrast, 0, 0, 0, 0}, // scale red
new float[] {0, contrast, 0, 0, 0}, // scale green
new float[] {0, 0, contrast, 0, 0}, // scale blue
new float[] {0, 0, 0, alpha, 0}, // scale alpha
new float[] {adjustedBrightness, adjustedBrightness, adjustedBrightness, 0, 1}}; // brightness
ImageAttributes imageAttributes = new ImageAttributes();
imageAttributes.ClearColorMatrix();
imageAttributes.SetColorMatrix(new ColorMatrix(ptsArray), ColorMatrixFlag.Default, ColorAdjustType.Bitmap);
imageAttributes.SetGamma(gamma, ColorAdjustType.Bitmap);
Graphics g = Graphics.FromImage(adjustedImage);
g.DrawImage(originalImage, new System.Drawing.Rectangle(0, 0, adjustedImage.Width, adjustedImage.Height)
, 0, 0, originalImage.Width, originalImage.Height,
GraphicsUnit.Pixel, imageAttributes);
this.DisplayImage.Source = ConvertToBitmapImage(adjustedImage);
Changing each pixel to transform to gray scale
Bitmap original = GetBitmapFromImageSoure(OriginalImage.Source);
Bitmap converted = (Bitmap)original.Clone();
System.Drawing.Color c;
for (int i = 0; i < converted.Width; i++)
{
for (int j = 0; j < converted.Height; j++)
{
c = converted.GetPixel(i, j);
byte gray = (byte)(.299 * c.R + .587 * c.G + .114 * c.B);
converted.SetPixel(i, j, System.Drawing.Color.FromArgb(gray, gray, gray));
}
}
DisplayImage.Source = ConvertToBitmapImage(converted);
The first method is for changing the brightness, gamma, contrast of an image which is controlled by a slider and immidiatly transform the image.
While the second method takes time to proccess the same image with just a checkbox.
Why is using a color matrix faster and how does it work better than changing each pixel in a loop.
I am currently creating sort of a game with C# and am trying to create outfits for the players. I would like to make cloth design and let players chose the colors.
I took pictures from gamefiles of TibiaME (tibiame.com), which does pretty much what i want to to.
How can I Fill this form with color? When I try to replace a certain color, it does not work, since it's not the same everyhwere. The shadows look pretty cool :P
The simplest (and fastest) way to color (tint) an image is to use a ColorMatrix.
Here is the result of using nine colors to tint the original:
Note that I have photoshopped the posted image bo be transparent around the center part; using just the original looks like this..:
((The glitch in the lower right is in the original..))
Here is a function the returns a list of tinted version of an image, one for each color in a list..:
List<Bitmap> TintImages(Bitmap bmp0, List<Color> colors )
{
List<Bitmap> tinted = new List<Bitmap>();
Size sz = bmp0.Size;
float f = 256f;
for (int i = 0; i < colors.Count; i++)
{
float r = colors[i].R / f;
float g = colors[i].G / f;
float b = colors[i].B / f;
float[][] colorMatrixElements = {
new float[] {r, 0, 0, 0, 0}, // red scaling factor of
new float[] {0, g, 0, 0, 0}, // green scaling factor
new float[] {0, 0, b, 0, 0}, // blue scaling factor
new float[] {0, 0, 0, 1, 0}, // alpha scaling factor
new float[] {0, 0, 0, 0, 1}}; // no further translations
ImageAttributes imageAttributes = new ImageAttributes();
ColorMatrix colorMatrix = new ColorMatrix(colorMatrixElements);
imageAttributes.SetColorMatrix(
colorMatrix,
ColorMatrixFlag.Default,
ColorAdjustType.Bitmap);
Bitmap bmp = new Bitmap(sz.Width, sz.Height);
using (Graphics gr = Graphics.FromImage(bmp))
{
gr.DrawImage(bmp0, new Rectangle(0, 0, sz.Width, sz.Height),
0, 0, sz.Width, sz.Height, GraphicsUnit.Pixel, imageAttributes);
tinted.Add(bmp);
}
}
return tinted;
}
You could iterate over each pixel of the bitmap and make a color shift in the direction you want. When I say colorshoft I mean a you have to adapt the RGB values of each pixel.
A simple shift to red could look like this:
for (int Xcount = 0; Xcount < myBitmap.Width; Xcount++)
{
for (int Ycount = 0; Ycount < myBitmap.Height; Ycount++)
{
//get color of the pixel
Color pixelColor = myBitmap.GetPixel(Xcount, Ycount);
byte red = pixelColor.R;
byte green = pixelColor.G;
byte blue = pixelColor.B;
//make shift and prevent overflow
if (red < 205)
red += 50;
else
red = 255;
//set color of the pixel
myBitmap.SetPixel(Xcount, Ycount, Color.FromRgb(red, green, blue));
}
}
Keep in mind this is just a simple example and may not result in what you expected.
You can read more about the RGB colorspace here: RGB color model and here you find a RGB Color Codes Chart
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; } }
}
}
}
This is my bitmap code
Bitmap b = new Bitmap(columns, rows, PixelFormat.Format24bppRgb);
BitmapData bmd = b.LockBits(new Rectangle(0, 0, columns, rows), ImageLockMode.ReadWrite, b.PixelFormat);
How can i save this as a grayscale image ?
Well iam specifically interested in the saving part. How do i save it as a file ?
I've used a similar method to this before
http://www.codeproject.com/KB/graphics/quickgrayscale.aspx
e.g:
for (int Y = 0; Y < Size.Y; Y++)
{
PixelData_s* PPixel =
PixelAt(0, Y, ImageWidth, PBase);
for (int X = 0; X < Size.X; X++)
{
byte Value = (byte)((PPixel->Red + PPixel->Green + PPixel->Blue) / 3);
PPixel->Red = Value;
PPixel->Green = Value;
PPixel->Blue = Value;
PPixel++;
} // End for
} // End for
Basically sum the RGB component values for the given pixel and divide by 3. This requires unsafe keyword which is required for operations using pointers. You could avoid using pointers and simply do something like this:
for (int X = 0; X < Size.X; X++)
{
for (int Y = 0; Y < Size.Y; Y++)
{
Color C = WinBitmap.GetPixel(X, Y);
int Value = (C.R + C.G + C.B) / 3;
WinBitmap.SetPixel(X, Y, Color.FromArgb(Value, Value, Value));
} // End for
} // End for
but this is rather slow compared.
We have an imaging component which facilitates application of numerous "effects", including simple color manipulations - it is considerably quicker to simply apply a color transformation matrix than manually walking pixel-by-pixel, like so, for example...
private static ColorMatrix GrayscaleMatrix = new ColorMatrix(
new float[][]
{
new float[] {0.30f, 0.30f, 0.30f, 0, 0},
new float[] {0.59f, 0.59f, 0.59f, 0, 0},
new float[] {0.11f, 0.11f, 0.11f, 0, 0},
new float[] {0, 0, 0, 1, 0},
new float[] {0, 0, 0, 0, 1}
}
);
public static void ApplyGrayscaleTransformation(string inputPath, string outputPath)
{
using (var image = Bitmap.FromFile(inputPath))
{
using (var graphics = Graphics.FromImage(image))
{
using (var attributes = new ImageAttributes())
{
attributes.SetColorMatrix(GrayscaleMatrix);
graphics.DrawImage(image,
new Rectangle(0,0,image.Width, image.Height),
0, 0, image.Width, image.Height, GraphicsUnit.Pixel,
attributes);
}
}
image.Save(outputPath);
}
}
The speed between this and unsafe methods are mostly negligible but can vary; it's worth testing cases when it gets to that point - one benefit is not having to compile with /unsafe.
I found a function how do this in this address
How to convert a colour image to grayscale
public Bitmap ConvertToGrayscale(Bitmap source)
{
Bitmap bm = new Bitmap(source.Width,source.Height);
for(int y=0;y<bm.Height;y++)
{
for(int x=0;x<bm.Width;x++)
{
Color c=source.GetPixel(x,y);
int luma = (int)(c.R*0.3 + c.G*0.59+ c.B*0.11);
bm.SetPixel(x,y,Color.FromArgb(luma,luma,luma));
}
}
return bm;
}
There are many links in google
like
https://web.archive.org/web/20110219113117/http://www.switchonthecode.com/tutorials/csharp-tutorial-convert-a-color-image-to-grayscale (WebArchive for broken link)
or
https://web.archive.org/web/20120707003826/http://www.dreamincode.net/code/snippet2570.htm (WebArchive for broken link)
I need to color balance an image that has an 18% gray card in it. The user loads this image into the application, then clicks on the gray card. From here is where I need help with an algorithm to color balance the image. I've found a few articles that mention doing a matrix transform, which I've tried, but without success (the image washes out or turns one color or another). The code I have now is:
int sampleSize = 20; // The square around the user's click on the gray card
int rVal = 0, gVal = 0, bVal = 0;
int count = 0;
for (int x = 0; x < sampleSize - 1; x++)
{
for (int y = 0; y < sampleSize - 1; y++)
{
System.Drawing.Color c = grayCardArea.GetPixel(x, y);
if (c.R > 0)
{
rVal += c.R;
gVal += c.G;
bVal += c.B;
rs.Add(c.R);
count++;
}
}
}
grayCardGraphics.Dispose();
int rAvg = 0, gAvg = 0, bAvg = 0;
rAvg = (int)Math.Round((decimal)rVal / (count));
gAvg = (int)Math.Round((decimal)gVal / (count));
bAvg = (int)Math.Round((decimal)bVal / (count));
// 117 is a value I found online for the neutral gray color of the gray card
float rDiff = (117 / (float)rAvg);
float gDiff = (117 / (float)gAvg);
float bDiff = (117 / (float)bAvg);
float[][] ptsArray =
{
new float[] {rDiff, 0, 0, 0, 0},
new float[] {0, gDiff, 0, 0, 0},
new float[] {0, 0, bDiff, 0, 0},
new float[] {0, 0, 0, 1, 0},
new float[] {0, 0, 0, .0f, 1}
};
// Create a ColorMatrix
ColorMatrix clrMatrix = new ColorMatrix(ptsArray);
// Create ImageAttributes
ImageAttributes imgAttribs = new ImageAttributes();
// Set color matrix
imgAttribs.SetColorMatrix(clrMatrix, ColorMatrixFlag.Default, ColorAdjustType.Default);
// Draw image with ImageAttributes
outputImageGraphics.DrawImage(srcImage, new System.Drawing.Rectangle(0, 0, srcImage.Width, srcImage.Height),
0, 0, srcImage.Width, srcImage.Height,
GraphicsUnit.Pixel, imgAttribs);
Viewing a saved copy of the outputImage shows an odd transformation of the image.
Any help is greatly appreciated!
My company, Atalasoft, has a free .NET Imaging SDK, with a class called LevelsCommand, that I think will do what you want.
http://atalasoft.com/photofree
Code is something like
AtalaImage img = new AtalaImage("filename");
LevelsCommand cmd = new LevelsCommand(/* ... */ ); // need to pass in leveling colors
img = cmd.Apply(img).Image;
img.Save("filename-new", new PngEncoder(), null); // or could be new JpegEncoder() or something else
You should use proper extensions on filenames to indicate the format.
Your first assumption appears to be that the image was properly exposed in the first place and that making the gray card read 117, 117, 117 will solve the problem. My advice is to leave the exposure alone and adjust just the color cast. You might find a different color model useful -- e.g., HSL. The saturation of a gray card should always be zero.
Alternatively, I have an example gray target reading 71, 72, 60. This is a bit warm. It would stand to reason that a more correct reading would be 67,67,67 or (R+G+B)/3. Because the image is a bit underexposed, I left it that way, but achieved a true neutral without altering the density of the image.
I hope this provides some help along your path toward getting the color right.