Как реализовать панорамирование / масштабирование на гигапиксельных растровых изображениях? - PullRequest
7 голосов
/ 10 февраля 2010

В моем проекте я использую (несжатые 16-битные оттенки серого) гигапиксельные изображения, которые поступают со сканера высокого разрешения для целей измерения. Поскольку эти растровые изображения не могут быть загружены в память (в основном из-за фрагментации памяти), я использую плитки (и мозаичный TIFF на диске). (см. тему StackOverflow по этому вопросу )

Мне нужно реализовать панорамирование / масштабирование таким образом, как Google Maps или DeepZoom. Я должен применить обработку изображения на лету, прежде чем представить его на экране, поэтому я не могу использовать предварительно приготовленную библиотеку, которая напрямую обращается к файлу изображения. Для увеличения я намереваюсь сохранить изображение с мульти-разрешением в моем файле (хранилище пирамид). Наиболее полезные шаги кажутся + 200%, 50% и показывают все.

Моя кодовая база в настоящее время C # и .NET 3.5. В настоящее время я предполагаю тип Forms, если WPF не дает мне большого преимущества в этой области. У меня есть метод, который может вернуть любую (обработанную) часть базового изображения.

Конкретные проблемы:

  • подсказки или ссылки о том, как реализовать это панорамирование / масштабирование с созданием частей изображения по требованию
  • любой код, который можно использовать в качестве основы (предпочтительно коммерческие или LGPL / BSD-подобные лицензии)
  • можно ли использовать для этого DeepZoom (то есть, есть ли способ, которым я могу предоставить функцию для предоставления плитки с правильным разрешением для текущего уровня масштабирования?) (Мне все еще нужно иметь точную пиксельную адресацию)

Ответы [ 3 ]

3 голосов
/ 12 февраля 2010

Я решил попробовать что-то сам. Я придумал простой код 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));
            }
    }

Чтобы проверить это, я создал матрицу размером 80x80 из 256 плиток (420 MPixel). Конечно, мне придется добавить некоторые отложенные загрузки в реальной жизни. Я могу оставить плитки (пустыми), если они еще не загружены. На самом деле, я попросил своего клиента вставить 8 ГБ в его машину, чтобы мне не пришлось слишком сильно беспокоиться о производительности. После загрузки плитки могут остаться в памяти.

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

Оказалось, что это была легкая часть. Сделать асинхронный многопоточный ввод / вывод в фоновом режиме было намного сложнее. Тем не менее, у меня это работает так, как описано здесь. Проблемы, которые нужно было решить, были связаны с многопоточностью .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 произвольного размера (мозаичного размера).

3 голосов
/ 19 ноября 2012

Я думаю, вы можете решить эту проблему, выполнив следующие действия:

  1. Генерация изображения:

    • сегментируйте ваше изображение на несколько изображений (фрагментов) небольшого разрешения, например, 500x500. Эти изображения глубиной 0
    • объедините серию плиток с глубиной 0 (4x4 или 6x6), измените размер комбинации, создав новую плитку с глубиной 500x500 пикселей 1.
    • продолжайте этот подход, пока не получите все изображение, используя только несколько плиток.
  2. Визуализация изображения

    • Старт с самой большой глубины
    • Когда пользователь перетаскивает изображение, загружайте плитки динамически
    • Когда пользователь увеличивает область изображения, уменьшает глубину, загружая плитки для этой области в более высоком разрешении.

Окончательный результат похож на Google Maps.

3 голосов
/ 10 февраля 2010

Эта статья CodeProject: Генерировать ... Коллекция изображений DeepZoom может быть полезна для чтения, поскольку в ней говорится о создании источника изображения DeepZoom.

Эта статья MSDN имеет раздел Динамический глубокий зум: предоставление пикселей изображения во время выполнения и ссылки на этот Исследователь Мандельброта , который "своего рода" звучит похоже на что вы пытаетесь сделать (т. е. он генерирует определенные части набора Мандельброта по запросу; вы хотите получать определенные части вашего гигапиксельного изображения по требованию).

Я думаю, что ответ "можно ли использовать DeepZoom для этого?" вероятно, "Да", однако, поскольку он доступен только в Silverlight, вам придется выполнить некоторые трюки со встроенным элементом управления веб-браузера, если вам нужно клиентское приложение WinForms / WPF.

Извините, я не могу дать более конкретные ответы - надеюсь, эти ссылки помогут.

p.s. Я не уверен, что Silverlight поддерживает изображения в формате TIFF - это может быть проблемой, если вы не конвертируете в другой формат.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...