2016-02-19 2 views
6

Я использую WinForms. В моих формах у меня открытая и следующая кнопка. Мое приложение открывает .tif изображений в картинке. Все изображения .tif, с которыми я работаю, имеют несколько страниц. Следующая кнопка предназначена для перехода на следующую страницу в изображении tif. Эти .tif изображений, с которыми я работаю, очень большие.Повысьте производительность при продвижении на следующую страницу. Использование изображений .tif

Пример: Размеры: 2600 х 3300 (.tif изображения)

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

Это код, который у меня есть до сих пор, но мое приложение немного отстает, когда я перехожу на следующую страницу. Не могли бы вы помочь мне?

Ниже приведена ссылка большого изображения tif с несколькими страницами для тестирования.

Ссылка

http://www.filedropper.com/tiftestingdoc

FileStream _stream; 
    Image _myImg; // setting the selected tiff 
    string _fileName; 


    private Image _Source = null; 
    private int _TotalPages = 0; 

    private int intCurrPage = 0; 

    private void Clone_File() 
    { // Reads file, then copys the file and loads it in the picture box as a temporary image doc. That way files are not locked in users directory when in use by this application. 
     try 
     { 

      if (_myImg == null) 
      { 

       try 
       { 
        _fileName = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N")); 
        File.Copy(@"C:\Picture_Doc\The_Image.tif", _fileName); 
        _stream = new FileStream(_fileName, FileMode.Open, FileAccess.Read); 
        this._Source = Image.FromStream(_stream); 
       } 
       catch (Exception ex) 
       { 
       } 
      } 
      _TotalPages = _Source.GetFrameCount(System.Drawing.Imaging.FrameDimension.Page); 

      intCurrPage = 1; 

      Display_Page(intCurrPage); 

     }catch(Exception ex) 
     { 
      MessageBox.Show(ex.Message); 
     } 

    } 

    private void Show_Processing_Image_Label() 
    { 
     Application.DoEvents(); 
    } 

    private void Display_Page(int PageNumber, RotateFlipType Change) 
    { 
     if (pictureBox1.Image != null && pictureBox1.Image != _Source) 
     { 
      //Release memory for old rotated image 
      pictureBox1.Image.Dispose(); 
     } 

     // set the variable to null for easy Garbage Collection cleanup 
     pictureBox1.Image = null; 

     _Source.SelectActiveFrame(System.Drawing.Imaging.FrameDimension.Page, PageNumber - 1); 

     pictureBox1.Image = new Bitmap(_Source); 

     pictureBox1.Image.RotateFlip(Change); 

     pictureBox1.Refresh(); 
     //Refresh() Calls Invalidate and then Update to refresh synchronously. 
    } 

    private void Display_Page(int PageNumber) 
    { 
     Show_Processing_Image_Label(); 

     //You could adjust the PictureBox size here for each frame OR adjust the image to fit the picturebox nicely 

     if (pictureBox1.Image != _Source) 
     { 
      if (pictureBox1.Image != null) 
      { 
       //Release memory for old copy and set the variable to null for easy GC cleanup 
       pictureBox1.Image.Dispose(); 
       pictureBox1.Image = null; 
      } 

      pictureBox1.Image = _Source; 
     } 

     pictureBox1.Image.SelectActiveFrame(System.Drawing.Imaging.FrameDimension.Page, PageNumber - 1); 

     pictureBox1.Refresh(); 

    } 

    private void Next_btn_Click(object sender, EventArgs e) 
    { 
     intCurrPage++; 
     Display_Page(intCurrPage); 
    } 

    private void Open_btn_Click(object sender, EventArgs e) 
    { 

      if (_stream != null) 
      { 
       _myImg = null; //dispose the copy image 
      } 

      if (openFileDialog1.ShowDialog() == DialogResult.OK) 
      { 
       Clone_File(); 
      } 

      pictureBox1.Size = new Size(850, 1100); 

    } 

enter image description here

+1

