C# Color an image with shadows - c#

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

Related

Difference between transforming an image with a Color Matrix or by Bitmap.SetPixel

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.

Replacing colour of an Image

I am trying to replace the black colours of a picture with white, and vice versa. This is actually so my OCR code can read it on white backgrounds better. It's currently getting the image from clipboard
Image img = Clipboard.GetImage();
pictureBox1.SizeMode = PictureBoxSizeMode.StretchImage;
pictureBox1.Image = img;
I've seen some other questions where they're working with an actual bitmap, but how do I approach it direct from Clipboard?
Another solution using the ColorMatrix class.
You can use the Graphics.DrawImage overload that accepts an ImageAttributes argument.
ImageAttributes.SetColorMatrix() sets the color-adjustment matrix, optionally targeting a specific category (Bitmap, Pen, Brush etc.) and can be instructed to skip the Gray Colors, modify the Gray colors only or all Colors.
The ImageAttributes.SetThreshold() method allows to regulate the Colors cutoff point (threshold) to fine tune the Brightness.
It accepts values from 0 to 1.
When set to 0, an image is all white, all black when set to 1 (see the Docs about it).
Also consider that the "Inversion" depends on the original bitmap color pattern, so try different approaches. Sometimes, inverting the brightness can give you a better result, sometime it doesn't.
You OCR must be "trained", to verify what values suits it better.
Take a look at these articles:
Recoloring (MSDN)
ASCII Art Generator (CodeProject)
Brightness Matrix:
R=Red G=Green B=Blue A=Alpha Channel W=White (Brightness)
Modify the Brightness component to obtain an "Inversion"
R G B A W
R [1 0 0 0 0]
G [0 1 0 0 0]
B [0 0 1 0 0]
A [0 0 0 1 0]
W [b b b 0 1] <= Brightness
using System.Drawing;
using System.Drawing.Imaging;
// ...
Image colorImage = Clipboard.GetImage();
// Default values, no inversion, no threshold adjustment
var bmpBlackWhite = BitmapToBlackAndWhite(colorImage);
// Inverted, use threshold adjustment set to .75f
var bmpBlackWhite = BitmapToBlackAndWhite(colorImage, true, true, .75f);
// ...
private Bitmap BitmapToBlackAndWhite(Image image, bool invert = false, bool useThreshold = false, float threshold = .5f)
{
var mxBlackWhiteInverted = new float[][]
{
new float[] { -1, -1, -1, 0, 0},
new float[] { -1, -1, -1, 0, 0},
new float[] { -1, -1, -1, 0, 0},
new float[] { 0, 0, 0, 1, 0},
new float[] { 1, 1, 1, 0, 1}
};
var mxBlackWhite = new float[][]
{
new float[] { 1, 1, 1, 0, 0},
new float[] { 1, 1, 1, 0, 0},
new float[] { 1, 1, 1, 0, 0},
new float[] { 0, 0, 0, 1, 0},
new float[] {-1, -1, -1, 0, 1}
};
var bitmap = new Bitmap(image.Width, image.Height);
using (var g = Graphics.FromImage(bitmap))
using (var attributes = new ImageAttributes()) {
attributes.SetColorMatrix(new ColorMatrix(invert ? mxBlackWhiteInverted : mxBlackWhite));
// Adjust the threshold as needed
if (useThreshold) attributes.SetThreshold(threshold);
var rect = new Rectangle(Point.Empty, image.Size);
g.DrawImage(image, rect, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, attributes);
return bitmap;
}
}
You can use the ColorMap and ImageAttributes classes from the System.Drawimg.Imaging namespace to directly replace the pixels in your image:
Image img = Clipboard.GetImage();
if (img != null) {
ColorMap[] cm = new ColorMap[1];
cm[0] = new ColorMap();
cm[0].OldColor = Color.Black;
cm[0].NewColor = Color.White;
ImageAttributes ia = new ImageAttributes();
ia.SetRemapTable(cm);
using (Graphics g = Graphics.FromImage(img)) {
g.DrawImage(img, new Rectangle(Point.Empty, img.Size), 0, 0, img.Width, img.Height,
GraphicsUnit.Pixel, ia);
}
pictureBox1.SizeMode = PictureBoxSizeMode.StretchImage;
pictureBox1.Image = img;
}
if your image is B&W, it is very easy in OpenCV:
// bmp is your bitmap
var inverted = (255 - bitmap.ToMat()).ToMat().
.ToBitamp() // or
.ToWriteableBitmap() // or
.ToBitmapSource()
OpenCV can be a little overkill if this in your only manipulation in the whole app

