How to apply a fade transition effect to Images using a Timer?

There are some problem to fix here:

fadeTimer.Interval = 10;:
You’re (possibly) using the wrong Timer: the System.Timers.Timer‘s Elapsed is raised in a ThreadPool Thread. Unless you have set the SynchronizingObject to a Control object, which is then used to marshal the handler calls, referencing a Control in the handler will cause trouble (cross-thread violation exceptions).
In this context, you can use a System.Windows.Forms.Timer instead: its Tick event is raised in the UI thread.
Also, the timer interval is to low. The standard (official) resolution of the System.Windows.Forms.Timer is 55ms (higher than the System.Timers.Timer). You end up with overlapping events.

GetPixel() / SetPixel():
cannot be used for this task. These methods are too slow when called sequentially to set multiple pixels. They’re used to modify a small set of pixels (or just one), not to modify the pixels of a whole image.
Bitmap.LockBits() is the common tool used to set the color bytes of Bitmaps.

pictureOut.Image = changeOpacity((Bitmap)pictureOut.Image.Clone(), oChange);:
You’re using the Image property of a Control to provide the source Bitmap, then you’re setting the same property that provided the source using the same source, modified.
This will never give you fully faded Image and you’re looking for trouble.

There’s a simple tool that can be used to accomplish this task quite easily: the ColorMatrix class. This class handles a standard 5×5 Matrix, providing some simplified tools that allow to set the values of the Matrix components.
The 5×5 Matrix component at [3, 3] (Matrix3x3) represents the Alpha value of all the RGB components.
The ColorMatrix class is applied to a Bitmap using the SetColorMatrix() method of the ImageAttributes class, which is then passed to the Graphics.DrawImage() overload that accepts an ImageAttributes object as argument.

Since this Fade procedure could be useful in other situations, I think it’s a good idea to create an Extension Method: it adds a new SetOpacity() method to the Bitmap class.
It can also change the Gamma at the same time, should the fade effect require it.

What’s left is to load two Bitmaps, create a Timer, set a sensible Interval (100ms here) and paint the Bitmap on the surface of two PictureBox Controls (three here, to test a simple blending effect ).

The Opacity increment/decrement value is set to .025f, so the opacity changes 1/4 of the 0.0f-1.0f max range each second. To adjust as required.


Bitmap Fade ColorMatrix
The quality is reduced since the GIF animation can only use 256 colors

▶ Add the extension class to the Project.
▶ Setup a Form with 3 PictureBox Controls and assign the 3 event handles you find here to each of them.
▶ Don’t assign a Bitmap to the PictureBoxes at design-time. The Bitmaps are loaded at run-time, as shown in the sample code.
▶ Add a Button to start the Timer.
▶ when the fading procedure terminates, you restart the Timer immediately, since it rewinds itself (the fading starts over, applying the inverse effect to each Bitmap and to the blended Bitmaps).

Bitmap Fade and blend ColorMatrix

Keep the Diagnostics Tools open: you’ll see that you don’t waste a single MB of memory, even if you repeat the operation multiple times.

using System.Drawing;
using System.IO;
using System.Windows.Forms;

public partial class FormBitmaFadeTest : Form
{
    Bitmap sourceBmp1 = null;
    Bitmap sourceBmp2 = null;
    Bitmap fadeBmp1 = null;
    Bitmap fadeBmp2 = null;

    float opacity1 = 0.0f;
    float opacity2 = 1.0f;
    float increment = .025f;
    Timer timer = null;

    public FormBitmaFadeTest()
    {
        InitializeComponent();

        if (components == null) components = new System.ComponentModel.Container();
        components.Add(timer);

        string image1Path = [Source Image 1 Path];
        string image2Path = [Source Image 2 Path];

        sourceBmp1 = (Bitmap)Image.FromStream(new MemoryStream(File.ReadAllBytes(image1Path)));
        sourceBmp2 = (Bitmap)Image.FromStream(new MemoryStream(File.ReadAllBytes(image2Path)));
        fadeBmp1 = sourceBmp1.Clone() as Bitmap;
        fadeBmp2 = sourceBmp2.Clone() as Bitmap;
        timer = new Timer() { Interval = 100 };
        timer.Tick += this.TimerTick;
    }

    private void TimerTick(object sender, EventArgs e)
    {
        opacity1 += increment;
        opacity2 -= increment;
        if ((opacity1 >= 1.0f || opacity1 <= .0f) || (opacity2 >= 1.0f || opacity2 <= .0f)) {
            increment *= -1;
            timer.Stop();
        }
        fadeBmp1?.Dispose();
        fadeBmp2?.Dispose();
        fadeBmp1 = sourceBmp1.SetOpacity(opacity1);
        fadeBmp2 = sourceBmp2.SetOpacity(opacity2);
        pictureBox1.Invalidate();
        pictureBox2.Invalidate();
        pictureBox3.Invalidate();
    }

    private void pictureBox1_Paint(object sender, PaintEventArgs e)
    {
        if (fadeBmp1 == null) return;
        var units = GraphicsUnit.Pixel;
        e.Graphics.DrawImage(fadeBmp1, new RectangleF(PointF.Empty, pictureBox1.ClientSize), fadeBmp1.GetBounds(ref units), units);
    }

    private void pictureBox2_Paint(object sender, PaintEventArgs e)
    {
        if (fadeBmp2 == null) return;
        var units = GraphicsUnit.Pixel;
        e.Graphics.DrawImage(fadeBmp2, new RectangleF(PointF.Empty, pictureBox2.ClientSize), fadeBmp2.GetBounds(ref units), units);
    }

    private void pictureBox3_Paint(object sender, PaintEventArgs e)
    {
        if (fadeBmp1 == null || fadeBmp2 == null) return;
        var units = GraphicsUnit.Pixel;
        e.Graphics.DrawImage(fadeBmp2, new RectangleF(PointF.Empty, pictureBox3.ClientSize), fadeBmp2.GetBounds(ref units), units);
        e.Graphics.DrawImage(fadeBmp1, new RectangleF(PointF.Empty, pictureBox3.ClientSize), fadeBmp1.GetBounds(ref units), units);
    }
}

The extension method:
Extension Methods (C# Programming Guide)

using System.Drawing;
using System.Drawing.Imaging;

public static class BitmapExtensions
{
    static float[][] fadeMatrix = {
        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[] {0, 0, 0, 0, 1}
    };

    public static Bitmap SetOpacity(this Bitmap bitmap, float Opacity, float Gamma = 1.0f)
    {
        var mx = new ColorMatrix(fadeMatrix);
        mx.Matrix33 = Opacity;
        var bmp = new Bitmap(bitmap.Width, bitmap.Height);

        using (var g = Graphics.FromImage(bmp))
        using (var attributes = new ImageAttributes()) {
            attributes.SetGamma(Gamma, ColorAdjustType.Bitmap);
            attributes.SetColorMatrix(mx, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);
            g.Clear(Color.Transparent);
            g.DrawImage(bitmap, new Rectangle(0, 0, bmp.Width, bmp.Height),
                0, 0, bitmap.Width, bitmap.Height, GraphicsUnit.Pixel, attributes);
            return bmp;
        }
    }
}

Leave a Comment