如何在WPF中显示渐进式JPEG?

时间:2022-04-24 21:23:31

How to display a progressive JPEG as it loads from a web URL? I am trying to display a Google Maps image in a image control in WPF, but I want to keep the advantage of the image being a progressive JPG.

如何在从Web URL加载时显示渐进式JPEG?我试图在WPF中的图像控件中显示Google地图图像,但我希望保持图像的优势是渐进式JPG。

How to load a progressive JPG in WPF?

如何在WPF中加载渐进式JPG?

Image imgMap;
BitmapImage mapLoader = new BitmapImage();

mapLoader.BeginInit();
mapLoader.UriSource = new Uri(URL);
mapLoader.EndInit();

imgMap.Source = mapLoader;

Currently, I make do with this. It will only shows the image after it loads completely. I want to show it progressively.

目前,我正在做这件事。它只会在完全加载后显示图像。我想逐步展示它。

2 个解决方案

#1


12  

A very basic sample. Im sure there are room for optimizations, and you can do a separate class from it that can handle numerous request, but at least its working, and you can shape it for your needs. Also note that this sample creates an image every time that we report a progress, you should avoid it! Do an image about every 5% or so to avoid a big overhead.

一个非常基本的样本。我确定有优化的空间,你可以从它做一个单独的类,可以处理大量的请求,但至少它的工作,你可以根据自己的需要塑造它。另请注意,每次我们报告进度时,此示例都会创建一个图像,您应该避免它!大约每5%左右做一次图像以避免大量开销。

Xaml:

XAML:

<Window x:Class="ScrollViewerTest.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="350" Width="525"
    DataContext="{Binding RelativeSource={RelativeSource Self}}">
  <StackPanel>
    <TextBlock Text="{Binding Path=Progress, StringFormat=Progress: {0}}" />
    <Image Source="{Binding Path=Image}" />
  </StackPanel>
</Window>

Code-behind:

代码隐藏:

public partial class MainWindow : Window, INotifyPropertyChanged
{

  #region Public Properties

  private int _progress;
  public int Progress
  {
    get { return _progress; }
    set
    {
      if (_progress != value)
      {
        _progress = value;

        if (PropertyChanged != null)
          PropertyChanged(this, new PropertyChangedEventArgs("Progress"));
      }
    }
  }

  private BitmapImage image;
  public BitmapImage Image
  {
    get { return image; }
    set
    {
      if (image != value)
      {
        image = value;
        if (PropertyChanged != null)
          PropertyChanged(this, new PropertyChangedEventArgs("Image"));
      }
    }
  }

  #endregion

  BackgroundWorker worker = new BackgroundWorker();

  public MainWindow()
  {
    InitializeComponent();

    worker.DoWork += backgroundWorker1_DoWork;
    worker.ProgressChanged += new ProgressChangedEventHandler(worker_ProgressChanged);
    worker.WorkerReportsProgress = true;
    worker.RunWorkerAsync(@"http://Tools.CentralShooters.co.nz/Images/ProgressiveSample1.jpg");
  }

