【问题标题】:View large multi page Tif images within 100 milliseconds在 100 毫秒内查看大型多页 Tif 图像
【发布时间】:2016-07-09 02:22:24
【问题描述】:

我正在使用 WinForms。在我的表单中,我有一个pictureBox(设置为normal mode),下一个和上一个按钮。我想快速调整和加载多页 TIF 图像。当我转到 Multipage TIF 图像的下一页时,每次将图像绘制到 pictureBox 时都会遇到延迟。图像的平均速度大约需要 800 毫秒。 我希望页面在 100 毫秒内加载。

我希望处理大型 TIF 图像的性能与 IrfanView 一样快。 IrfanView 是一个小型图像查看应用程序。如果你下载 IrfanView 你可以看到性能有多快。目前我有另一个解决方案,我使用多线程后台工作程序将 TIF 页面加载到数组中,然后将其缩小。这种方法最初需要一些时间,但这里的目标是不必等待。

有没有办法提高 .NET 中大图像的 Graphics.DrawImage 性能?

g.DrawImage(img, 0, 0, width, height); //此行会导致延迟“ 800 毫秒,具体取决于您的计算机”

  • 我使用的 TIF 图像的大小:宽度=16800,高度=10800
  • 仅黑白 Tif 图像
  • 位深 = 1
  • 分辨率单位 = 2

 using System;
 using System.Collections.Generic;
 using System.ComponentModel;
 using System.Data;
 using System.Diagnostics;
 using System.Drawing;
 using System.Linq;
 using System.Text;
 using System.Threading.Tasks;
 using System.Windows.Forms;

namespace Tif_Preformance_Question
{
public partial class Form1 : Form
{

    int counter = -1;
    int frameCount = 0;
    Stopwatch s = new Stopwatch();
    Image img;
    Image[] images;

    public Form1()
    {
        InitializeComponent();
    }

    private void btn_Open_Click(object sender, EventArgs e)
    {
        var s = new Stopwatch();
        s.Start();
        s.Stop();
        this.Text = "Elapsed Time Milliseconds" + s.ElapsedMilliseconds;


        img = Image.FromFile(@"C:\image\Large_Tif_Image_15pages.tif");
        frameCount = img.GetFrameCount(System.Drawing.Imaging.FrameDimension.Page);
        images = new Image[frameCount];

        for (int i = 0; i < frameCount; i++)
        {
            img.SelectActiveFrame(System.Drawing.Imaging.FrameDimension.Page, i);
            images[i] = (Image)img.Clone();
        }
        img.SelectActiveFrame(System.Drawing.Imaging.FrameDimension.Page, 0);
        pictureBox1.Image = (Image)img.Clone();

    }

    private void btn_Next_Click(object sender, EventArgs e)
    {
        counter++;
        if (counter >= frameCount)
        {
            counter = frameCount - 1;
            btn_Next.Enabled = false;
        }
        btn_Next.Enabled = false;
        LoadPage();
        btn_Next.Enabled = true;
    }

    private void LoadPage()
    {

        StartWatch();
        img.SelectActiveFrame(System.Drawing.Imaging.FrameDimension.Page, counter);
        pictureBox1.Image = ResizeImage((Image)img.Clone(), pictureBox1.Width, pictureBox1.Height);
        pictureBox1.Refresh();
        Stopwatch();
    }

    public Image ResizeImage(Image img, int width, int height)
    {
        Bitmap b = new Bitmap(width, height);
        using (Graphics g = Graphics.FromImage((Image)b))
        {
            g.DrawImage(img, 0, 0, width, height);
        }
        return (Image)b;
    }

    private void StartWatch()
    {
        s.Start();
    }
    private void Stopwatch()
    {

        s.Stop();
        this.Text = "Elapsed Time Milliseconds: " + s.ElapsedMilliseconds;
        s.Reset();
    }
  }
}

参考文献

IrfanView:

http://www.irfanview.com/

测试:下方的大 TIF 图像

http://www.filedropper.com/largetifimage15pages_2

Visual Studio 解决方案

http://www.filedropper.com/tifpreformancequestion_1

