Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Transparency Tutorial with C# - Part 1

0.00/5 (No votes)
23 Mar 2004 2  
An article on alpha blending and transparency

Introduction

I decided to write these tutorials after I realized that I didn’t really understand how C# handled transparency. I was doing some alpha blending and the resulting colors were not what I expected. So I built a tool, AlphaBlender Figure 1, to show a Venn diagram of the colors mixed like in standard color theory texts and I was further puzzled that the tool produced an image unlike any I’d seen in school.

Figure 1 AlphaBlender demo application

Figure 1 AlphaBlender demo application

Figure 2 shows what I got versus what I expected.

Figure2, Alpha blend versus my expectations of ‘real’ blended colors

Figure2, Alpha blend versus my expectations of ‘real’ blended colors

Well… nobody told me that alpha blending was supposed to be like my expectations; in fact, I am frequently surprised at how little anything works like I first expect it too. So I decided to write these tutorials to help me understand what’s really going on with transparency in C#.

In each of these tutorials, we consider the ‘what’ before the ‘how’-- a discussion is presented of the concepts behind the code, and then at the end, we look at the code behind the concepts. In the code section, I’ll introduce each relevant new element of GDI+ as it occurs, and I won’t mention it again if it reoccurs in later code. This should help with redundancy and get the elementary stuff over with quickly.

Also a word of caution: I’m no C# guru. I’ve written the demonstration code to illustrate the transparency concepts, not to demonstrate good programming practice. I encourage any and all to send me comments on my coding practices and how I might improve them.

The Concepts

What Is Color?

Color is a human thing. It is defined by our ability to perceive a narrow band of the electromagnetic spectrum that we call visible light. Our eyes have ’rod’ cells that sense variations in black and white, and we have three types of cone cells, one each for red, green and blue.

We can simulate our perception of color by mixing red, green, and blue, which is what a computer monitor does. This brings us to the natural use of these components to create colors in C#, where a color is a 32-bit structure of four bytes for Alpha, Red, Green, and Blue

Alpha is a transparency parameter that defines how much of the existing display color pixel that should ‘show through’ the new color.

Visualization Tools

I propose that if a picture is worth a thousand words, then a demo program is worth a ten thousand. The following demonstrations show some things about transparency use in C#.

I wrote ColorMaker, Figure 3, to show the effect of varying each of the color structure parameters. The color is created over a gradient, black to white, to illustrate how the Alpha value affects the ‘show through’ of the background color

Figure 3, ColorMaker demo application

Figure 3, ColorMaker demo application

