I want what's "behind" the yellow to show through.
EDIT 1: But, if I'm drawing on "white", I'd like the marker color to retain its pure yellowness.
EDIT 2: @Kevin's answer is probably correct, and I marked it correct, even though I didn't code it up. In my code, I'm settling for @Guffa's answer, using Color.FromArgb.
EDIT 3: I posted code that works with decent performance. Yes, subtracting the blue is the basic idea, but you can't do it with a high level API, and SetPixel is too slow. The solution with good performance uses Bitmap.LockBits, UnlockBits.
A highlighter is pigment, so it's essentially subtractive — you want to turn white into yellow, but not black into yellow. I don't know .NET, but what you want is a non-default “blending mode”, specifically subtract. Specifically, set the blending mode to subtract and your color to pure blue (the color you want to subtract, leaving yellow). Black will be left alone, since there's no blue to subtract, and white will become yellow.
Unfortunately, many modern drawing interfaces leave out blending modes other than alpha, and it looks like this might be one of them. If you have access to the bitmap, you can implement it yourself — take each pixel value and set the blue component to zero. Or if the area you want to highlight is complicated, then: make a copy of your image, draw black over the highlighted area in the copy, then combine the red and green channels from the original and the blue channel from the copy into the final result image.
You use a semi transparent color, for example with a 50% alpha opacity:
Color.FromArgb(128, Color.Yellow)
With a lower alpha value more of the background will show through.
This is the code you need:
protected override void OnPaint( PaintEventArgs e )
{
using ( var bmp = new Bitmap( 100, 100 ) )
using ( var g = Graphics.FromImage( bmp ) )
using ( var ia = new ImageAttributes() )
{
// 1. create a sample bitmap
g.Clear( Color.White );
var p = Point.Empty;
foreach ( var color in new Color[] { Color.Black, Color.Gray, Color.LightBlue, Color.Green, Color.Red, Color.Magenta } )
using ( var brush = new SolidBrush( color ) )
{
g.DrawString( "Some sample text", SystemFonts.DefaultFont, brush, p );
p.Offset( 0, 16 );
}
// 2. transfer the bitmap on screen
e.Graphics.DrawImage( bmp, Point.Empty );
// 3. transfer a part of the bitmap on screen again, this time removing all blue
ia.SetColorMatrix( new ColorMatrix( new float[][] {
new float[] {1, 0, 0, 0, 0},
new float[] {0, 1, 0, 0, 0},
new float[] {0, 0, 0, 0, 0},
new float[] {0, 0, 0, 1, 0},
new float[] {0, 0, 0, 0, 1}} ) );
e.Graphics.DrawImage(
bmp,
new Rectangle( 30, 0, 40, 100 ),
30, 0, 40, 100,
GraphicsUnit.Pixel,
ia );
}
}
This code works. It wipes out the blue component of every RGB where I want yellow. At first I tried using Bitmap.GetPixel/SetPixel, but that was painfully slow. Using Lock/Unlock to get the raw bits was performed fast enough.
using (Bitmap tempBitmap = new Bitmap(bitmap.Width, bitmap.Height))
{
using (Graphics tempG = Graphics.FromImage(tempBitmap))
{
tempG.DrawLines(penYellowHighlighter, stroke.points.ToArray());
// get the raw bits of the source and target and remove the blue from every
// bit of the target where there is a yellow bit of the source
Rectangle rect = new Rectangle(0, 0, bitmapWithStrokes.Width, bitmapWithStrokes.Height);
// lock
System.Drawing.Imaging.BitmapData sourceData =
tempBitmap.LockBits(
rect,
System.Drawing.Imaging.ImageLockMode.ReadOnly,
tempBitmap.PixelFormat);
System.Drawing.Imaging.BitmapData targetData =
bitmapWithStrokes.LockBits(
rect,
System.Drawing.Imaging.ImageLockMode.ReadWrite,
bitmapWithStrokes.PixelFormat);
// Get the address of the first line.
IntPtr sourcePtr = sourceData.Scan0;
IntPtr targetPtr = targetData.Scan0;
// Declare an array to hold the bytes of the bitmap.
int numberOfBytes = Math.Abs(sourceData.Stride) * tempBitmap.Height;
byte[] sourceRgbValues = new byte[numberOfBytes];
byte[] targetRgbValues = new byte[numberOfBytes];
// Copy the RGB values into the array.
System.Runtime.InteropServices.Marshal.Copy(sourcePtr, sourceRgbValues, 0, numberOfBytes);
System.Runtime.InteropServices.Marshal.Copy(targetPtr, targetRgbValues, 0, numberOfBytes);
for (int p = 0; p < numberOfBytes; p += 4)
{
// if the source's red is yellows's red
if (sourceRgbValues[p + 2] == yellowsRedComponent)
{
// wipe out the target's blue
targetRgbValues[p] = 0;
}
}
// Copy the RGB values back to the bitmap
System.Runtime.InteropServices.Marshal.Copy(targetRgbValues, 0, targetPtr, numberOfBytes);
// Unlock the bits.
tempBitmap.UnlockBits(sourceData);
bitmapWithStrokes.UnlockBits(targetData);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With