2010-02-10 25 views
7

Nel mio progetto, sto usando (non compresso a 16 bit in scala di grigi) immagini gigapixel che provengono da uno scanner ad alta risoluzione per scopi di misurazione. Dato che questi bitmap non possono essere caricati in memoria (principalmente a causa della frammentazione della memoria) sto usando tiles (e TIFF piastrellato su disco). (vedi StackOverflow topic on this)Come implementare pan/zoom su bitmap gigapixel?

Ho bisogno di implementare panning/zoom in un modo come Google Maps o DeepZoom. Devo applicare l'elaborazione delle immagini al volo prima di presentarla sullo schermo, quindi non posso usare una libreria precotta che accede direttamente a un file immagine. Per lo zoom intendo conservare un'immagine multi-risoluzione nel mio file (memoria piramidale). I passi più utili sembrano essere + 200%, 50% e mostrare tutto.

Il mio codice base è attualmente C# e .NET 3.5. Attualmente presumo il tipo di moduli, a meno che WPF non mi dia un grande vantaggio in quest'area. Ho un metodo che può restituire qualsiasi parte (elaborata) dell'immagine sottostante.

Questioni specifiche:

  • suggerimenti o riferimenti su come implementare questa pan/zoom con generazione on-demand di parti di immagine
  • alcun codice che potrebbe essere utilizzato come base (di preferenza commerciale o LGPL/Licenze simili BSD)
  • è possibile utilizzare DeepZoom per questo (ovvero esiste un modo per fornire una funzione per fornire una piastrella con la giusta risoluzione per il livello di zoom corrente?) (È necessario avere ancora un indirizzamento preciso dei pixel)
+0

DeepZoom sembra l'approccio. Dovrai creare versioni a bassa risoluzione delle immagini con qualcosa come http://www.imagemagick.org/script/index.php. Dalla mia ricerca, sembra che questo sia un campo minato brevettato, quindi .... – kenny

risposta

3

Ho deciso di provare qualcosa da solo. Ho trovato un semplice codice GDI +, che usa le tessere che ho già ricevuto. Ho solo filtrato le parti che sono rilevanti per la regione di ritaglio corrente. Funziona come per magia! Si prega di trovare il mio codice qui sotto. (Impostazioni modulo doppio buffering per i migliori risultati)

protected override void OnPaint(PaintEventArgs e) 
    { 
     base.OnPaint(e); 
     Graphics dc = e.Graphics; 
     dc.ScaleTransform(1.0F, 1.0F); 
     Size scrollOffset = new Size(AutoScrollPosition); 

     int start_x = Math.Min(matrix_x_size, 
          (e.ClipRectangle.Left - scrollOffset.Width)/256); 
     int start_y = Math.Min(matrix_y_size, 
          (e.ClipRectangle.Top - scrollOffset.Height)/256); 
     int end_x = Math.Min(matrix_x_size, 
         (e.ClipRectangle.Right - scrollOffset.Width + 255)/256); 
     int end_y = Math.Min(matrix_y_size, 
         (e.ClipRectangle.Bottom - scrollOffset.Height + 255)/256); 

     // start * contain the first and last tile x/y which are on screen 
     // and which need to be redrawn. 
     // now iterate trough all tiles which need an update 
     for (int y = start_y; y < end_y; y++) 
      for (int x = start_x; x < end_x; x++) 
      { // draw bitmap with gdi+ at calculated position. 
       dc.DrawImage(BmpMatrix[y, x], 
          new Point(x * 256 + scrollOffset.Width, 
            y * 256 + scrollOffset.Height)); 
      } 
    } 

Per provarlo, ho creato una matrice di 80x80 di 256 piastrelle (420 MPixel). Ovviamente dovrò aggiungere del caricamento posticipato nella vita reale. Posso lasciare le tessere (vuote) se non sono ancora state caricate. In effetti, ho chiesto al mio cliente di attaccare 8 GByte nella sua macchina, quindi non devo preoccuparmi troppo delle prestazioni. Una volta caricate le tessere può rimanere in memoria.

public partial class Form1 : Form 
{ 
    bool dragging = false; 
    float Zoom = 1.0F; 
    Point lastMouse; 
    PointF viewPortCenter; 