Почему бы вам не поставить точки останова в коде, особенно в коде, где вы переходите на следующую страницу .. и посмотреть, можете ли вы определить точку бутылки. Я ненавижу использование 'Application.DoEvents()' однако после вы вызываете метод .Refresh, попробуйте положить вызов 'Application.DoEvents() после этого – MethodMan

+0

Я использовал точки останова. Любопытно, почему вы ненавидите использование 'Application.DoEvents()'? @MethodMan – taji01

+0

Читайте на «Про» и «Недостатки» Application.DoEvents() 'есть тонны сообщений на' SO' об этом, а также другие, которые расскажут вам об этом, но я просто делал личное мнение/утверждение о том, почему я ненавижу использовать его .. это не стоит вдаваться в этом отношении ... – MethodMan

ответ

3

Оказалось, что медленная часть - это вызов Image.SelectActiveFrame.

Как обычно, решение caching. Однако, чтобы не увеличивать начальное время загрузки, оно должно выполняться лениво на фоне.

Идея проста. Запустите рабочий поток и загрузите все кадры изображения как отдельные Bitmap s в массив. Затем используйте кешированное изображение из массива вместо SelectActiveFrame.

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

class PageBuffer : IDisposable 
{ 
    public static PageBuffer Open(string path) 
    { 
     return new PageBuffer(File.OpenRead(path)); 
    } 

    private PageBuffer(Stream stream) 
    { 
     this.stream = stream; 
     Source = Image.FromStream(stream); 
     PageCount = Source.GetFrameCount(FrameDimension.Page); 
     if (PageCount < 2) return; 
     pages = new Image[PageCount]; 
     var worker = new Thread(LoadPages) { IsBackground = true }; 
     worker.Start(); 
    } 

    private void LoadPages() 
    { 
     for (int index = 0; ; index++) 
     { 
      lock (syncLock) 
      { 
       if (disposed) return; 
       if (index >= pages.Length) 
       { 
        // If you don't need the source image, 
        // uncomment the following line to free some resources 
        //DisposeSource(); 
        return; 
       } 
       if (pages[index] == null) 
        pages[index] = LoadPage(index); 
      } 
     } 
    } 

    private Image LoadPage(int index) 
    { 
     Source.SelectActiveFrame(FrameDimension.Page, index); 
     return new Bitmap(Source); 
    } 

    private Stream stream; 
    private Image[] pages; 
    private object syncLock = new object(); 
    private bool disposed; 

    public Image Source { get; private set; } 
    public int PageCount { get; private set; } 
    public Image GetPage(int index) 
    { 
     if (disposed) throw new ObjectDisposedException(GetType().Name); 
     if (PageCount < 2) return Source; 
     var image = pages[index]; 
     if (image == null) 
     { 
      lock (syncLock) 
      { 
       image = pages[index]; 
       if (image == null) 
        image = pages[index] = LoadPage(index); 
      } 
     } 
     return image; 
    } 

    public void Dispose() 
    { 
     if (disposed) return; 
     lock (syncLock) 
     { 
      disposed = true; 
      if (pages != null) 
      { 
       foreach (var item in pages) 
        if (item != null) item.Dispose(); 
       pages = null; 
      } 
      DisposeSource(); 
     } 
    } 

    private void DisposeSource() 
    { 
     if (Source != null) 
     { 
      Source.Dispose(); 
      Source = null; 
     } 
     if (stream != null) 
     { 
      stream.Dispose(); 
      stream = null; 
     } 
    } 
} 

Полный рабочий демо:

using System; 
using System.Drawing; 
using System.Drawing.Imaging; 
using System.IO; 
using System.Threading; 
using System.Windows.Forms; 

