18

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.

How to load a progressive JPG in WPF?

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.

Chinmoy
  • 1,750
  • 2
  • 21
  • 45

2 Answers2

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.

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;
}
TFD
  • 23,890
  • 2
  • 34
  • 51
Ben
  • 1,023
  • 9
  • 10
  • @TFD I suggest you google Progressive JPeG. There's a lot of resources on what it is.. but not really how to handle them.. especially in .net.. writing one in .net is not a trivial task. – Mel May 26 '11 at 16:20
  • 4
    While i really skipped the Progressive thing, i will try to defend myself a little: The base problem was that the image shows up only when the download completes, and the OP want to show parts of the image while donwloading it. My sample code does it. Its a solution to a problem, and an answer to a desire that you can read above: `I want to show it progressively.`. And while there are no answer to the _question_, the problem is solved: the end user can see some progress of the downloading image. Maybe i can't deserve that the OP accept my answer, but i don't deserve that -1 either. – Ben May 26 '11 at 17:47
  • @BoltClock I know what a progressive JPG is. What progress bar? This sample code shows the image and a percent complete counter using "Progress: {0}". The image displays given the partial stream available. That's what the OP wanted? The only mistake was not to link to a sample progressive JPG. Change the URL to a large progressive and you can see it work fine. Please don't -1 posts that are technically correct – TFD May 26 '11 at 23:30
  • My fault for misreading the code and being unwarrantingly overbearing. And yeah, I should have tested it myself. I'm sorry for that. I have no downvote to revoke, but here's an upvote for actually being correct. (Also removed my previous comments.) – BoltClock May 26 '11 at 23:35
  • @Ben You are out of the red now, hope you get to enjoy the bounty! – TFD May 27 '11 at 00:04
  • @r3st0r3 You should up your bounty to 100 and give it to @ben for not actually trying his sample code he so kindly offered :-) Please also delete comment – TFD May 27 '11 at 00:07
  • Hey, I tried the code and it works, not in the way I wanted it to but it does work. Great effort. I have an answer. Thanks @Ben, @BoltClock and @TDF. – Chinmoy May 27 '11 at 20:36
  • @r3st0r3 you could parse the stream as well, and look for the frame tokens, and only update the Image control when a new complete frame has become available (IIRC progressive DCT SOF = 0xFFCA or 0xFFCE) – TFD May 28 '11 at 05:30
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.

Patrick Szalapski
  • 8,738
  • 11
  • 67
  • 129
  • @Patrick_Szalapski I don't understand where your comment is coming from. WPF Image fully support progressive jpg. There is not shortcomming. It just works. See @ben 's answer – TFD May 27 '11 at 00:10
  • 1
    @TFD That's still re-rendering the partial image from scratch every 5%. Full support would be rendering it in one go as it loads. – Rup May 27 '11 at 10:44
  • Right--I was thinking that a custom control that has all of @Ben's code baked in to the control. If you take care of that code externally, then certainly you don't need a special control. – Patrick Szalapski May 31 '11 at 22:35