2015-07-28 2 views
3

Я хочу создать экземпляр«вручную», который содержит анимацию.Создание анимированного изображения растрового изображения программно

Bitmap экземпляр для создания должен соответствовать следующим критериям:

  • Это анимация (image.FrameDimensionsLists имеет размерность времени)
  • Она имеет несколько кадров (image.GetFrameCount(dimension) > 1)
  • я могу получить задержку между кадрами (image.GetPropertyItem(0x5100).Value)

Я уверен, что можно создать такое изображение через некоторый Wi Näpi. Именно это делает GIF-декодер.

Я знаю, что могу воспроизвести анимацию, если у меня есть кадры из любого источника, сделав это вручную, но я хочу сделать это совместимым способом: если бы я мог создать такой растровый рисунок, я мог бы просто использовать его на a Button, Label, PictureBox или любой другой существующий элемент управления, а встроенный ImageAnimator также может обрабатывать его автоматически.

Большинство подобных тем предлагают преобразовать кадры в анимированный GIF; однако это нехорошее решение, поскольку оно не поддерживает истинный цвет и полупрозрачность (например, анимация APNG).

Обновление: После некоторого изучения я узнал, что могу реализовать декодер, используя WIC; однако я не хочу регистрировать новый декодер в Windows, и он использует COM, который я хочу избежать, если это возможно. Не упоминая, что в конце у меня будет IWICBitmapSource, который мне еще нужно преобразовать в Bitmap.

Обновление 2: Я установил щедрость. Вы победитель, если вы можете реализовать следующий метод:

public void Bitmap CreateAnimation(Bitmap[] frames, int[] delays) 
{ 
    // Any WinApi is allowed. WIC is also allowed, but not preferred. 
    // Creating an animated GIF is not an acceptable answer. What if frames are from an APNG? 
} 

ответ

3
public void Bitmap CreateAnimation(Bitmap[] frames, int[] delays) 

Установка жестких ограничений на ожидаемой реализации, как это не очень мудрым. Это технически возможно, используя формат изображения TIFF, он способен хранить несколько кадров. Однако они не основаны на времени, но поддерживает только кодек GIF. Требуется один дополнительный аргумент, поэтому элемент управления может быть обновлен, когда нужно будет отобразить следующее изображение. Как это:

public static Image CreateAnimation(Control ctl, Image[] frames, int[] delays) { 
     var ms = new System.IO.MemoryStream(); 
     var codec = ImageCodecInfo.GetImageEncoders().First(i => i.MimeType == "image/tiff"); 

     EncoderParameters encoderParameters = new EncoderParameters(2); 
     encoderParameters.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.SaveFlag, (long)EncoderValue.MultiFrame); 
     encoderParameters.Param[1] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, (long)EncoderValue.CompressionLZW); 
     frames[0].Save(ms, codec, encoderParameters); 

     encoderParameters = new EncoderParameters(1); 
     encoderParameters.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.SaveFlag, (long)EncoderValue.FrameDimensionPage); 
     for (int i = 1; i < frames.Length; i++) { 
      frames[0].SaveAdd(frames[i], encoderParameters); 
     } 
     encoderParameters.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.SaveFlag, (long)EncoderValue.Flush); 
     frames[0].SaveAdd(encoderParameters); 

     ms.Position = 0; 
     var img = Image.FromStream(ms); 
     Animate(ctl, img, delays); 
     return img; 
    } 

Метод Animate() нужен таймер для выбора следующего кадра и получить контроль над обновление: использование

private static void Animate(Control ctl, Image img, int[] delays) { 
     int frame = 0; 
     var tmr = new Timer() { Interval = delays[0], Enabled = true }; 
     tmr.Tick += delegate { 
      frame++; 
      if (frame >= delays.Length) frame = 0; 
      img.SelectActiveFrame(FrameDimension.Page, frame); 
      tmr.Interval = delays[frame]; 
      ctl.Invalidate(); 
     }; 
     ctl.Disposed += delegate { tmr.Dispose(); }; 
    } 

Пример:

public Form1() { 
     InitializeComponent(); 
     pictureBox1.Image = CreateAnimation(pictureBox1, 
      new Image[] { Properties.Resources.Frame1, Properties.Resources.Frame2, Properties.Resources.Frame3 }, 
      new int[] { 1000, 2000, 300 }); 
    } 

Умнее способ попробуйте полностью отказаться от требования к возвращаемому значению, поэтому вам не нужно создавать TIFF. И просто используйте метод Animate() с аргументом Action<Image>, чтобы обновить свойство элемента управления. Но не то, что вы просили.

+0