namespace Demo 
{ 
    class TestForm : Form 
    { 
     public TestForm() 
     { 
      var panel = new Panel { Dock = DockStyle.Top, BorderStyle = BorderStyle.FixedSingle }; 
      openButton = new Button { Text = "Open", Top = 8, Left = 16 }; 
      prevButton = new Button { Text = "Prev", Top = 8, Left = 16 + openButton.Right }; 
      nextButton = new Button { Text = "Next", Top = 8, Left = 16 + prevButton.Right }; 
      panel.Height = 16 + openButton.Height; 
      panel.Controls.AddRange(new Control[] { openButton, prevButton, nextButton }); 
      pageViewer = new PictureBox { Dock = DockStyle.Fill, SizeMode = PictureBoxSizeMode.Zoom }; 
      ClientSize = new Size(850, 1100 + panel.Height); 
      Controls.AddRange(new Control[] { panel, pageViewer }); 
      openButton.Click += OnOpenButtonClick; 
      prevButton.Click += OnPrevButtonClick; 
      nextButton.Click += OnNextButtonClick; 
      Disposed += OnFormDisposed; 
      UpdatePageInfo(); 
     } 

     private Button openButton; 
     private Button prevButton; 
     private Button nextButton; 
     private PictureBox pageViewer; 
     private PageBuffer pageData; 
     private int currentPage; 

     private void OnOpenButtonClick(object sender, EventArgs e) 
     { 
      using (var dialog = new OpenFileDialog()) 
      { 
       if (dialog.ShowDialog(this) == DialogResult.OK) 
        Open(dialog.FileName); 
      } 
     } 

     private void OnPrevButtonClick(object sender, EventArgs e) 
     { 
      SelectPage(currentPage - 1); 
     } 

     private void OnNextButtonClick(object sender, EventArgs e) 
     { 
      SelectPage(currentPage + 1); 
     } 

     private void OnFormDisposed(object sender, EventArgs e) 
     { 
      if (pageData != null) 
       pageData.Dispose(); 
     } 

     private void Open(string path) 
     { 
      var data = PageBuffer.Open(path); 
      pageViewer.Image = null; 
      if (pageData != null) 
       pageData.Dispose(); 
      pageData = data; 
      SelectPage(0); 
     } 

     private void SelectPage(int index) 
     { 
      pageViewer.Image = pageData.GetPage(index); 
      currentPage = index; 
      UpdatePageInfo(); 
     } 

     private void UpdatePageInfo() 
     { 
      prevButton.Enabled = pageData != null && currentPage > 0; 
      nextButton.Enabled = pageData != null && currentPage < pageData.PageCount - 1; 
     } 
    } 

    static class Program 
    { 
     [STAThread] 
     static void Main() 
     { 
      Application.EnableVisualStyles(); 
      Application.SetCompatibleTextRenderingDefault(false); 
      Application.Run(new TestForm()); 
     } 
    } 

    class PageBuffer : IDisposable 
    { 
     public static PageBuffer Open(string path) 
     { 
      return new PageBuffer(File.OpenRead(path)); 
     } 

     private PageBuffer(Stream stream) 
     { 
      this.stream = stream; 
      Source = Image.FromStream(stream); 
      PageCount = Source.GetFrameCount(FrameDimension.Page); 
      if (PageCount < 2) return; 
      pages = new Image[PageCount]; 
      var worker = new Thread(LoadPages) { IsBackground = true }; 
      worker.Start(); 
     } 

     private void LoadPages() 
     { 
      for (int index = 0; ; index++) 
      { 
       lock (syncLock) 
       { 
        if (disposed) return; 
        if (index >= pages.Length) 
        { 
         // If you don't need the source image, 
         // uncomment the following line to free some resources 
         //DisposeSource(); 
         return; 
        } 
        if (pages[index] == null) 
         pages[index] = LoadPage(index); 
       } 
      } 
     } 

     private Image LoadPage(int index) 
     { 
      Source.SelectActiveFrame(FrameDimension.Page, index); 
      return new Bitmap(Source); 
     } 

     private Stream stream; 
     private Image[] pages; 
     private object syncLock = new object(); 
     private bool disposed; 

