'using' Statement being ignored - c#

With the function below, I'm running into problems trying to access the WriteableBitmap.PixelBuffer property. The message I am getting is:
'WriteableBitmap' does not contain a definition for 'PixelBuffer' and no extension method 'PixelBuffer' accepting a first argument of type 'WriteableBitmap' could be found (are you missing a using directive or an assembly reference?)
I have read other places that I need to include
using System.Runtime.InteropServices.WindowsRuntime;
But when I use this include, nothing changes in my code. Looking through the references of my solution, I don't see anything like System.Runtime.InteropServices. Im frustrated as this seems to be the solution to other people trying to access the PixelBuffer of a WriteableBitmap.
private WriteableBitmap ChangeBrightness(WriteableBitmap source, byte change_value)
{
WriteableBitmap dest = new WriteableBitmap(source);
byte[] color = new byte[4];
using (Stream s = source.PixelBuffer.AsStream())
{
using (Stream d = dest.PixelBuffer.AsStream())
{
// read the pixel color
while (s.Read(color, 0, 4) > 0)
{
// color[0] = b
// color[1] = g
// color[2] = r
// color[3] = a
// do the adding algo per byte (skip the alpha)
for (int i = 0; i < 4; i++)
{
if ((int)color[i] + change_value > 255) color[i] = 255; else color[i] = (byte)(color[i] + change_value);
}
// write the new pixel color
d.Write(color, 0, 4);
}
}
}
// return the new bitmap
return dest;
}

Make sure you are referencing the assembly that package belongs to:
System.Runtime.WindowsRuntime assembly

I've ended up going about solving my original problem via other means. To adjust the brightness of my image, I ended up using this functions instead:
public ImageSource AdjustBrightness(BitmapImage Image, int Value, int mod)
{
Bitmap TempBitmap = BitmapImage2Bitmap(Image);
Bitmap NewBitmap = new Bitmap(TempBitmap.Width, TempBitmap.Height);
Graphics NewGraphics = Graphics.FromImage(NewBitmap);
float FinalValue = (float)Value / 255.0f;
float[][] FloatColorMatrix ={
new float[] {1, 0, 0, 0, 0},
new float[] {0, 1, 0, 0, 0},
new float[] {0, 0, 1, 0, 0},
new float[] {0, 0, 0, 1, 0},
new float[] {mod * FinalValue, mod * FinalValue, mod * FinalValue, 1, 1}
};
ColorMatrix NewColorMatrix = new ColorMatrix(FloatColorMatrix);
ImageAttributes Attributes = new ImageAttributes();
Attributes.SetColorMatrix(NewColorMatrix);
NewGraphics.DrawImage(TempBitmap, new Rectangle(0, 0, TempBitmap.Width, TempBitmap.Height), 0, 0, TempBitmap.Width, TempBitmap.Height, GraphicsUnit.Pixel, Attributes);
Attributes.Dispose();
NewGraphics.Dispose();
return Bitmap2BitmapImage(NewBitmap);
}

Related

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:

Inverting image returns a black image