Благодарим вас за ответ. Как я уже сказал, я могу программно имитировать любую анимацию, и это то, что я делаю, например, когда я извлекаю кадры из анимации APNG. Тем не менее, GIF-декодер способен создать один битмап, который содержит всю необходимую информацию для анимации изображения, и я также хочу как-то добиться этого. Здесь вы делаете многостраничное изображение TIFF с помощью TIFF-кодировщика (и декодера при загрузке обратно из потока), что приводит к тому, что экземпляр Bitmap имеет размер страницы вместо Time. Таким образом, встроенный ImageAnimator не будет воспроизводить анимацию на кнопке, например. – taffer

+0

Ну, да, это все совершенно очевидно. Ни один из кодировщиков изображений Windows не поддерживает измерение времени, это означает, что доллар останавливается. Вам нужен этот метод Animate(). Хорошо работает, я рекомендую вам его использовать. –

+0

Спасибо, я уже внедрил подобный аниматор. Но ключ - это совместимость здесь (и любопытство, конечно). Я знаю, что среди встроенных декодеров GIF-декодер является единственным, который использует эту функцию Bitmap и создает битмап с критериями, указанными в вопросе. Я мог бы переформулировать вопрос следующим образом: как это делает GIF-декодер и как я могу достичь того же результата независимо от исходного формата? – taffer

1

К сожалению, мы можем расширить ни System.Darwing.Image, ни System.Darwing.Bitmap так главенствующими image.FrameDimensionsLists и подобные члены не может быть и речи, и, как Hans Passant упоминалось, ни один из датчиков изображения Windows, не поддерживают измерение времени само по себе. Однако я считаю, в данном конкретном случае на основе

Я знаю, что я могу играть анимацию, если у меня есть кадры из любого источника, делая это вручную, но я хочу, чтобы это сделать совместимым способом: Если Я мог бы создать такой Bitmap, я мог бы просто использовать его на кнопке, Ярлык, PictureBox или любой другой существующий элемент управления, а встроенный ImageAnimator также мог бы обрабатывать его автоматически.

Мы можем уйти от реализации нового класса, который может обрабатывать анимация, а затем неявно брось в Bitmap, мы можем использовать методы расширения для автоматического включения анимации для управления, я знаю, что этот метод является Hacky, но я, хотя, возможно, это стоит упомянуть об этом.

здесь грубое внедрение и использование образца

AnimatedBitmap: ручки кадров и анимации на основе базового времени на предоставленных последовательностей:

public class Sequence 
    { 
     public Image Image { get; set; } 
     public int Delay { get; set; } 
    } 

    public class AnimatedBitmap:IDisposable 
    { 
     private readonly Bitmap _buffer; 
     private readonly Graphics _g; 
     private readonly Sequence[] _sequences; 
     private readonly CancellationTokenSource _cancelToken; 

     public event EventHandler FrameUpdated; 

     protected void OnFrameUpdated() 
     { 
      if (FrameUpdated != null) 
       FrameUpdated(this, EventArgs.Empty); 
     } 

     public AnimatedBitmap(int width, int height, params Sequence[] sequences) 
     { 
      _buffer = new Bitmap(width, height, PixelFormat.Format32bppArgb) {Tag = this}; 

      _sequences = sequences; 
      _g=Graphics.FromImage(_buffer); 
      _g.CompositingMode=CompositingMode.SourceCopy; 

      _cancelToken = new CancellationTokenSource(); 
      Task.Factory.StartNew(Animate 
       , TaskCreationOptions.LongRunning 
       , _cancelToken.Token); 
     } 

     private void Animate(object obj) 
     { 
      while (!_cancelToken.IsCancellationRequested) 
       foreach (var sequence in _sequences) 
       { 
        if (_cancelToken.IsCancellationRequested) 
         break; 

        _g.Clear(Color.Transparent); 
        _g.DrawImageUnscaled(sequence.Image,0,0); 
        _g.Flush(FlushIntention.Flush); 
        OnFrameUpdated(); 
        Thread.Sleep(sequence.Delay); 
       } 

      _g.Dispose(); 
      _buffer.Dispose(); 
     } 

     public AnimatedBitmap(params Sequence[] sequences) 
      : this(sequences.Max(s => s.Image.Width), sequences.Max(s => s.Image.Height), sequences) 
     { 
     } 

     public void Dispose() 
     { 
      _cancelToken.Cancel(); 
     } 

     public static implicit operator Bitmap(AnimatedBitmap animatedBitmap) 
     { 
      return animatedBitmap._buffer; 
     } 

     public static explicit operator AnimatedBitmap(Bitmap bitmap) 
     { 
      var tag = bitmap.Tag as AnimatedBitmap; 
      if (tag != null) 
       return tag; 

      throw new InvalidCastException(); 
     } 

     public static AnimatedBitmap CreateAnimation(Image[] frames, int[] delays) 
     { 
      var sequences = frames.Select((t, i) => new Sequence {Image = t, Delay = delays[i]}).ToArray(); 
      var animated=new AnimatedBitmap(sequences); 
      return animated; 
     } 
    } 