     public Image Source { get; private set; } 
     public int PageCount { get; private set; } 
     public Image GetPage(int index) 
     { 
      if (disposed) throw new ObjectDisposedException(GetType().Name); 
      if (PageCount < 2) return Source; 
      var image = pages[index]; 
      if (image == null) 
      { 
       lock (syncLock) 
       { 
        image = pages[index]; 
        if (image == null) 
         image = pages[index] = LoadPage(index); 
       } 
      } 
      return image; 
     } 

     public void Dispose() 
     { 
      if (disposed) return; 
      lock (syncLock) 
      { 
       disposed = true; 
       if (pages != null) 
       { 
        foreach (var item in pages) 
         if (item != null) item.Dispose(); 
        pages = null; 
       } 
       DisposeSource(); 
      } 
     } 

     private void DisposeSource() 
     { 
      if (Source != null) 
      { 
       Source.Dispose(); 
       Source = null; 
      } 
      if (stream != null) 
      { 
       stream.Dispose(); 
       stream = null; 
      } 
     } 
    } 
} 

UPDATE: Как уже упоминалось в комментариях , приведенная выше реализация использует довольно простую жадную стратегию кэширования, которая использует много памяти и не работает для больших файлов.

Хорошо, однако, что после того, как логика инкапсулируется внутри класса, мы можем изменить стратегию, не касаясь нашего кода приложения.Например, мы можем удалить кэширование на всех (возвращение в исходное состояние), или оптимизировать «prev/next» навигация поддерживая небольшой набор кэшированных «окно» изображения, как этот

class PageBuffer : IDisposable 
{ 
    public const int DefaultCacheSize = 5; 

    public static PageBuffer Open(string path, int cacheSize = DefaultCacheSize) 
    { 
     return new PageBuffer(File.OpenRead(path), cacheSize); 
    } 

    private PageBuffer(Stream stream, int cacheSize) 
    { 
     this.stream = stream; 
     source = Image.FromStream(stream); 
     pageCount = source.GetFrameCount(FrameDimension.Page); 
     if (pageCount < 2) return; 
     pageCache = new Image[Math.Min(pageCount, Math.Max(cacheSize, 3))]; 
     var worker = new Thread(LoadPages) { IsBackground = true }; 
     worker.Start(); 
    } 

    private void LoadPages() 
    { 
     while (true) 
     { 
      lock (syncLock) 
      { 
       if (disposed) return; 
       int index = Array.FindIndex(pageCache, 0, pageCacheSize, p => p == null); 
       if (index < 0) 
        Monitor.Wait(syncLock); 
       else 
        pageCache[index] = LoadPage(pageCacheStart + index); 
      } 
     } 
    } 

    private Image LoadPage(int index) 
    { 
     source.SelectActiveFrame(FrameDimension.Page, index); 
     return new Bitmap(source); 
    } 

    private Stream stream; 
    private Image source; 
    private int pageCount; 
    private Image[] pageCache; 
    private int pageCacheStart, pageCacheSize; 
    private object syncLock = new object(); 
    private bool disposed; 

    public Image Source { get { return source; } } 
    public int PageCount { get { return pageCount; } } 
    public Image GetPage(int index) 
    { 
     if (disposed) throw new ObjectDisposedException(GetType().Name); 
     if (PageCount < 2) return Source; 
     lock (syncLock) 
     { 
      AdjustPageCache(index); 
      int cacheIndex = index - pageCacheStart; 
      var image = pageCache[cacheIndex]; 
      if (image == null) 
       image = pageCache[cacheIndex] = LoadPage(index); 
      return image; 
     } 
    } 