  // This function is based on code from
  //   http://devtoolshed.com/content/c-download-file-progress-bar
  private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
  {
    // the URL to download the file from
    string sUrlToReadFileFrom = e.Argument as string;

    // first, we need to get the exact size (in bytes) of the file we are downloading
    Uri url = new Uri(sUrlToReadFileFrom);
    System.Net.HttpWebRequest request = (System.Net.HttpWebRequest)System.Net.WebRequest.Create(url);
    System.Net.HttpWebResponse response = (System.Net.HttpWebResponse)request.GetResponse();
    response.Close();
    // gets the size of the file in bytes
    Int64 iSize = response.ContentLength;

    // keeps track of the total bytes downloaded so we can update the progress bar
    Int64 iRunningByteTotal = 0;

    // use the webclient object to download the file
    using (System.Net.WebClient client = new System.Net.WebClient())
    {
      // open the file at the remote URL for reading
      using (System.IO.Stream streamRemote = client.OpenRead(new Uri(sUrlToReadFileFrom)))
      {
        using (Stream streamLocal = new MemoryStream((int)iSize))
        {
          // loop the stream and get the file into the byte buffer
          int iByteSize = 0;
          byte[] byteBuffer = new byte[iSize];
          while ((iByteSize = streamRemote.Read(byteBuffer, 0, byteBuffer.Length)) > 0)
          {
            // write the bytes to the file system at the file path specified
            streamLocal.Write(byteBuffer, 0, iByteSize);
            iRunningByteTotal += iByteSize;

            // calculate the progress out of a base "100"
            double dIndex = (double)(iRunningByteTotal);
            double dTotal = (double)byteBuffer.Length;
            double dProgressPercentage = (dIndex / dTotal);
            int iProgressPercentage = (int)(dProgressPercentage * 100);

            // update the progress bar, and we pass our MemoryStream, 
            //  so we can use it in the progress changed event handler
            worker.ReportProgress(iProgressPercentage, streamLocal);
          }

          // clean up the file stream
          streamLocal.Close();
        }

        // close the connection to the remote server
        streamRemote.Close();
      }
    }
  }

  void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
  {
    Dispatcher.BeginInvoke(
         System.Windows.Threading.DispatcherPriority.Normal,
         new Action(delegate()
         {
           MemoryStream stream = e.UserState as MemoryStream;

           BitmapImage bi = new BitmapImage();
           bi.BeginInit();
           bi.StreamSource = new MemoryStream(stream.ToArray());
           bi.EndInit();

           this.Progress = e.ProgressPercentage;
           this.Image = bi;
         }
       ));
  }

  public event PropertyChangedEventHandler PropertyChanged;
}

#2


1  

This does seem to be a shortcoming of the Image control. Maybe you could create a StreamImage that inherits from Image, takes a stream in the constructor, reads bytes in the background from the stream, figures out when it has enough, constructs an internal "fuzzy image" with the bytes read so far and renders that iteratively until it has all the bytes. You'd have to understand how bytes of a progressive JPEG are sent--I don't imagine it is simple.

这似乎是Image控件的缺点。也许你可以创建一个继承自Image的StreamImage,在构造函数中获取一个流,从流中读取背景中的字节,在它足够的时候计算出来,构造一个内部“模糊图像”,其中包含到目前为止读取的字节并呈现迭代地直到它具有所有字节。您必须了解渐进式JPEG的字节是如何发送的 - 我不认为这很简单。

#1


12  

A very basic sample. Im sure there are room for optimizations, and you can do a separate class from it that can handle numerous request, but at least its working, and you can shape it for your needs. Also note that this sample creates an image every time that we report a progress, you should avoid it! Do an image about every 5% or so to avoid a big overhead.

一个非常基本的样本。我确定有优化的空间,你可以从它做一个单独的类,可以处理大量的请求,但至少它的工作,你可以根据自己的需要塑造它。另请注意,每次我们报告进度时,此示例都会创建一个图像,您应该避免它!大约每5%左右做一次图像以避免大量开销。

Xaml:

XAML:

<Window x:Class="ScrollViewerTest.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="350" Width="525"
    DataContext="{Binding RelativeSource={RelativeSource Self}}">
  <StackPanel>
    <TextBlock Text="{Binding Path=Progress, StringFormat=Progress: {0}}" />
    <Image Source="{Binding Path=Image}" />
  </StackPanel>
</Window>

Code-behind:

代码隐藏:

public partial class MainWindow : Window, INotifyPropertyChanged
{

  #region Public Properties

  private int _progress;
  public int Progress
  {
    get { return _progress; }
    set
    {
      if (_progress != value)
      {
        _progress = value;

        if (PropertyChanged != null)
          PropertyChanged(this, new PropertyChangedEventArgs("Progress"));
      }
    }
  }

  private BitmapImage image;
  public BitmapImage Image
  {
    get { return image; }
    set
    {
      if (image != value)
      {
        image = value;
        if (PropertyChanged != null)
          PropertyChanged(this, new PropertyChangedEventArgs("Image"));
      }
    }
  }