AnimationController: ручки обновления управления анимацией

public static class AnimationController 
{ 
    private static readonly List<Control> Controls =new List<Control>(); 
    private static CancellationTokenSource _cancelToken; 

    static AnimationController() 
    { 
     _cancelToken = new CancellationTokenSource(); 
     _cancelToken.Cancel(); 
    } 

    private static void Animate(object arg) 
    { 
     while (!_cancelToken.IsCancellationRequested) 
     { 
      Controls.RemoveAll(c => !(c.BackgroundImage.Tag is AnimatedBitmap)); 

      foreach (var c in Controls) 
      { 
       var control = c; 
       if (!control.Disposing) 
        control.Invoke(new Action(() => control.Refresh())); 
      } 

      Thread.Sleep(40); 
     } 
    } 

    public static void StartAnimation(this Control control) 
    { 
     if (_cancelToken.IsCancellationRequested) 
     { 
      _cancelToken = new CancellationTokenSource(); 
      Task.Factory.StartNew(Animate 
       , TaskCreationOptions.LongRunning 
       , _cancelToken.Token); 
     } 

     Controls.Add(control); 
     control.Disposed += Disposed; 
    } 

    private static void Disposed(object sender, EventArgs e) 
    { 
     (sender as Control).StopAnimation(); 
    } 

    public static void StopAnimation(this Control control) 
    { 
     Controls.Remove(control); 
     if(Controls.Count==0) 
      _cancelToken.Cancel(); 
    } 

    public static void SetAnimatedBackground(this Control control, AnimatedBitmap bitmap) 
    { 
     control.BackgroundImage = bitmap; 
     control.StartAnimation(); 
    } 
} 

и здесь образец Использование:

public Form1() 
    { 
     InitializeComponent(); 

     var frame1 = Image.FromFile(@"1.png"); 
     var frame2 = Image.FromFile(@"2.png"); 

     var animatedBitmap= new AnimatedBitmap(
      new Sequence {Image = frame1, Delay = 33}, 
      new Sequence {Image = frame2, Delay = 33} 
      ); 

     // or we can do 
     //animatedBitmap = AnimatedBitmap.CreateAnimation(new[] {frame1, frame2}, new[] {1000, 2000}); 

     pictureBox1.SetAnimatedBackground(animatedBitmap); 
     button1.SetAnimatedBackground(animatedBitmap); 
     label1.SetAnimatedBackground(animatedBitmap); 
     checkBox1.SetAnimatedBackground(animatedBitmap); 

     //or we can do 
     //pictureBox1.BackgroundImage = animatedBitmap; 
     //pictureBox1.StartAnimation(); 
    } 
0

Там вы идете (короткий ответ :-):

public void Bitmap CreateAnimation(Bitmap[] frames, int[] delays) 
{ 
    throw new NotSupportedException(); 
} 

Серьезно, вы можете безопасно удалить Баунти прямо сейчас, потому что нет никакого решения с ограничениями, которые вы установили. Теоретически вы можете реализовать пользовательский WIC-кодек, но для его использования требуется регистрация COM (я даже не уверен, что он будет использоваться GDI +, например, хотя на основе WIC WPF привязан к встроенным кодекам) представит проблемы с развертыванием и просто не стоит. Странно, что System.Drawing.Image не имеет виртуальных методов, не может быть унаследован и ImageAnimator связан с ним жестким кодом, но это так. Вы должны жить с анимированной поддержкой Gif «из коробки» или использовать свое собственное решение :-).
насчет любопытства, я думаю, что вы только начали с неправильным предположением

Я довольно уверен, можно создать такой образ с помощью некоторого WinApi. Именно это делает GIF-декодер.

, а затем в комментариях

как делает GIF декодер это сделать, и как я могу добиться того же результата, независимо от исходного формата

Это не делает. Ключевым словом здесь является декодер. API-интерфейсы (или управляемые API-оболочки :-)) называли бы это при предоставлении услуг «изображения» для вас.Например, метод IWICBitmapDecoder::GetFrameCount, скорее всего, используется GdipImageGetFrameCount (или Image.GetFrameCount, если хотите). В общем, вы можете добавлять фреймы и параметры только для кодеров, и декодеры являются единственными, которые могут вернуть такую ​​информацию вызывающему.

Смежные вопросы