Search code examples
.netgdi+system.drawing

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.


Solution

  • 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);