2016-06-28 89 views
11

Uso WinForms. All'interno del mio modulo ho un pictureBox (impostato su normal mode), pulsante successivo e precedente. Voglio ridimensionare e caricare rapidamente le immagini TIF multipagina. Quando si passa alla pagina successiva nell'immagine TIF multipagina, si verifica un ritardo ogni volta che l'immagine viene disegnata su pictureBox. La velocità media dell'immagine è di circa 800 millisecondi. Desidero caricare le pagine entro 100 Millisecondi.Visualizzazione di immagini Tif di più pagine in 100 millisecondi

Desidero le prestazioni di elaborando immagini TIF di grandi dimensioni veloci come IrfanView. IrfanView è una piccola applicazione per la visualizzazione di immagini. Se scarichi IrfanView puoi vedere quanto è veloce la performance. Attualmente ho un'altra soluzione in cui utilizzo l'operatore in background multi-threading per caricare le pagine TIF in un array, quindi lo ridimensiono. Questo metodo richiede inizialmente un po 'di tempo, ma l'obiettivo qui non è dover aspettare.

C'è un modo per migliorare le prestazioni di Graphics.DrawImage per le immagini di grandi dimensioni in .NET?

g.DrawImage (img, 0, 0, larghezza, altezza); // Questa riga causa il ritardo "800 millisecondi a seconda del computer"

  • La dimensione delle immagini TIF con cui lavoro: Width = 16800, altezza = 10800 immagini
  • Solo bianco e nero Tif
  • profondità di bit = 1
  • Unità Risoluzione = 2

enter image description here

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

Riferimenti

IrfanView:

http://www.irfanview.com/

prova: Grande immagine TIF seguito

http://www.filedropper.com/largetifimage15pages_2

Visual Studio Solution

http://www.filedropper.com/tifpreformancequestion_1

+1

Forse è possibile memorizzare nella cache le immagini precedenti, attuali e successive ogni volta che si verifica la navigazione? – Crowcoder

+1

Utilizzare un backgroundworker per creare un elenco di immagini ridimensionate che puoi memorizzare nella cache è la mia prossima ipotesi .. – TaW

+0

Grazie per la risposta, ho una soluzione simile a quella che hai menzionato. Questa è la soluzione alternativa che uso: http://stackoverflow.com/questions/35510498/boost-the-performance-when-advancing-to-the-next-page-using-tif-images. Questa soluzione utilizza un worker in background che carica le pagine TIF e le ridimensiona. Questa soluzione è veloce solo quando tutte le pagine sono caricate, ma ci vuole ancora tempo. Stavo pensando di visualizzare le pagine non appena l'immagine viene caricata nella casella immagine. Inoltre, non voglio aspettare molto tempo prima che l'immagine TIF venga caricata nella pictureBox. @TaW – taji01

risposta

3

Cosa c'è molto costy è il ridimensionamento delle immagini, perché è una grande immagine (si ha anche un clone in più prima di ridimensionamento che sembra inutile e costi come ~ 10%).

io non sono sicuro che si può trovare un più veloce caricamento/Resizer, vista forse Irfan ha scritto uno specifico (TIF come quella nel campione è una semplice immagine 1 BPP B & W. Una volta caricata l'immagine, si potrebbe ridimensionare in una modalità multithreading, la generazione di generatori dice 2,4,8 o 16 thread di lavoro, ognuno su una parte rettangolare dell'immagine e diviso in generale per il numero di thread).

W/o qualsiasi terza parte, qui è puro.NET un esempio che funziona nel proprio ambiente, con una specifica classe di utilità SizedTifImage multi-thread che memorizza nella cache tutti i frame già ridimensionati in memoria. Quando lo si esegue, si vedrà solo il ~ tempo iniziale 1s del carico e quindi la navigazione attraverso immagini non dovrebbe essere evidente:

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; 
     } 
    } 
+0

Questo è un commento interessante, non è possibile impostare la dimensione iniziale del pannello di disegno sulla dimensione dell'immagine, quindi eliminare dimensionamento? –

+1

@MaviDomates: la dimensione dell'immagine è 16800x10800, quindi * deve * ridimensionarlo in qualche modo. –

3

Ho il sospetto che ci sono diversi problemi qui. Innanzitutto, sospetto che IrfanView non sia scritto in C#. C# è un linguaggio meraviglioso, ma alcuni dei suoi punti di forza non sempre promuovono il massimo delle prestazioni. Ad esempio C# ha un sovraccarico maggiore quando si ha a che fare con la memoria (lo cancella sull'allocazione, tiene traccia dell'utilizzo e dei garbage collects, ecc.).

Le aree che guarderei sono I/O e threading. Sulla mia macchina ci vogliono circa 30 ms per leggere il file (ovvero circa 1/3 del budget di 100 ms.) Sospetto che il problema con DrawImage sia che non è thread (suppongo). eseguire attraverso 22 MB di dati su limiti non di byte, 10x10 pixel di 1 bit nella vecchia immagine devono essere elaborati per produrre 1 pixel nella nuova immagine (ridimensionata). È possibile confermare questo guardando il grafico della CPU del task manager (processori logici view) durante l'esecuzione e quindi durante un'esecuzione di IrfanView.

La soluzione di entrambi i problemi può essere non banale.È possibile accelerare l'I/O utilizzando l'I/O con memoria mappata. Sospetto che la vera vincita sia il threading ridimensiona; 800 ms/8 core ~ ​​= 100 ms (poiché il ridimensionamento è molto parallelizzabile). Puoi scrivere il tuo resizer threadato o potrebbe esserci una libreria di terze parti disponibile per fare ciò che ti serve o potresti essere in grado di modificare un open libreria di sorgenti per essere t hreaded/più veloce.

È inoltre possibile consultare la fonte di MS per la chiamata DrawImage here Sembra che si stia avvolgendo una chiamata GdipDrawImageRectI gidplus.dll.

Si potrebbe anche dare un'occhiata a Parallelizing GDI+ Image Resizing .net per le idee

3

Si potrebbe avere un vantaggio nel creare il proprio PictureBox che eredita da quello originale. È possibile ignorare l'OnPaint e ottimizzare i seguenti parametri dell'oggetto Graphics passato:

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

Alcuni di questi parametri hanno un enorme impatto sulla velocità di rendering (e per la qualità del risultato).

I parametri utilizzati nell'esempio di codice sono già abbastanza veloci ma forse si trovano combinazioni migliori per le proprie esigenze.

3

L'immagine è troppo grande. Il ridimensionamento che normalmente include il calcolo del livellamento può essere estremamente lento.

Pertanto, l'unico modo è utilizzare il puntatore per accedere ai bit di immagine e visualizzare i pixel in modo selettivo.

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

Ridimensiona a 525 x 345 entro 31ms

Il risultato è significativo. Tuttavia, la qualità non è certamente buona come 1 secondo calcolo completo.

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

Per ripristinare la qualità, ridimensionare con un fattore, ad esempio 1,5, fornendo ulteriori dettagli.

Equilibrio tra velocità e qualità.