如何使用计时器将淡入淡出过渡效果应用于图片框图像? [英] How to apply a fade transition effect to PictureBox Images using a Timer?

查看:18
本文介绍了如何使用计时器将淡入淡出过渡效果应用于图片框图像?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试在两个 PictureBox 控件之间进行淡入淡出过渡.
每次时间过去时,我都使用 Timer 使用 GetPixelSetPixel 更改两个图片框的不透明度.

现阶段问题是这段代码导致了异常:

<块引用>

System.InvalidOperationException:对象当前正在其他地方使用

我尝试修改克隆的Bitmaps,而不是直接操作设置为控件Image属性的Bitmaps,但无论如何都不起作用.
这是我的代码:

public Bitmap changeOpacity(Bitmap pic, int opacity){for (int w = 0; w CrossFadeEvent(sender, e, pictureOut, pictureIn, outChange, inChange, change);淡入淡出.开始();}//每个时间间隔都被调用private void CrossFadeEvent(对象源,System.Timers.ElapsedEventArgs e,PictureBox pictureOut,PictureBox pictureIn,int oChange,int iChange,int change){如果(iChange <= 255){o改变-=改变;iChange += 改变;textBox1.Text = iChange.ToString();pictureOut.Image = changeOpacity((Bitmap)pictureOut.Image.Clone(), oChange);pictureIn.Image = changeOpacity((Bitmap)pictureIn.Image.Clone(), iChange);}否则如果 (iChange > 255){pictureIn.Image = changeOpacity((Bitmap)pictureOut.Image.Clone(), 255);淡入淡出.停止();}}

解决方案

这里有一些问题需要解决:

fadeTimer.Interval = 10;:
您(可能)使用了错误的计时器:
由于GIF动画只能使用256色,质量下降

▶ 将扩展类添加到项目中.
▶ 设置一个包含 3 个图片框控件的表单,并将您在此处找到的 3 个事件句柄分配给每个控件.
▶ 不要在设计时为图片框分配位图.位图在运行时加载,如示例代码所示.
▶ 添加一个按钮来启动计时器.
▶ 当 fading 过程终止时,您立即重新启动 Timer,因为它倒带本身(淡入淡出重新开始,将相反的效果应用于每个位图和混合位图).

保持诊断工具打开:您会发现即使多次重复操作,也不会浪费一 MB 内存.

使用 System.Drawing;使用 System.IO;使用 System.Windows.Forms;公共部分类 FormBitmaFadeTest :表单{位图 sourceBmp1 = null;位图 sourceBmp2 = null;位图fadeBmp1 = null;位图fadeBmp2 = null;浮动不透明度1 = 0.0f;浮动不透明度2 = 1.0f;浮动增量 = .025f;定时器 timer = null;公共 FormBitmaFadeTest(){初始化组件();if (components == null) components = new System.ComponentModel.Container();组件.添加(定时器);字符串 image1Path = [源图像 1 路径];字符串 image2Path = [源图像 2 路径];sourceBmp1 = (Bitmap)Image.FromStream(new MemoryStream(File.ReadAllBytes(image1Path)));sourceBmp2 = (Bitmap)Image.FromStream(new MemoryStream(File.ReadAllBytes(image2Path)));FadeBmp1 = sourceBmp1.Clone() 作为位图;FadeBmp2 = sourceBmp2.Clone() 作为位图;定时器=新定时器(){间隔=100};timer.Tick += this.TimerTick;}private void TimerTick(object sender, EventArgs e){opacity1 += 增量;opacity2 -= 增量;if ((opacity1 >= 1.0f || opacity1 <= .0f) || (opacity2 >= 1.0f || opacity2 <= .0f)) {增量 *= -1;定时器.停止();}fadeBmp1?.Dispose();FadeBmp2?.Dispose();FadeBmp1 = sourceBmp1.SetOpacity(opacity1);FadeBmp2 = sourceBmp2.SetOpacity(opacity2);pictureBox1.Invalidate();pictureBox2.Invalidate();pictureBox3.Invalidate();}private void pictureBox1_Paint(对象发送者,PaintEventArgs e){如果(fadeBmp1 == null)返回;var 单位 = GraphicsUnit.Pixel;e.Graphics.DrawImage(fadeBmp1, new RectangleF(PointF.Empty,pictureBox1.ClientSize),fadeBmp1.GetBounds(ref units), units);}private void pictureBox2_Paint(对象发送者,PaintEventArgs e){如果(fadeBmp2 == null)返回;var 单位 = GraphicsUnit.Pixel;e.Graphics.DrawImage(fadeBmp2, new RectangleF(PointF.Empty,pictureBox2.ClientSize),fadeBmp2.GetBounds(ref units), units);}private void pictureBox3_Paint(对象发送者,PaintEventArgs e){if (fadeBmp1 == null || fakeBmp2 == null) 返回;var 单位 = 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);}}

