Как применить эффект плавного перехода к изображениям PictureBox с помощью таймера? - PullRequest
1 голос
/ 18 апреля 2020

Я пытаюсь сделать плавный переход между двумя элементами управления PictureBox.
Я использовал таймер для изменения непрозрачности двух PictureBox, используя GetPixel и SetPixel каждый раз, когда истекает время.

На этом этапе проблема заключается в том, что этот код вызывает исключение:

System.InvalidOperationException: объект в настоящее время используется в другом месте

Я пытался изменить клонированные растровые изображения вместо непосредственной работы с растровыми изображениями, установленными в свойствах изображения элементов управления, но это все равно не работает.
Вот мой код:

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

1 Ответ

2 голосов
/ 18 апреля 2020

Здесь есть проблема, которую нужно исправить:

fadeTimer.Interval = 10;:
(возможно) вы используете неправильный таймер: System.Timers. Таймер Истек повышен в потоке ThreadPool. Если вы не установили SynchronizingObject для объекта Control, который затем используется для маршалинга вызовов обработчика, ссылка на Control в обработчике вызовет проблему (исключения нарушения между потоками). В этом контексте вы можете использовать System. Windows .Forms.Timer вместо этого: его событие Tick вызывается в потоке пользовательского интерфейса.
Кроме того, интервал таймера слишком мал. Стандартное (официальное) разрешение System.Windows.Forms.Timer составляет 55ms (выше, чем System.Timers.Timer). Вы заканчиваете перекрывающимися событиями.

GetPixel() / SetPixel():
не может использоваться для этой задачи. Эти методы слишком медленны при последовательном вызове для установки нескольких пикселей. Они используются для изменения небольшого набора пикселей (или только одного), а не для изменения пикселей всего изображения.
Bitmap.LockBits () - это общий инструмент, используемый для установки цвета байты растровых изображений.

pictureOut.Image = changeOpacity((Bitmap)pictureOut.Image.Clone(), oChange);:
Вы используете свойство Image элемента управления для предоставления растрового изображения источника, затем вы устанавливаете то же свойство, которое предоставляло источник, используя тот же источник, модифицированный.
Это никогда не даст вам полностью блеклое изображение, и вы ищете проблемы.

Существует простой инструмент, который можно легко выполнить для sh этой задачи: класс ColorMatrix . Этот класс обрабатывает стандартную матрицу 5x5, предоставляя некоторые упрощенные инструменты, позволяющие устанавливать значения компонентов Matrix.
Компонент Matrix 5x5 со значением [3, 3] (Matrix3x3) представляет значение Alpha: все компоненты RGB.
Класс ColorMatrix применяется к растровому изображению с помощью метода SetColorMatrix () класса ImageAttributes, который затем передается в перегрузка Graphics.DrawImage () , которая принимает объект ImageAttributes в качестве аргумента.

Поскольку эта процедура Fade может быть полезна в других ситуациях, я думаю, что это хорошая идея - создать метод расширения: он добавляет новый метод SetOpacity() к класс Bitmap.
Он также может одновременно изменять гамму, если этого требует эффект затухания.

Осталось загрузить две битовые карты, создать таймер, установить разумный Interval (100ms здесь) и нарисовать битовую карту на поверхности двух элементов управления PictureBox (три здесь , чтобы проверить простой эффект смешивания).

Значение увеличения / уменьшения непрозрачности установлено на .025f, поэтому непрозрачность меняется 1/4 максимального диапазона 0.0f-1.0f каждую секунду. Для настройки по мере необходимости.


Bitmap Fade ColorMatrix
Не очень хорошо с 256 цветами

► Добавить класс расширения в проект.
► Настройте форму с 3-мя элементами управления PictureBox и назначьте каждому из них 3 описателя событий, которые вы здесь найдете.
► Не назначайте растровое изображение для PictureBoxes во время разработки. Растровые изображения загружаются во время выполнения, как показано в примере кода.
► Добавьте кнопку для запуска таймера.
► после завершения процедуры fading вы немедленно перезапускаете таймер, поскольку он перематывает сам (затухание начинается заново, применяя обратный эффект к каждому растровому изображению и смешанным растровым изображениям).

Bitmap Fade and blend ColorMatrix

Держите инструменты диагностики открытыми: вы увидите, что не тратите ни одного МБ памяти, даже если вы повторите операцию несколько раз раз.

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

Метод расширения:
Методы расширения (C# Руководство по программированию)

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