Next I wrote WhatColorIsIt, Figure 4, to show the color parameters for any pixel on the screen. (This demo is based on Charles Petzold’s WhatColor example from his C# book).

Figure 4, WhatColorIsIt demo application

Figure 4, WhatColorIsIt demo application

I combined what I learned with these two programs and wrote Spectrum, Figure 5, which simulates the color spectrum of visible light and allows the user to read the color parameters.

Figure 5 Light spectrum simulation demo application

Figure 5 Light spectrum simulation demo application

Transparency

I started this tutorial because I didn’t understand how alpha blending actually worked. Figure 1 shows what I was getting versus what I was expecting, and it also shows fairly obviously what is really going on. Alpha blending does not work like blending light; it works like stacking glass filters.

If you take a red, green, and blue glass filters and lay them on a background, you would get an effect like what we see in the demo. Filters with 50% transparency should look like the demo with alpha set to 127.

Here’s the alpha blend algorithm:

displayColor = sourceColor×alpha / 255 + backgroundColor×(255 – alpha) / 255

I did some calculations starting with an opaque white background to see what this gives

Add a 50% transparent red pixel over an opaque white pixel:
sourceColor(127,255,0,0) ( Red, 50% transparent)
background Color(255,255,255,255) (Opaque white)

displayColor Red = ( <span class="cpp-literal">255</span> * <span class="cpp-literal">127</span>/<span class="cpp-literal">255</span>) + (<span class="cpp-literal">255</span>)*(<span class="cpp-literal">255</span> – <span class="cpp-literal">127</span>)/<span class="cpp-literal">255</span> = 
<span class="cpp-literal">255</span>;<br />displayColor Green = (<span class="cpp-literal">0</span> * <span class="cpp-literal">127</span>/<span class="cpp-literal">255</span>) + (<span class="cpp-literal">255</span>)*(<span class="cpp-literal">255</span> – <span class="cpp-literal">127</span>)/<span class="cpp-literal">255</span> = 
<span class="cpp-literal">127</span>;<br />displayColor Blue = (<span class="cpp-literal">0</span> * <span class="cpp-literal">127</span>/<span class="cpp-literal">255</span>) + (<span class="cpp-literal">255</span>)*(<span class="cpp-literal">255</span> – <span class="cpp-literal">127</span>)/<span class="cpp-literal">255</span> = 
<span class="cpp-literal">127</span>;

Resulting Color (127,255,127,127)

Add a 50% transparent green pixel over the results:
sourceColor(127,0,255,0) ( Green, 50% transparent)
backgroundColor(127,255,127,127)

displayColor Red = (<span class="cpp-literal">0</span> * <span class="cpp-literal">127</span>/<span class="cpp-literal">255</span>) + (<span class="cpp-literal">255</span>)*(<span class="cpp-literal">255</span> – <span class="cpp-literal">127</span>)/<span class="cpp-literal">255</span> = 
<span class="cpp-literal">127</span>;<br />displayColor Green = (<span class="cpp-literal">255</span> * <span class="cpp-literal">127</span>/<span class="cpp-literal">255</span>) + (<span class="cpp-literal">127</span>)*(<span class="cpp-literal">255</span> – <span class="cpp-literal">127</span>)/<span class="cpp-literal">255</span> = 
<span class="cpp-literal">192</span>;<br />displayColor Blue = (<span class="cpp-literal">0</span> * <span class="cpp-literal">127</span>/<span class="cpp-literal">255</span>) + (<span class="cpp-literal">127</span>)*(<span class="cpp-literal">255</span> – <span class="cpp-literal">127</span>)/<span class="cpp-literal">255</span> = 
<span class="cpp-literal">65</span>;

Resulting Color (127,127,192,64)

Add a 50% transparent blue pixel over the results:
sourceColor(127,0,0,255) ( Blue, 50% transparent)
background Color(127,127,192,64)

displayColor Red = (<span class="cpp-literal">0</span> * <span class="cpp-literal">127</span>/<span class="cpp-literal">255</span>) + (<span class="cpp-literal">127</span>)*(<span class="cpp-literal">255</span> – <span class="cpp-literal">127</span>)/<span class="cpp-literal">255</span> = 
<span class="cpp-literal">64</span>;<br />displayColor Green = (<span class="cpp-literal">0</span> * <span class="cpp-literal">127</span>/<span class="cpp-literal">255</span>) + (<span class="cpp-literal">192</span>)*(<span class="cpp-literal">255</span> – <span class="cpp-literal">127</span>)/<span class="cpp-literal">255</span> = 
<span class="cpp-literal">96</span>;<br />displayColor Blue = (<span class="cpp-literal">255</span> * <span class="cpp-literal">127</span>/<span class="cpp-literal">255</span>) + (<span class="cpp-literal">64</span>)*(<span class="cpp-literal">255</span> – <span class="cpp-literal">127</span>)/<span class="cpp-literal">255</span> = <span class="cpp-literal">159</span>;

Resulting Color (127,64,96,159)

Compare the ‘real’ world to the GDI+ world with 50% transparent colors: ‘Real World’ GDI+ World
Red over white (255,0,0) - Pink. (255,0,0) - Pink
Green over results (255,255,0) - Yellow (127,192,64) – Light Olive?
Blue over result (255,255,255) - White. (64,96,159) – Dark Slate Blue?

I did the same calculations starting over opaque black and got:

Red over black (127,0,0) – Med. Red (127,0,0) – Med. Red
Green over results (127,127,0) – Med. Yellow (64,127,0) – Dark Olive?
Blue over results (127127,0) – Med. Gray (32,64,127) – Gray Navy?

Conclusion: Alpha blending simulates real world transparency for one layer only.

I wrote a tool, Alpha Demonstrator Figure 6, to show the effect of ‘stacking’ order and background color to further illustrate what’s really going on. You can change the stacking order and background color in the demo to view each stacking and background color permutation.

Figure 6, Alpha Demonstrator

Figure 6, Alpha Demonstrator

To wind things up for this tutorial, I wrote Color Demo, Figure 7, which shows additive and subtractive color and effects of various backgrounds, the way I think they should look to simulate the ‘real’ world.

Figure 7 Color Demo illustrates additive and subtractive color theory

Figure 7 Color Demo illustrates additive and subtractive color theory

The Code

Note on flicker-free drawing

Some of these tutorial demos push the systems resources and flicker like crazy using ‘standard’ C# coding practices. There are many ways to prevent flicker and I present one way to use a double buffering technique to get fairly flicker free drawing. I say ‘fairly’ because Windows© will draw your image buffer to the screen when it damn well pleases. It would be best to put your buffer into screen memory during the CRT’s vertical blanking interval when nothing is being written to the screen (I’m not sure if this is true for LCD’s). If Windows© is in the process of writing your image in memory as the CRT ‘paints’ the screen through the memory you are using, the loaded part will show, but the rest won’t since it hasn’t been loaded yet. Windows© finishes loading and on the next screen painting cycle the full image shows up. This causes a kind of flicker called ‘tear’ and I know of no way to prevent this in GDI+. In DirectDraw you would load your buffered image during the vertical blanking interval and avoid tear. That said, the double buffering used here prevents most of the flicker that you’d see if you don’t use double buffering and yields results that I can live with.

First you set the style using the Control.SetStyle method for setting flags that categorize supported behavior. The flags are listed in the ControlStyles enumeration. We will use three:

  1. AllPaintingInWmPaint – the control will ignore the WM_ERASEBKGND message and paint its own background.
  2. UserPaint – the control paints itself rather than letting the OS do the painting. You do not use the form’s Paint event, you instead override the OnPaint method.
  3. DoubleBuffer – the drawing is done in a buffer and the buffer is drawn to the screen.

In your form constructor add the following styles:

<span class="cs-keyword">public</span> Form1()
{
  <span class="cs-comment">//</span>
  <span class="cs-comment">// Required for Windows Form Designer support</span>
  <span class="cs-comment">//</span>
  InitializeComponent();

  <span class="cs-comment">//</span>
<span class="cs-comment">// TODO: Add any constructor code after InitializeComponent call</span>
  <span class="cs-comment">//</span>
  SetStyle(ControlStyles.AllPaintingInWmPaint | 
    ControlStyles.UserPaint | 
    ControlStyles.DoubleBuffer,
    <span class="cs-keyword">true</span>);    
}

Next you override the OnPaint method and draw the background:

<span class="cs-keyword">protected</span> <span class="cs-keyword">override</span> <span class="cs-keyword">void</span> OnPaint(System.Windows.Forms.PaintEventArgs e)
{
<span class="cs-comment">// Fill in Background (for efficiency only the area that has been clipped)</span>
e.Graphics.FillRectangle(<span class="cs-keyword">new</span> SolidBrush(SystemColors.Window),
e.ClipRectangle.X,
e.ClipRectangle.Y,
e.ClipRectangle.Width,
e.ClipRectangle.Height);
  <span class="cs-comment">// </span>
<span class="cs-comment">// Do your drawing</span>
<span class="cs-comment">// </span>

  <span class="cs-comment">// </span>
<span class="cs-comment">// Make darn sure you dispose of everything that you create</span>
<span class="cs-comment">// that is disposable (brushs, pens, bitmaps, etc.). Garbage </span>
<span class="cs-comment">// collection will get rid of the stuff eventually, but it is </span>
<span class="cs-comment">// real easy to overload the system with lots of paints.</span>
<span class="cs-comment">// </span>
}

Color Maker

The ColorMaker, Figure 3, allows the user to use slider controls to set the red, green, blue, and alpha parameters of the color structure and paint the results over a black to white gradient background.

We create a rectangle for the gradient and the color.

<span class="cs-comment">// Create rectangle</span>
<span class="cs-keyword">private</span> Rectangle rect = <span class="cs-keyword">new</span> Rectangle(<span class="cs-literal">8</span>, <span class="cs-literal">48</span>, <span class="cs-literal">272</span>, <span class="cs-literal">72</span>);

The user uses scroll bars sets the color elements.

<span class="cs-keyword">private</span> <span class="cs-keyword">void</span> trackBarRed_Scroll(<span class="cs-keyword">object</span> sender, System.EventArgs e)
{
  <span class="cs-keyword">int</span> temp = trackBarRed.Value;
  <span class="cs-keyword">if</span>(temp><span class="cs-literal">255</span>)temp = <span class="cs-literal">255</span>;
  red = (<span class="cs-keyword">byte</span>)temp;
  labelRed.Text = <span class="cpp-string">"Red: "</span> + red.ToString();
  Refresh();
}

In the OnPaint method we create the gradient box by first creating a gradient brush that will make a black to white horizontal gradient.

<span class="cs-comment">// Create back box brush</span>
LinearGradientBrush lgBrush = 
<span class="cs-keyword">new</span> LinearGradientBrush(backRectangle,
Color.Black,
Color.White,
LinearGradientMode.Horizontal);  

We then draw this box to the screen using the Graphics FillRectangle method.

<span class="cs-comment">// Create back box fill</span>
e.Graphics.FillRectangle(lgBrush,backRectangle);

Next we create the colorBrush from color elements provided by the slider values.

<span class="cs-comment">// Create transparent brushes</span>
SolidBrush colorBrush = 
<span class="cs-keyword">new</span> SolidBrush(Color.FromArgb(alpha,red,green,blue));

We then draw the color over the gradient

<span class="cs-comment">// Create the color box fills</span>
e.Graphics.FillRectangle(colorBrush,rect);

And don’t forget to clean up.

<span class="cs-comment">// Dispose now to conserve system resources</span>
lgBrush.Dispose();
colorBrush.Dispose();

AlphaBlender

This is the demonstration that got me to thinking about all this in the first place. As I said in the beginning, it didn’t behave like I expected, instead it did just what it was supposed to.

AlphaBlender, Figure 1, adds FillEllipse to the prior discussion.

<span class="cs-comment">// Create circle fills</span>
e.Graphics.FillEllipse(redBrush,redRectangle);
e.Graphics.FillEllipse(greenBrush,greenRectangle);
e.Graphics.FillEllipse(blueBrush,blueRectangle);

WhatColorIsIt

WhatColorIsIt, Figure 4, is derived from WhatColor.cs © 2002 by Charles Petzold, www.charlespetzold.com. It uses COM Interoperability, which allows C# users to access non-GDI+ functions from the Win32 API.

This is hardly a beginner topic, but I’ve included it here because the tool itself is so useful and it gives quick insight in how to expand your C# toolset. Make certain that you dispose of anything you create from a DLL since it is unmanaged and doesn’t get garbage collected when you close.

At the top of the code we add:

<span class="cs-keyword">using</span> System.Runtime.InteropServices;

In the Form1 class we define the external Win2 functions:

[DllImport(<span class="cpp-string">"gdi32.dll"</span>)]
<span class="cs-keyword">public</span> <span class="cs-keyword">static</span> <span class="cs-keyword">extern</span> IntPtr CreateDC(<span class="cs-keyword">string</span> strDriver, 
  <span class="cs-keyword">string</span> strDevice, <span class="cs-keyword">string</span> strOutput, IntPtr pData);
[DllImport(<span class="cpp-string">"gdi32.dll"</span>)]
<span class="cs-keyword">public</span> <span class="cs-keyword">static</span> <span class="cs-keyword">extern</span> <span class="cs-keyword">bool</span> DeleteDC(IntPtr hdc);
[DllImport(<span class="cpp-string">"gdi32.dll"</span>)]
<span class="cs-keyword">public</span> <span class="cs-keyword">static</span> <span class="cs-keyword">extern</span> <span class="cs-keyword">int</span> GetPixel(IntPtr hdc, <span class="cs-keyword">int</span> x, <span class="cs-keyword">int</span> y);

We use the form designer to add a timer. Then we use the properties box to add the Tick event. To this we add our (well, Petzold’s) code.

<span class="cs-keyword">private</span> <span class="cs-keyword">void</span> timer1_Tick(<span class="cs-keyword">object</span> sender, System.EventArgs e)
{
<span class="cs-comment">// Get the current mouse position (screen coordinates).</span>
Point pt = MousePosition;

<span class="cs-comment">// Call the three external functions.</span>
IntPtr hdcScreen = CreateDC(<span class="cpp-string">"Display"</span>, <span class="cs-keyword">null</span>, <span class="cs-keyword">null</span>, IntPtr.Zero);
<span class="cs-keyword">int</span> cr = GetPixel(hdcScreen, pt.X, pt.Y);
DeleteDC(hdcScreen);

<span class="cs-comment">// Convert a Win32 COLORREF to a .NET Color object</span>
clr = Color.FromArgb((cr & <span class="cs-literal">0x000000FF</span>),
  (cr & <span class="cs-literal">0x0000FF00</span>) >>

In our OnPaint method we only do something if something has changed:

<span class="cs-keyword">if</span> (clr != clrLast)
{
  clrLast = clr;

 

And, to previously discussed concepts we add DrawString

e.Graphics.DrawString(<span class="cpp-string">"\nRed: "</span> + 
clr.R.ToString(<span class="cpp-string">"X00"</span>) + 
<span class="cpp-string">" - "</span> + 
clr.R.ToString() +

…More strings… );

LightSpectrum

To the concepts we’ve looked at so far, LightSpectrum, Figure 5, elaborates on the gradient brush to simulate a full spectrum of visible light. I reuse this code in several subsequent demonstrations, so in a real-world coding situation (this is an unreal-world) I’d put this stuff in its own class. This, as is, really has nothing to do with transparency, but I use it later as a backcolor for transparency demos.

In the OnPaint method we add a new LinearGradientBrush with some dummy colors.

LinearGradientBrush brBrush = 
  <span class="cs-keyword">new</span> LinearGradientBrush(
  rect, Color.Blue, Color.Red, 
  LinearGradientMode.Horizontal);

Then we create a color array for the gradient. This array is based on the assumption that the values used will give a good simulation, and to my eye it does.

Color[] clrArray =
{
  Color.FromArgb(<span class="cs-literal">255</span>,<span class="cs-literal">0</span>,<span class="cs-literal">0</span>,<span class="cs-literal">0</span>),
  Color.FromArgb(<span class="cs-literal">255</span>,<span class="cs-literal">128</span>,<span class="cs-literal">0</span>,<span class="cs-literal">128</span>),
  Color.FromArgb(<span class="cs-literal">255</span>,<span class="cs-literal">255</span>,<span class="cs-literal">0</span>,<span class="cs-literal">255</span>),
  Color.FromArgb(<span class="cs-literal">255</span>,<span class="cs-literal">128</span>,<span class="cs-literal">0</span>,<span class="cs-literal">255</span>),
  Color.FromArgb(<span class="cs-literal">255</span>,<span class="cs-literal">0</span>,<span class="cs-literal">0</span>,<span class="cs-literal">255</span>),
  Color.FromArgb(<span class="cs-literal">255</span>,<span class="cs-literal">0</span>,<span class="cs-literal">128</span>,<span class="cs-literal">255</span>),
  Color.FromArgb(<span class="cs-literal">255</span>,<span class="cs-literal">0</span>,<span class="cs-literal">255</span>,<span class="cs-literal">255</span>),
  Color.FromArgb(<span class="cs-literal">255</span>,<span class="cs-literal">0</span>, <span class="cs-literal">255</span>,<span class="cs-literal">128</span>),
  Color.FromArgb(<span class="cs-literal">255</span>,<span class="cs-literal">0</span>,<span class="cs-literal">255</span>,<span class="cs-literal">0</span>),
  Color.FromArgb(<span class="cs-literal">255</span>,<span class="cs-literal">128</span>,<span class="cs-literal">255</span>,<span class="cs-literal">0</span>),
  Color.FromArgb(<span class="cs-literal">255</span>,<span class="cs-literal">255</span>,<span class="cs-literal">255</span>,<span class="cs-literal">0</span>),
  Color.FromArgb(<span class="cs-literal">255</span>,<span class="cs-literal">255</span>,<span class="cs-literal">128</span>,<span class="cs-literal">0</span>),
  Color.FromArgb(<span class="cs-literal">255</span>,<span class="cs-literal">255</span>,<span class="cs-literal">0</span>,<span class="cs-literal">0</span>),
  Color.FromArgb(<span class="cs-literal">255</span>,<span class="cs-literal">128</span>,<span class="cs-literal">0</span>,<span class="cs-literal">0</span>),
  Color.FromArgb(<span class="cs-literal">255</span>,<span class="cs-literal">0</span>,<span class="cs-literal">0</span>,<span class="cs-literal">0</span>)  
};

As with the color array, a points array is created with values that we assume will give use a good continuum in our simulation.

<span class="cs-keyword">float</span>[] posArray =
{
  <span class="cs-literal">0</span>.0f,
  <span class="cs-literal">1</span>.0f/<span class="cs-literal">14</span>.0f,
  <span class="cs-literal">2</span>.0f/<span class="cs-literal">14</span>.0f,
  <span class="cs-literal">3</span>.0f/<span class="cs-literal">14</span>.0f,
  <span class="cs-literal">4</span>.0f/<span class="cs-literal">14</span>.0f,
  <span class="cs-literal">5</span>.0f/<span class="cs-literal">14</span>.0f,
  <span class="cs-literal">6</span>.0f/<span class="cs-literal">14</span>.0f,
  <span class="cs-literal">7</span>.0f/<span class="cs-literal">14</span>.0f,
  <span class="cs-literal">8</span>.0f/<span class="cs-literal">14</span>.0f,
  <span class="cs-literal">9</span>.0f/<span class="cs-literal">14</span>.0f,
  <span class="cs-literal">10</span>.0f/<span class="cs-literal">14</span>.0f,
  <span class="cs-literal">11</span>.0f/<span class="cs-literal">14</span>.0f,
  <span class="cs-literal">12</span>.0f/<span class="cs-literal">14</span>.0f,
  <span class="cs-literal">13</span>.0f/<span class="cs-literal">14</span>.0f,
  <span class="cs-literal">1</span>.0f
};

Next we create an instance of the ColorBlend class, which defines color and position arrays used for interpolating color blending in a multicolor gradient

ColorBlend colorBlend = <span class="cs-keyword">new</span> ColorBlend();

We then set the properties.

colorBlend.Colors = clrArray;
colorBlend.Positions = posArray;

And next we set the LinearGradientBrush InterpolationColors property to our ColorBlend.

<span class="cs-comment">// Set interpolationColors property</span>
brBrush.InterpolationColors = colorBlend;

AlphaDemonstrator

I built AlphaDemonstrator, Figure 6, to show more variations on alpha blending as it actually works and contrary to my expectations. No new code concepts are added, so no extra discussion is given.

ColorDemo

I wrote ColoDemo to provide a simulation of how I thought color and transparency should be simulated for the ‘real world’. That is, what do we need to do to get the effect of mixing paints or projecting colored lights?

I hacked around a bit and came up with the following function:

<span class="cs-keyword">private</span> Bitmap trueColorMix(Bitmap bitmap1, Bitmap bitmap2, 
  <span class="cs-keyword">int</span> X, <span class="cs-keyword">int</span> Y, <span class="cs-keyword">byte</span> alpha)
{
  Color clrPixel1;
  Color clrPixel2;
  <span class="cs-keyword">int</span> redMix,greenMix,blueMix;

  <span class="cs-keyword">for</span>(<span class="cs-keyword">int</span> i = <span class="cs-literal">0</span>; i < bitmap2.Width; i++)
  {
    <span class="cs-keyword">for</span>(<span class="cs-keyword">int</span> j = <span class="cs-literal">0</span>; j < bitmap2.Height; j++)
    {  
      clrPixel1 = bitmap1.GetPixel(i+X,j+Y);
      clrPixel2 = bitmap2.GetPixel(i,j);
      redMix = ((<span class="cs-keyword">int</span>)clrPixel1.R + (<span class="cs-keyword">int</span>)clrPixel2.R);
      <span class="cs-keyword">if</span>(redMix > <span class="cs-literal">255</span>) redMix = <span class="cs-literal">255</span>;
      greenMix = ((<span class="cs-keyword">int</span>)clrPixel1.G + (<span class="cs-keyword">int</span>)clrPixel2.G);
      <span class="cs-keyword">if</span>(greenMix > <span class="cs-literal">255</span>) greenMix = <span class="cs-literal">255</span>;
      blueMix = ((<span class="cs-keyword">int</span>)clrPixel1.B + (<span class="cs-keyword">int</span>)clrPixel2.B);
      <span class="cs-keyword">if</span>(blueMix > <span class="cs-literal">255</span>) blueMix = <span class="cs-literal">255</span>;
      bitmap1.SetPixel(i+X,
          j+Y,
          Color.FromArgb(alpha,
          (<span class="cs-keyword">byte</span>)redMix,
          (<span class="cs-keyword">byte</span>)greenMix,  
          (<span class="cs-keyword">byte</span>)blueMix));
    }
  }

  <span class="cs-keyword">return</span> bitmap1;
}

This function receives the background bitmap1, the source bitmap2, the source X and Y locations and alpha for the blend. It iterates through each pixel of bitmap2 that overlays bitmap1, adds each pixel together, limiting the maximum value to 255, then resets the bitmap1 pixel to the new red, green, and blue values and sets alpha to the given alpha.

This provides a good simulation of additive color over a black background.

But it doesn’t work over white since, for white, bitmap1 starts out with all color values already at 255. Putting color over white, subtractive color, is what printers do, using Cyan, Magenta and Yellow as their primary colors. They also use black, since they can’t get a good black mixing the colors, calling their system CYMK, where K is the black. In the Color Demo code, all that’s needed to demo subtractive color is to start with a white background and subtract the pixels in bitmap2.

In the next tutorial, we’ll begin with images by looking at the CompositingMode Enumeration and the ColorMatrix, and ImageAttributes classes for making color changes to entire images.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here