扩展方法:
扩展方法(C# 编程指南))

使用 System.Drawing;使用 System.Drawing.Imaging;公共静态类 BitmapExtensions{静态浮点[][]fadeMatrix = {新浮点[] {1, 0, 0, 0, 0},新浮点[] {0, 1, 0, 0, 0},新浮点[] {0, 0, 1, 0, 0},新浮点[] {0, 0, 0, 1, 0},新浮点[] {0, 0, 0, 0, 1}};public static Bitmap SetOpacity(此位图位图,浮动不透明度,浮动Gamma = 1.0f){var mx = new ColorMatrix(fadeMatrix);mx.Matrix33 = 不透明度;var bmp = new Bitmap(bitmap.Width, bitmap.Height);使用 (var g = Graphics.FromImage(bmp))使用 (var 属性 = 新 ImageAttributes()) {attributes.SetGamma(Gamma, ColorAdjustType.Bitmap);attributes.SetColorMatrix(mx, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);g.Clear(颜色.透明);g.DrawImage(bitmap, new Rectangle(0, 0, bmp.Width, bmp.Height),0, 0, bitmap.Width, bitmap.Height, GraphicsUnit.Pixel, 属性);返回 bmp;}}}

I am trying to make fade transition between two PictureBox Controls.
I used a Timer to change the opacity of two PictureBoxes using GetPixel and SetPixel every time the time elapses.

At this stage, the problem is that this code causes an exception:

System.InvalidOperationException: object is currently in use elsewhere

I tried to modify cloned Bitmaps instead of directly operate on the Bitmaps set to the Image properties of the controls, but it doesn't work anyway.
Here is my code:

public Bitmap changeOpacity(Bitmap pic, int opacity)
{
    for (int w = 0; w < pic.Width; w++)
    {
        for (int h = 0; h < pic.Height; h++)
        {
            Color c = pic.GetPixel(w, h);
            Color newC = Color.FromArgb(opacity, c);
            pic.SetPixel(w, h, newC);
        }
    }
    return pic;
}

public void CrossFade(PictureBox pictureOut, PictureBox pictureIn, int duration)
{
    int outChange = 255; // opacity of pictureOut
    int inChange = 0;    // opacity of pictureIn
    int change = 55;     // change of opacity 
    fadeTimer.Interval = 10; // this timer's type is System.Timers.Timer
    Bitmap bmp = new Bitmap(pictureIn.Image);
    // make the pictureIn transparent first
    pictureIn.Image = changeOpacity((Bitmap)bmp.Clone(), 0);
    fadeTimer.Elapsed += (sender, e) => CrossFadeEvent(sender, e, pictureOut, pictureIn, outChange, inChange, change);
    fadeTimer.Start();
}

// being called every time interval
private void CrossFadeEvent(Object source, System.Timers.ElapsedEventArgs e, PictureBox pictureOut, PictureBox pictureIn, int oChange, int iChange, int change)
{
    if (iChange <= 255)
    {
        oChange -= change;
        iChange += change;
        textBox1.Text = iChange.ToString();
        pictureOut.Image = changeOpacity((Bitmap)pictureOut.Image.Clone(), oChange);
        pictureIn.Image = changeOpacity((Bitmap)pictureIn.Image.Clone(), iChange);
    }
    else if (iChange > 255)
    {
        pictureIn.Image = changeOpacity((Bitmap)pictureOut.Image.Clone(), 255);
        fadeTimer.Stop();
    }
}

解决方案

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 5x5 Matrix, providing some simplified tools that allow to set the values of the Matrix components.
The 5x5 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.



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).

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;
        }
    }
}

这篇关于如何使用计时器将淡入淡出过渡效果应用于图片框图像?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