  #endregion

  BackgroundWorker worker = new BackgroundWorker();

  public MainWindow()
  {
    InitializeComponent();

    worker.DoWork += backgroundWorker1_DoWork;
    worker.ProgressChanged += new ProgressChangedEventHandler(worker_ProgressChanged);
    worker.WorkerReportsProgress = true;
    worker.RunWorkerAsync(@"http://Tools.CentralShooters.co.nz/Images/ProgressiveSample1.jpg");
  }

  // This function is based on code from
  //   http://devtoolshed.com/content/c-download-file-progress-bar
  private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
  {
    // the URL to download the file from
    string sUrlToReadFileFrom = e.Argument as string;

    // first, we need to get the exact size (in bytes) of the file we are downloading
    Uri url = new Uri(sUrlToReadFileFrom);
    System.Net.HttpWebRequest request = (System.Net.HttpWebRequest)System.Net.WebRequest.Create(url);
    System.Net.HttpWebResponse response = (System.Net.HttpWebResponse)request.GetResponse();
    response.Close();
    // gets the size of the file in bytes
    Int64 iSize = response.ContentLength;

    // keeps track of the total bytes downloaded so we can update the progress bar
    Int64 iRunningByteTotal = 0;

    // use the webclient object to download the file
    using (System.Net.WebClient client = new System.Net.WebClient())
    {
      // open the file at the remote URL for reading
      using (System.IO.Stream streamRemote = client.OpenRead(new Uri(sUrlToReadFileFrom)))
      {
        using (Stream streamLocal = new MemoryStream((int)iSize))
        {
          // loop the stream and get the file into the byte buffer
          int iByteSize = 0;
          byte[] byteBuffer = new byte[iSize];
          while ((iByteSize = streamRemote.Read(byteBuffer, 0, byteBuffer.Length)) > 0)
          {
            // write the bytes to the file system at the file path specified
            streamLocal.Write(byteBuffer, 0, iByteSize);
            iRunningByteTotal += iByteSize;

            // calculate the progress out of a base "100"
            double dIndex = (double)(iRunningByteTotal);
            double dTotal = (double)byteBuffer.Length;
            double dProgressPercentage = (dIndex / dTotal);
            int iProgressPercentage = (int)(dProgressPercentage * 100);

            // update the progress bar, and we pass our MemoryStream, 
            //  so we can use it in the progress changed event handler
            worker.ReportProgress(iProgressPercentage, streamLocal);
          }

          // clean up the file stream
          streamLocal.Close();
        }

        // close the connection to the remote server
        streamRemote.Close();
      }
    }
  }

  void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
  {
    Dispatcher.BeginInvoke(
         System.Windows.Threading.DispatcherPriority.Normal,
         new Action(delegate()
         {
           MemoryStream stream = e.UserState as MemoryStream;

           BitmapImage bi = new BitmapImage();
           bi.BeginInit();
           bi.StreamSource = new MemoryStream(stream.ToArray());
           bi.EndInit();

           this.Progress = e.ProgressPercentage;
           this.Image = bi;
         }
       ));
  }

  public event PropertyChangedEventHandler PropertyChanged;
}

#2


1  

This does seem to be a shortcoming of the Image control. Maybe you could create a StreamImage that inherits from Image, takes a stream in the constructor, reads bytes in the background from the stream, figures out when it has enough, constructs an internal "fuzzy image" with the bytes read so far and renders that iteratively until it has all the bytes. You'd have to understand how bytes of a progressive JPEG are sent--I don't imagine it is simple.

这似乎是Image控件的缺点。也许你可以创建一个继承自Image的StreamImage,在构造函数中获取一个流,从流中读取背景中的字节,在它足够的时候计算出来,构造一个内部“模糊图像”,其中包含到目前为止读取的字节并呈现迭代地直到它具有所有字节。您必须了解渐进式JPEG的字节是如何发送的 - 我不认为这很简单。