Convert grayscale partially transparent image to a single color in c#

I am trying to create a function that takes a gray scale image and a color and colors the gray scale image using that color shade but keeps the shading levels of the gray scale image. The function also should not color the transparent parts of the image. I have multiple layers (multiple png's) I will be combining later and only need to color certain layers. I have looked around and found similar things but not quite what I need. I know how to do it in HTML5 on front end for the user using Canvas but I need a way to achieve same thing on the backend using I am guessing either a manual method using unlocked bitmap memory calls or a ColorMatrix class. Can anyone help me, graphics aren't my strongest area but I am slowly learning. See the function below for what I need in C# that I did in javascript. Doing the hidden canvas stuff isn't as important because I am doing this server side for saving to PNG file...
function drawImage(imageObj, color) {
var hidden_canvas = document.createElement("canvas");
hidden_canvas.width = imageObj.width;
hidden_canvas.height = imageObj.height;
var hidden_context = hidden_canvas.getContext("2d");
// draw the image on the hidden canvas
hidden_context.drawImage(imageObj, 0, 0);
if (color !== undefined) {
var imageData = hidden_context.getImageData(0, 0, imageObj.width, imageObj.height);
var data = imageData.data;
for (var i = 0; i < data.length; i += 4) {
var brightness = 0.34 * data[i] + 0.5 * data[i + 1] + 0.16 * data[i + 2];
//red
data[i] = brightness + color.R;
//green
data[i + 1] = brightness + color.G;
//blue
data[i + 2] = brightness + color.B;
}
//overwrite original image
hidden_context.putImageData(imageData, 0, 0);
}
var canvas = document.getElementById('card');
var context = canvas.getContext('2d');
context.drawImage(hidden_canvas, 0, 0);
};
This should do the job:
public static Bitmap MakeChromaChange(Bitmap bmp0, Color tCol, float gamma)
{
Bitmap bmp1 = new Bitmap(bmp0.Width, bmp0.Height);
using (Graphics g = Graphics.FromImage(bmp1))
{
float f = (tCol.R + tCol.G + tCol.B) / 765f;
float tr = tCol.R / 255f - f;
float tg = tCol.G / 255f - f;
float tb = tCol.B / 255f - f;
ColorMatrix colorMatrix = new ColorMatrix(new float[][]
{ new float[] {1f + tr, 0, 0, 0, 0},
new float[] {0, 1f + tg, 0, 0, 0},
new float[] {0, 0, 1f + tb, 0, 0},
new float[] {0, 0, 0, 1, 0},
new float[] {0, 0, 0, 0, 1} });
ImageAttributes attributes = new ImageAttributes();
attributes.SetGamma(gamma);
attributes.SetColorMatrix(colorMatrix);
g.DrawImage(bmp0, new Rectangle(0, 0, bmp0.Width, bmp0.Height),
0, 0, bmp0.Width, bmp0.Height, GraphicsUnit.Pixel, attributes);
}
return bmp1;
}
Note that I kept a gamma parameter; if you don't need it keep the value at 1f;
Here it is at work, adding first red then more red and some blue :
Transparent pixels are not affected.
For more on ColorMatrix here is a really nice intro!
As a fun project I applied the known colors to a known face:

How to repair dimmed image of book page

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 ...

How to do color balancing using a gray card in C#

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.

Categories