【问题讨论】:

  • 也许您可以在每次导航发生时缓存上一张、当前和下一张图片?
  • 我的下一个猜测是使用后台工作程序创建可以缓存的调整大小图像列表..
  • 感谢您的回复,我有一个类似于您提到的解决方案。这是我使用的替代解决方案:stackoverflow.com/questions/35510498/…。此解决方案使用后台工作程序加载 TIF 页面并调整它们的大小。此解决方案仅在所有页面都已加载时才快速,但这仍然需要时间。我计划在图片框中加载图像后立即查看页面。我也不想等待很长时间才能将 TIF 图像加载到图片框中。 @TaW
  • 是否有任何比 .net 代码更快的本机(c++ 或 c)库,您可以调用并让它在后台线程上进行缩减/拆分?如果您不接受第 3 方解决方案,可以考虑使用 C++ 自己创建一个库,让它执行拆分比例缩小可能会给您带来您正在寻找的效率和相应的速度提升。
  • 如果我可以从应用程序调用它,我愿意使用不同的库。我在看 NuGet 的 SharpDX。这看起来很有希望,但我知道如何使用它。 @伊戈尔

标签: c# .net image winforms image-resizing


【解决方案1】:

我怀疑这里有几个问题。首先,我怀疑 IrfanView 不是用 C# 编写的。 C# 是一门很棒的语言,但它的一些优势并不总是能提高性能。例如,C# 在处理内存时有更多开销(它会在分配时清除它,它会跟踪使用情况和垃圾收集等)。

