Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using System.Drawing, how can I draw something that imitates the effect of a yellow highlighting marker?

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.

like image 844
Corey Trager Avatar asked Feb 13 '10 13:02

Corey Trager


4 Answers

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.

like image 79
Kevin Reid Avatar answered Dec 14 '22 11:12

Kevin Reid


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.

like image 34
Guffa Avatar answered Dec 14 '22 10:12

Guffa


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 );
        }
    }
like image 26
Dan Byström Avatar answered Dec 14 '22 10:12

Dan Byström


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);
like image 36
Corey Trager Avatar answered Dec 14 '22 10:12

Corey Trager