    private readonly Brush solidYellowBrush = new SolidBrush(Color.Yellow); 
    private readonly Brush solidBlueBrush = new SolidBrush(Color.LightBlue); 
    const int matrix_x_size = 80; 
    const int matrix_y_size = 80; 
    private Bitmap[,] BmpMatrix = new Bitmap[matrix_x_size, matrix_y_size]; 
    public Form1() 
    { 
     InitializeComponent(); 

     Font font = new Font("Times New Roman", 10, FontStyle.Regular); 
     StringFormat strFormat = new StringFormat(); 
     strFormat.Alignment = StringAlignment.Center; 
     strFormat.LineAlignment = StringAlignment.Center; 
     for (int y = 0; y < matrix_y_size; y++) 
      for (int x = 0; x < matrix_x_size; x++) 
      { 
       BmpMatrix[y, x] = new Bitmap(256, 256, PixelFormat.Format24bppRgb); 
       //     BmpMatrix[y, x].Palette.Entries[0] = (x+y)%1==0?Color.Blue:Color.White; 

       using (Graphics g = Graphics.FromImage(BmpMatrix[y, x])) 
       { 
        g.FillRectangle(((x + y) % 2 == 0) ? solidBlueBrush : solidYellowBrush, new Rectangle(new Point(0, 0), new Size(256, 256))); 
        g.DrawString("hello world\n[" + x.ToString() + "," + y.ToString() + "]", new Font("Tahoma", 8), Brushes.Black, 
         new RectangleF(0, 0, 256, 256), strFormat); 
        g.DrawImage(BmpMatrix[y, x], Point.Empty); 
       } 
      } 

     BackColor = Color.White; 

     Size = new Size(300, 300); 
     Text = "Scroll Shapes Correct"; 

     AutoScrollMinSize = new Size(256 * matrix_x_size, 256 * matrix_y_size); 
    } 

Questa è stata la parte facile.Ottenere l'i/o multithread asincrono fatto in background era molto più difficile da ottenere. Eppure, ho funzionato nel modo descritto qui. I problemi da risolvere erano più legati al multithreading .NET/Form rispetto a questo argomento.

In pseudo-codice funziona in questo modo:

after onPaint (and on Tick) 
    check if tiles on display need to be retrieved from disc 
     if so: post them to an async io queue 
     if not: check if tiles close to display area are already loaded 
      if not: post them to an async io/queue 
    check if bitmaps have arrived from io thread 
     if so: updat them on screen, and force repaint if visible 

Risultato: Ora ho il mio controllo personalizzato che utilizza circa 50 MByte per un accesso molto veloce alla dimensione arbitraria (piastrelle) file TIFF.

+0

Assicurati di aggiungere un flag per il compilatore che dice che è solo a 64 bit, altrimenti diventa un paradiso extra-gioiello – Dykam

3

Questo CodeProject article: Generate...DeepZoom Image Collection potrebbe essere una lettura utile poiché parla della generazione di una sorgente di immagini DeepZoom.

Questo MSDN article ha una sezione dinamico Deep Zoom:. Fornire i pixel dell'immagine in fase di esecuzione e link a questo Mandelbrot Explorer che 'un po' suona simile a quello che stai cercando di fare (cioè lui sta generando specifiche parti del mandelbrot set on-demand, vuoi recuperare parti specifiche della tua immagine gigapixel su richiesta).

Penso che la risposta a "possa utilizzare DeepZoom per questo?" è probabilmente "Sì", tuttavia poiché è disponibile solo in Silverlight, dovrai eseguire alcuni trucchi con un controllo del browser Web incorporato se hai bisogno di un'app client WinForms/WPF.

Siamo spiacenti, non posso fornire risposte più specifiche, speriamo che i collegamenti siano di aiuto.

p.s. Non sono sicuro che Silverlight supporti le immagini TIFF; potrebbe trattarsi di un problema, a meno che non si converta in un altro formato.

+0

Silverlight attualmente non supporta le immagini TIFF, ma ha una classe WriteableBitmap –

+0

Grazie. +1 per la risposta e i collegamenti. Sono spaventato per la complessità dell'aggiunta di Silverlight-via-a-webserver-in-wpf solo per il pan/zoom. (Al momento non ho alcuna parte server o browser nella mia applicazione, ma è desktop). Per quanto riguarda il TIFF: questo non è un problema; Posso eseguire lo streaming PNG dal TIFF internamente. Basta scartare il 50% dei bit ;-) – Adriaan

+0

Da quanto ho imparato finora su DeepZoom, sembra che cose come l'indirizzamento accurato (sincronizzazione dello zoom/posizione tra più finestre nella mia applicazione) saranno molto difficili. La funzionalità offerta da Deepzoom non giustifica la complessità architettonica che porterà (aggiungendo Silverlight e un server solo per il pan/zoom). Sto facendo un'applicazione personalizzata (una delle) in cui le ore di sviluppo sono più importanti delle campane e dei fischietti. Lo terrò d'occhio per le applicazioni future (quando si tratta di .NET xx sarà interessante! – Adriaan

3

Credo che si può risolvere il problema seguendo le istruzioni riportate di seguito:

  1. generazione Image:

    • segmento vostra immagine in più immagini parziali (piastrelle) di una piccola risoluzione, per instace, 500x500 . Queste immagini sono la profondità 0
    • combinare una serie di tessere con profondità 0 (4x4 o 6x6), ridimensionare la combinazione generando una nuova piastrella con 500x500 pixel in profondità 1.
    • continuare con questo approccio fino a ottenere l'intera immagine utilizzando solo alcune tessere.
  2. visualizzazione Immagine

    • Inizia dalla profondità massima
    • Quando l'utente trascina l'immagine, caricare le piastrelle in modo dinamico
    • Quando l'utente ingrandire una regione dell'immagine, diminuire la profondità, caricando le tessere per quella regione con una risoluzione più alta.

Il risultato finale è simile a Google Maps.

+2

I'm avere difficoltà a vedere come il link che hai fornito è utile, non offre alcun codice/pseudocodice per descrivere come fare ciò che l'OP stava chiedendo.Sto rimuovendo il link in modo che nessuno sbagli questa risposta per lo spam –

+0

, la mia applicazione aveva bisogno principalmente di livelli di zoom del 25-100%, e per questo l'utilizzo di un singolo layer con piastrelle fisse da 256x256 funzionava molto bene. È solo che GDI + non è in grado di gestire immagini di grandi dimensioni e ha bisogno di aiuto (visualizzazione dinamica delle tessere) e caricamento di file TIFF (Tifflib.net).Il tuo metodo è effettivamente necessario per le immagini a terra o se è necessario un fattore di zoom. Grazie per il tuo contributo. – Adriaan