我会关注的领域是 I/O 和线程。在我的机器上,读取文件大约需要 30 毫秒(这几乎是 100 毫秒预算的 1/3。我怀疑 DrawImage 的问题在于它没有线程(我的猜测)。要调整大小,它必须在非字节边界上运行 22 MB 数据;必须处理旧图像中的 10x10 1 位像素以在新(缩放)图像中产生 1 个像素。您可以通过查看任务管理器 CPU 图(逻辑处理器view) 在执行期间,然后在执行 IrfanView 期间。

解决任何一个问题可能都不是一件容易的事。您可以使用内存映射 I/O 加速 I/O。我怀疑真正的胜利在于调整大小; 800 ms / 8 cores ~= 100 ms(因为调整大小非常可并行化)。您可以编写自己的线程大小调整器,或者可能有一个 3rd 方库可以满足您的需要。或者您可以修改开源库以使其线程化/更快。

您还可以查看 MS 的 DrawImage 调用源代码here 它似乎正在包装 gidplus.dll GdipDrawImageRectI 调用。

您还可以查看Parallelizing GDI+ Image Resizing .net 的想法

【讨论】:

    【解决方案2】:

    创建自己的继承自原始的 PictureBox 可能会有优势。您可以覆盖 OnPaint 并调整传递的 Graphics 对象的以下参数:

    private override OnPaint(object sender, PaintEventArgs e)
    {
        e.Graphics.CompositingQuality = CompositingQuality.HighSpeed;
        e.Graphics.InterpolationMode = InterpolationMode.NearestNeighbor;
        e.Graphics.SmoothingMode = SmoothingMode.None;
        e.Graphics.PixelOffsetMode = PixelOffsetMode.Half;
        e.Graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAliasGridFit;
        e.Graphics.CompositingMode = CompositingMode.SourceCopy;
        base OnPaint(sender, e)
    }
    

    其中一些参数会对渲染速度(以及结果质量)产生巨大影响。

    代码示例中使用的参数已经相当快了,但也许您会找到更好的组合来满足您的要求。

    【讨论】:

      【解决方案3】:

      非常昂贵的是调整图像的大小,因为它是一个大图像(在调整大小之前你还有一个额外的克隆,看起来没用,成本约为 10%)。

      我不确定你能找到更快的加载器/调整器,也许 irfan view 专门写了一个(TIF 就像你的示例中的那个是一个简单的 1 bpp 黑白图像。一旦图像加载,你可以在多线程中调整大小模式,生成 2、4、8 或 16 个工作线程,每个工作线程位于图像的矩形部分,然后除以线程数)。

      没有任何第 3 方,这里是纯 .NET 示例,可在您的环境中工作,具有特定的多线程 SizedTifImage 实用程序类,可缓存已在内存中调整大小的所有帧。当你运行它时,你只会看到最初的大约 1 秒的加载时间,然后浏览图片应该不会很明显:​​

      public partial class Form1 : Form
      {
          SizedTifImage _tif;
      
          private void btn_Open_Click(object sender, EventArgs e)
          {
             ...
              _tif = new SizedTifImage(@"Large_Tif_Image_15pages.tif", pictureBox1.Width, pictureBox1.Height);
              pictureBox1.Image = _tif.GetFrame(0);
              btn_Next_Click(null, null);
          }
      
          private void btn_Next_Click(object sender, EventArgs e)
          {
              counter++;
              if (counter >= _tif.FrameCount)
              {
                  counter = _tif.FrameCount - 1;
                  btn_Next.Enabled = false;
              }
              btn_Next.Enabled = false;
              LoadPage();
              btn_Next.Enabled = true;
          }
      
          private void LoadPage()
          {
              StartWatch();
              pictureBox1.Image = _tif.GetFrame(counter);
              Stopwatch();
          }
      }
      
      public class SizedTifImage : IDisposable
      {
          private Image _image;
          private ConcurrentDictionary<int, Image> _frames = new ConcurrentDictionary<int, Image>();
      
          public SizedTifImage(string filename, int width, int height)
          {
              Width = width;
              Height = height;
              _image = Image.FromFile(filename);
              FrameCount = _image.GetFrameCount(FrameDimension.Page);
              ThreadPool.QueueUserWorkItem(ResizeFrame);
          }
      
          public int FrameCount { get; private set; }
          public int Width { get; private set; }
          public int Height { get; private set; }
      
          private void ResizeFrame(object state)
          {
              for (int i = 0; i < FrameCount; i++)
              {
                  if (_image == null)
                      return;
      
                  _image.SelectActiveFrame(FrameDimension.Page, i);
                  var bmp = new Bitmap(Width, Height);
                  using (var g = Graphics.FromImage(bmp))
                  {
                      if (_image == null)
                          return;
      
                      g.DrawImage(_image, 0, 0, bmp.Width, bmp.Height);
                  }
                  _frames.AddOrUpdate(i, bmp, (k, oldValue) => { bmp.Dispose(); return oldValue; });
              }
          }
      
          public Image GetFrame(int i)
          {
              if (i >= FrameCount)
                  throw new IndexOutOfRangeException();
      
              if (_image == null)
                  throw new ObjectDisposedException("Image");
      
              Image img;
              do
              {
                  if (_frames.TryGetValue(i, out img))
                      return img;
      
                  Thread.Sleep(10);
              }
              while (true);
          }
      
          public void Dispose()
          {
              var images = _frames.Values.ToArray();
              _frames.Clear();
              foreach (var img in images)
              {
                  img.Dispose();
              }
      
              if (_image != null)
              {
                  _image.Dispose();
                  _image = null;
              }
          }
      

      【讨论】:

      • 这是一个有趣的评论,他能不把初始绘图面板大小设置为图像大小,所以不要重新调整大小吗?
      • @MaviDomates - 图像尺寸为 16800x10800,所以他必须以某种方式调整它的大小。
      【解决方案4】:

      您的图片太大。通常包括平滑计算的调整大小可能会非常缓慢。

      因此,唯一的方法是使用指针访问图像位并选择性地显示像素。

      public unsafe Image ResizeImage(Bitmap img, int width, int height)
      {
          var stopwatch = Stopwatch.StartNew();
      
          var imgBits = img.LockBits(new Rectangle(Point.Empty, img.Size), ImageLockMode.ReadOnly, img.PixelFormat);
      
          Bitmap b = new Bitmap(width, height);
          var bBits = b.LockBits(new Rectangle(Point.Empty, b.Size), ImageLockMode.WriteOnly, b.PixelFormat);
      
          for (int j = 0; j < height; j++)
          {
              var imgJ = j * img.Height / height;
      
              for (int i = 0; i < width; i++)
              {
                  var imgI = i * img.Width / width;
      
                  var imgPointer = (byte*)imgBits.Scan0 + imgJ * imgBits.Stride + (imgI >> 3);
                  var mask = (byte)(0x80 >> (imgI & 0x7));
                  var imgPixel = (uint)(*imgPointer & mask);
      
                  var bPointer = (uint*)bBits.Scan0 + j * bBits.Width + i;
                  *bPointer = imgPixel > 0 ? 0x00FFFFFF : 0xFF000000;
              }
          }
      
          img.UnlockBits(imgBits);
          b.UnlockBits(bBits);
      
          stopwatch.Stop();
          Console.WriteLine("Resize to " + width + " x " + height + " within " + stopwatch.ElapsedMilliseconds + "ms");
      
          return b;
      }
      
      public void Test()
      {
          var rawImage = new Bitmap(@"Large_Tif_Image_15pages.tif");
          rawImage.SelectActiveFrame(FrameDimension.Page, 3);
      
          pictureBox1.Image = ResizeImage(rawImage, pictureBox1.Width, pictureBox1.Height);
      }
      

      在 31 毫秒内调整为 525 x 345

      结果很重要。但是,质量当然不如 1 整秒计算。

      var outputFactor = 1.5;
      var outputWidth = (int)(pictureBox1.Width * outputFactor);
      var outputHeight = (int)(pictureBox1.Height * outputFactor);
      var outputImage = ResizeImage(rawImage, outputWidth, outputHeight);
      

      要恢复质量,请使用 1.5 等因子调整大小,以提供更多细节。

      速度和质量之间的平衡。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多