在100毫秒内查看大型多页Tif图像

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

我希望像IrfanView一样快速处理大型TIF图像 。 IrfanView是一个小型图像查看应用程序。 如果您下载IrfanView,您可以看到性能有多快。 目前我有另一个解决方案,我使用multithreading后台工作程序将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) { 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

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

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

解决任何一个问题可能都是非常重要的。 您可以使用Memory Mapped I / O加速I / O. 我怀疑真正的胜利将是resize; 800 ms / 8核〜= 100 ms(因为resize非常可并行化)。 您可以编写自己的线程调整器,也可以使用第三方库来执行您需要的操作。 或者您可以将开源库修改为线程/更快。

您还可以在此处查看MS的DrawImage调用源。它似乎包含了一个gidplus.dll GdipDrawImageRectI调用。

您还可以查看并行化GDI +图像resize.net的想法

您可能有一个优势就是创建自己inheritance原始PictureBox的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) } 

其中一些参数对渲染速度(以及结果的质量)有很大影响。

代码示例中使用的参数已经非常快,但您可能会找到更符合您要求的组合。

什么是非常有用的是图像的大小调整,因为它是一个大图像(在resize之前你还有一个额外的克隆,似乎没用,成本约为10%)。

我不确定你能找到一个更快的加载器/缩放器,也许irfan视图专门写了一个(像你的样本中的那个TIF是一个简单的1 bpp B&W图像。一旦加载了图像,你就可以在multithreading模式下resize,产生比如2,4,8或16个工作线程,每个线程在图像的矩形部分上,并且整体除以线程数)。

没有任何第三方,这里是纯.NET,一个在您的环境中工作的示例,具有特定的multithreadingSizedTifImage实用程序类,用于缓存已在内存中resize的所有帧。 当你运行它时,你只会看到最初的~1s加载时间,然后浏览图像不应该是显而易见的:

 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 _frames = new ConcurrentDictionary(); 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; } } 

你的形象太大了。 通常包括平滑计算的大小调整可能非常慢。

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

 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); } 

在31ms内调整为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等因素resize,以提供更多详细信息。

速度和质量之间的平衡。