I want to invert an Image object. Currently my code looks like this:
private Image Invert(Image img)
{
var bmpPicture = new Bitmap(img.Width, img.Height);
var iaPicture = new ImageAttributes();
var cmPicture = new ColorMatrix { Matrix00 = -1, Matrix11 = -1, Matrix22 = -1 };
iaPicture.SetColorMatrix(cmPicture);
var gfxPicture = Graphics.FromImage(img);
var rctPicture = new Rectangle(0, 0, img.Width, img.Height);
gfxPicture.DrawImage(img, rctPicture, 0, 0, img.Width, img.Height, GraphicsUnit.Pixel, iaPicture);
return bmpPicture;
}
However, when I run this, and show it in a PictureBox, the result is a black image. I'm running this in Visual Studio 2012 under Windows 8 Release preview. If there is a better way to do this, please let me know. Thanks.
Try this: http://mariusbancila.ro/blog/2009/11/13/using-colormatrix-for-creating-negative-image/
public Bitmap Transform(Bitmap source)
{
//create a blank bitmap the same size as original
Bitmap newBitmap = new Bitmap(source.Width, source.Height);
//get a graphics object from the new image
Graphics g = Graphics.FromImage(newBitmap);
// create the negative color matrix
ColorMatrix colorMatrix = new ColorMatrix(new float[][]
{
new float[] {-1, 0, 0, 0, 0},
new float[] {0, -1, 0, 0, 0},
new float[] {0, 0, -1, 0, 0},
new float[] {0, 0, 0, 1, 0},
new float[] {1, 1, 1, 0, 1}
});
// create some image attributes
ImageAttributes attributes = new ImageAttributes();
attributes.SetColorMatrix(colorMatrix);
g.DrawImage(source, new Rectangle(0, 0, source.Width, source.Height),
0, 0, source.Width, source.Height, GraphicsUnit.Pixel, attributes);
//dispose the Graphics object
g.Dispose();
return newBitmap;
}
Fast alternative to using a ColorMatrix for pixel manipulation:
public static void BitmapInvertColors(Bitmap bitmapImage)
{
var bitmapRead = bitmapImage.LockBits(new Rectangle(0, 0, bitmapImage.Width, bitmapImage.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppPArgb);
var bitmapLength = bitmapRead.Stride * bitmapRead.Height;
var bitmapBGRA = new byte[bitmapLength];
Marshal.Copy(bitmapRead.Scan0, bitmapBGRA, 0, bitmapLength);
bitmapImage.UnlockBits(bitmapRead);
for (int i = 0; i < bitmapLength; i += 4)
{
bitmapBGRA[i] = (byte)(255 - bitmapBGRA[i]);
bitmapBGRA[i + 1] = (byte)(255 - bitmapBGRA[i + 1]);
bitmapBGRA[i + 2] = (byte)(255 - bitmapBGRA[i + 2]);
// [i + 3] = ALPHA.
}
var bitmapWrite = bitmapImage.LockBits(new Rectangle(0, 0, bitmapImage.Width, bitmapImage.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppPArgb);
Marshal.Copy(bitmapBGRA, 0, bitmapWrite.Scan0, bitmapLength);
bitmapImage.UnlockBits(bitmapWrite);
}
VB version of Dan's Answer. It works like a charm !!
Public Function Transform(source As Bitmap) As Bitmap
'create a blank bitmap the same size as original
Dim newBitmap As New Bitmap(source.Width, source.Height)
'get a graphics object from the new image
Dim g As Graphics = Graphics.FromImage(newBitmap)
' create the negative color matrix
Dim colorMatrix As New ColorMatrix(New Single()() {New Single() {-1, 0, 0, 0, 0}, New Single() {0, -1, 0, 0, 0}, New Single() {0, 0, -1, 0, 0}, New Single() {0, 0, 0, 1, 0}, New Single() {1, 1, 1, 0, 1}})
' create some image attributes
Dim attributes As New ImageAttributes()
attributes.SetColorMatrix(colorMatrix)
g.DrawImage(source, New Rectangle(0, 0, source.Width, source.Height), 0, 0, source.Width, source.Height, _
GraphicsUnit.Pixel, attributes)
'dispose the Graphics object
g.Dispose()
Return newBitmap
End Function
You aren't setting Matrix33 or Matrix44. My understanding is that Matrix33 would be the alpha component, so I suspect you're making your entire image transparent.
Try setting Matrix33 = 1.
I used "SIMD Supported Vectors" to make a code faster then pointers.Its under System.Numerics namespace.But before using these classes you need to get the update for System.Numerics from NuGet.Just search for System.Numerics.Anyways thats my class to invert the image
public class VBitmap : IDisposable
{
public short Width { get; private set; }
public short Height { get; private set; }
public int Stride { get; private set; }
public int PixelLenght { get; private set; }
public byte BytesperPixel { get; private set; }
public PixelFormat PixelFormat { get; private set; }
public byte[] Pixels { get; private set; }
public VBitmap(string path)
{
using (Bitmap bmp = new Bitmap(path))
{
BitmapData bmpdata = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, bmp.PixelFormat);
Width = checked((short)bmp.Width);
Height = checked((short)bmp.Height);
Stride = bmpdata.Stride;
PixelLenght = Stride * Height;
BytesperPixel = checked((byte)(Stride / Width));
PixelLenght = (PixelLenght % 16) != 0 ? PixelLenght + (PixelLenght % 16) : PixelLenght;
PixelFormat = bmp.PixelFormat;
Pixels = new byte[PixelLenght];
Marshal.Copy(bmpdata.Scan0, Pixels, 0, PixelLenght);
bmp.UnlockBits(bmpdata);
}
}
~VBitmap()
{
Dispose();
}
public void InvertPixels()
{
byte[] resultarray = Pixels;
unsafe
{
fixed (byte* result_ptr = resultarray)
{
for (int i = 0; i < PixelLenght; i += 16)
(~new Vector<byte>(Pixels, i)).CopyTo(resultarray, i);
}
}
}
public void InvertPixels(string name)
{
byte[] resultarray = Pixels;
unsafe
{
fixed (byte* result_ptr = resultarray)
{
for (int i = 0; i < PixelLenght; i += 16)
(~new Vector<byte>(Pixels, i)).CopyTo(resultarray, i);
SaveImage(name);
}
}
}
public unsafe void SaveImage(string name)
{
fixed (byte* p_ptr = Pixels)
{
using (Bitmap resultbmp = new Bitmap(Width, Height, Stride, PixelFormat, (IntPtr)p_ptr))
{
resultbmp.Save(name, ImageFormat.Jpeg);
}
}
}
public void Dispose()
{
Width = 0;
Height = 0;
Stride = 0;
PixelLenght = 0;
BytesperPixel = 0;
Pixels = null;
GC.Collect();
}
}
Usage.Note:To get best performance from vectors you need to run your code on Release Mode:
static void Main(string[] args)
{
new VBitmap("testp.png").InvertPixels("qq.jpg");//Inverts the given bitmap and saves it.
}
By inverting, do you mean creating a negative? If yes here is a snippet:
public void ApplyInvert()
{
byte A, R, G, B;
Color pixelColor;
for (int y = 0; y < bitmapImage.Height; y++)
{
for (int x = 0; x < bitmapImage.Width; x++)
{
pixelColor = bitmapImage.GetPixel(x, y);
A = pixelColor.A;
R = (byte)(255 - pixelColor.R);
G = (byte)(255 - pixelColor.G);
B = (byte)(255 - pixelColor.B);
bitmapImage.SetPixel(x, y, Color.FromArgb((int)A, (int)R, (int)G, (int)B));
}
}
}
from: http://www.smokycogs.com/blog/image-processing-in-c-sharp-inverting-an-image/
If you want read more about your problem and color matrix, please proceed with the following link: https://web.archive.org/web/20141230042200/http://bobpowell.net/negativeimage.aspx
var gfxPicture = Graphics.FromImage(img);
==>
var gfxPicture = Graphics.FromImage(bmpPicture);
DrawImage could distort image, GetPixel is slow, Try WPF imaging API

save bitmap image as grayscale image in C#

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)

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