2010-02-10 76 views
7

在我的項目中,我使用來自高分辨率掃描儀的(未壓縮的16位灰度)千兆像素圖像進行測量。由於這些位圖無法加載到內存中(主要是由於內存碎片),因此我正在使用磁貼(磁盤上的平鋪TIFF)。 (請參閱StackOverflow topic on this如何在千兆像素位圖上實現平移/縮放?

我需要以Google地圖或DeepZoom的方式實施平移/縮放。我必須在屏幕上顯示圖像之前先進行圖像處理,因此我無法使用直接訪問圖像文件的預先編輯好的庫。對於縮放,我打算在我的文件(金字塔存儲)中保留一個多分辨率圖像。最有用的步驟似乎是+ 200%,50%,並顯示所有。

我的代碼庫目前是C#和.NET 3.5。目前我假設Forms類型,除非WPF在這方面給我很大的優勢。我有一種方法可以返回底層圖像的任何(處理過的)部分。

的具體問題:關於如何執行這個搖攝/帶點播生成圖像部分的放大

  • 提示或引用
  • 其可以被用作基礎(優選商業或LGPL任何代碼/ BSD like licenses)
  • DeepZoom可以用於這個(也就是說,我可以提供一個函數,以提供一個在當前縮放級別的正確解析瓦片?)(我需要像素精確尋址仍然)
+0

DeepZoom看起來像這樣。您將不得不使用http://www.imagemagick.org/script/index.php等來創建圖像的低分辨率版本。從我的角度來看,這似乎是一個專利雷區,所以...... – kenny 2010-02-10 08:19:07

回答

3

我決定自己試一下。我想出了一個簡單的GDI +代碼,它使用我已經得到的瓷磚。我只是篩選出與當前裁剪區域相關的部分。它像魔術般運作!請在下面找到我的代碼。 (表格設置以獲得最佳效果雙緩衝)

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

爲了測試它,我已經創造了256的80×80瓦(420兆像素)的矩陣。當然,我必須在現實生活中添加一些延遲加載。如果它們尚未加載,我可以將它們留空(空)。事實上,我已經要求我的客戶在他的機器上放置8 GByte,所以我不必爲性能打擾太多。一旦加載的瓷磚可以留在內存中。

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

原來這是很容易的部分。在後臺獲得異步多線程I/O是難以實現的。儘管如此,我仍然按照這裏描述的方式工作。要解決的問題是與此主題相關的更多.NET/Form多線程。

在僞代碼,它的工作原理是這樣的:

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 

結果:我現在有一個使用大約50兆字節的速度非常快獲得任意大小(瓷磚)TIFF文件我自己的自定義控制。

+0

請確保添加一個編譯器標誌,說它只有64位,否則它將成爲一個超現實的天堂。 – Dykam 2010-02-22 12:44:07

3

這個CodeProject article: Generate...DeepZoom Image Collection可能是一個有用的閱讀,因爲它談到了生成DeepZoom圖像源。

MSDN article有一節動態的Deep Zoom:在運行時,並鏈接到本Mandelbrot Explorer提供圖像的像素,其「有點」的聲音類似於你正在試圖做什麼(即他產生的特定部位mandelbrot按需設置;您想要按需檢索您的千兆像素圖像的特定部分)。

我認爲答案是「可以使用DeepZoom嗎?」可能是「是」,但是隻有在Silverlight中可用時,如果您需要WinForms/WPF客戶端應用程序,則必須使用嵌入式Web瀏覽器控件執行一些技巧。

對不起,我不能提供更具體的答案 - 希望這些鏈接幫助。

p.s.我不確定Silverlight是否支持TIFF圖像 - 這可能是一個問題,除非您轉換爲其他格式。

+0

Silverlight目前不支持TIFF圖像,但它確實有一個WriteableBitmap類 – 2010-02-10 08:19:51

+0

謝謝。答案和鏈接+1。我擔心爲了平移/縮放而添加Silverlight-via-a-webserver-in-wpf的複雜性。 (我目前在我的應用程序中沒有服務器或瀏覽器部分;它是桌面)。至於TIFF:這不是問題;我可以在內部從TIFF流式傳輸PNG。丟棄50%的比特;-) – Adriaan 2010-02-10 08:36:03

+0

從我迄今爲止對DeepZoom的瞭解中可以看出,精確尋址(我的應用程序中多個窗口之間的縮放/位置同步)等操作將非常困難。 Deepzoom提供的功能並不能證明它會帶來的體系結構複雜性(僅用於平移/縮放添加Silverlight和服務器)。我正在製作一個自定義(其中一個)應用程序,其中開發時間比鈴鐺聲更重要。我將繼續關注它的未來應用(當談到.NET xx時,它會很有趣! – Adriaan 2010-02-12 07:38:39

3

我想你可以按照下面的步驟解決這個問題:

  1. 圖像生成:

    • 細分的小分辨率的多個子圖像(磚)的圖像,爲instace,500×500 。這些圖像是深度0
    • 結合隨深度0(4×4或6×6)的一系列的瓷磚,調整組合產生具有500x500像素一個新的瓦片在深度1
    • 繼續這種方法,直至僅使用得到的整個圖像幾塊瓷磚。
  2. 圖像可視化

    • 開始從最高深度
    • 當用戶拖曳圖像,加載瓦片動態
    • 當用戶縮放圖像的區域中,降低的深度,以更高的分辨率加載該區域的圖塊。

最終結果與谷歌地圖類似。

+2

我是很難看到你提供的鏈接是有幫助的;它沒有提供任何代碼/僞代碼來描述如何做OP的要求,我正在刪除鏈接,以便沒有人錯誤地回答垃圾郵件的回答。 – 2012-11-19 17:13:59

+0

實際上,我的應用程序主要需要縮放級別爲25-100%,而使用固定256x256的單層圖層效果非常好,只是GDI +無法真正處理大圖像並需要一些幫助(動態顯示圖塊)和加載TIFF文件(Tifflib.net)。您的方法確實需要用於terrabit圖像或需要任何縮放因子。感謝您的貢獻。 – Adriaan 2012-11-21 12:42:15