    private void AdjustPageCache(int pageIndex) 
    { 
     int start, end; 
     if ((start = pageIndex - pageCache.Length/2) <= 0) 
      end = (start = 0) + pageCache.Length; 
     else if ((end = start + pageCache.Length) >= PageCount) 
      start = (end = PageCount) - pageCache.Length; 
     if (start < pageCacheStart) 
     { 
      int shift = pageCacheStart - start; 
      if (shift >= pageCacheSize) 
       ClearPageCache(0, pageCacheSize); 
      else 
      { 
       ClearPageCache(pageCacheSize - shift, pageCacheSize); 
       for (int j = pageCacheSize - 1, i = j - shift; i >= 0; j--, i--) 
        Exchange(ref pageCache[i], ref pageCache[j]); 
      } 
     } 
     else if (start > pageCacheStart) 
     { 
      int shift = start - pageCacheStart; 
      if (shift >= pageCacheSize) 
       ClearPageCache(0, pageCacheSize); 
      else 
      { 
       ClearPageCache(0, shift); 
       for (int j = 0, i = shift; i < pageCacheSize; j++, i++) 
        Exchange(ref pageCache[i], ref pageCache[j]); 
      } 
     } 
     if (pageCacheStart != start || pageCacheStart + pageCacheSize != end) 
     { 
      pageCacheStart = start; 
      pageCacheSize = end - start; 
      Monitor.Pulse(syncLock); 
     } 
    } 

    void ClearPageCache(int start, int end) 
    { 
     for (int i = start; i < end; i++) 
      Dispose(ref pageCache[i]); 
    } 

    static void Dispose<T>(ref T target) where T : class, IDisposable 
    { 
     var value = target; 
     if (value != null) value.Dispose(); 
     target = null; 
    } 

    static void Exchange<T>(ref T a, ref T b) { var c = a; a = b; b = c; } 

    public void Dispose() 
    { 
     if (disposed) return; 
     lock (syncLock) 
     { 
      disposed = true; 
      if (pageCache != null) 
      { 
       ClearPageCache(0, pageCacheSize); 
       pageCache = null; 
      } 
      Dispose(ref source); 
      Dispose(ref stream); 
      if (pageCount > 2) 
       Monitor.Pulse(syncLock); 
     } 
    } 
} 

или осуществлять другие «умная» стратегия кэширования. Мы можем даже выбрать стратегию, выбрав Strategy pattern.

Бу, который будет другой историей. Вторая реализация PageBuffer должна быть достаточной для случая использования ОП.

+0

Это отличное решение Если изображения '.tif' имеют небольшое количество страниц, например 20 страниц, но если документ' .tif' имеет более 100 страниц, это становится проблемой. Программа продолжает загружать ('for (int index = 0;; index ++)'), а использование ОЗУ продолжает расти. Я тестировал это на двух компьютерах на первом компьютере, он работал нормально, потому что в нем было много барана. Второй компьютер, однако, не мог справиться с этим, поскольку этот метод требовал большого объема оперативной памяти, когда документ имеет много страниц. @Ivan Stoev – taji01

+1

@ taji01 Да, это очевидный недостаток. Трудно найти компромисс между памятью и скоростью. Конечно, вышеприведенная стратегия наивысшей стратегии кэширования грубой силы, но она может быть скорректирована, например, для сохранения скользящего окна из нескольких кешированных изображений и т. Д. –

+0

Если я изменил размер изображения пропорционально до его загрузки, и это будет быстрее правильно? – taji01

-2

Вы можете попробовать двойной буферизации свою форму; Я знаю, что это помогло мне с аналогичной проблемой, когда моя форма медленно рисовала элементы управления, когда они включали изображение с высоким разрешением или множество элементов управления на основе изображений.

public partial class form1 : Form 
{ 
    public form1() 
    { 
    InitializeComponent(); 
    } 

    //Paste this in your form: 
    protected override CreateParams CreateParams 
    { 
     get 
     { 
      CreateParams cp = base.CreateParams; 
      cp.ExStyle |= 0x02000000; 
      return cp; 
     } 
    } 

    //... your code here 
} 
+0

Спасибо за ответ, может быть, это поможет кому-то еще, но у меня все еще есть проблема. – taji01

+1

Это не будет увеличивать производительность, только свести к минимуму разрывы в приложении, поскольку оно отображает – TheLethalCoder

+0

. Я стою исправлено.Извините за дезинформацию – Patrick

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

  • Нет связанных вопросов^_^