使用.tif图像进入下一页时提升性能

时间:2016-02-19 16:49:28

标签: c# .net image winforms picturebox

我正在使用WinForms。在我的表格中,我有一个打开和下一个按钮。我的应用程序将.tif图像打开到图片框中。我使用的所有.tif图片都有多个页面。下一个按钮用于转到tif图片中的下一页。我使用的这些.tif图像非常大。

示例:尺寸:2600 x 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

2 个答案:

答案 0 :(得分:3)

事实证明,慢速部分是Image.SelectActiveFrame电话。

像往常一样,解决方案是缓存。但是,为了不增加初始加载时间,应该在后台延迟执行。

这个想法很简单。启动工作线程并将所有图像帧作为单独的Bitmap加载到数组中。然后使用数组中的缓存图像而不是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:正如评论中所提到的,上面的实现使用了非常简单的贪婪缓存策略,它使用大量内存而不适用于大文件。

但好处是,一旦将逻辑封装在类中,我们就可以在不触及应用程序代码的情况下更改策略。例如,我们可以删除缓存(返回初始状态),或者通过维护一小组缓存图像“窗口”来优化“上一页/下一页”导航

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来选择策略。

这将是另一个故事。对于OP用例,第二个PageBuffer实现应该足够了。

答案 1 :(得分:-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
}