Как реализовать панорамирование / масштабирование на растровых картах gigapixel?
в моем проекте я использую (несжатые 16-битные оттенки серого) изображения gigapixel, которые поступают из сканера с высоким разрешением для целей измерения. Поскольку эти растровые изображения не могут быть загружены в память (в основном из-за фрагментации памяти) я использую плитки (и TIFF с на диск). (см. тема StackOverflow на этом)
Мне нужно реализовать панорамирование / масштабирование таким образом, как Google Maps или DeepZoom. Я должен применить обработку изображений на лету, прежде чем представить его на экране, поэтому я не могу использовать предварительно подготовленную библиотеку, которая напрямую обращается к файлу изображения. Для масштабирования я намерен сохранить изображение с несколькими разрешениями в своем файле (хранилище пирамид). Самые полезные шаги кажутся +200%, 50% и показывают все.
моя база кода в настоящее время является C# и .NET 3.5. В настоящее время я принимаю тип форм, если WPF не дает мне большого преимущества в этой области. У меня есть метод, который может возвращать любую (обработанную) часть базового изображения.
конкретные вопросы:
- подсказки или ссылки о том, как реализовать эту панорамирование/масштабирование с генерацией по требованию частей изображения
- любой код, который может быть использован в качестве основы (предпочтительно торговое или LGPL и BSD лицензии)
- можно ли использовать DeepZoom для этого (т. е. есть ли способ, которым я могу предоставить функцию для предоставления плитки с правильным результатом для текущего уровня масштабирования?) (Мне нужно иметь пиксельную точную адресацию)
3 ответов
этой статья CodeProject: создать...Коллекция Изображений DeepZoom может быть полезно прочитать, так как он говорит о создании источника изображения DeepZoom.
этой статья MSDN раздел динамический глубокий зум: поставка пикселей изображения во время выполнения и ссылки на этот Мандельброта Проводника какой " вид " звучит похоже на то, что вы пытаетесь сделать (т. е. он генерирует определенные части набора Мандельброта по требованию; вы хотите получение определенных частей изображения gigapixel по требованию).
Я думаю, что ответ на "Может ли DeepZoom использоваться для этого?"вероятно, "да", однако, поскольку он доступен только в Silverlight, вам придется сделать некоторые трюки со встроенным элементом управления веб-браузером, если вам нужно клиентское приложение WinForms/WPF.
Извините, я не могу предоставить более конкретные ответы - надеюсь, что эти ссылки помогут.
p.s. Я не уверен, что Silverlight поддерживает изображения TIFF - это может быть проблемой, если вы конвертировать в другой формат.
я решил попробовать кое-что сам. Я придумал простой код 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);
}
оказалось, это была легкая часть. Получить асинхронный многопоточный ввод-вывод в фоновом режиме было намного сложнее. Тем не менее, у меня все работает так, как описано здесь. Вопросов для решения больше .Сетка/форма многопоточность, чем в этой теме.
в псевдо код работает следующим образом:
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.
Я думаю, вы можете решить эту проблему, выполнив следующие действия:
-
генерация изображения:
- сегментируйте изображение в нескольких подизображениях (плитках) небольшого разрешения, например, 500x500. Эти изображения имеют глубину 0
- объедините серию плиток с глубиной 0 (4x4 или 6x6), измените размер комбинации, генерирующей новую плитку с 500x500 пикселями в глубину 1.
- продолжайте этот подход, пока не получите все изображение, используя только несколько плиток.
-
визуализация изображения
- начните с самой высокой глубины
- когда пользователь перетаскивает изображение, загрузите плитки динамически
- когда пользователь масштабирует область изображения, уменьшите глубину, загрузив плитки для этой области в более высоком разрешении.
конечный результат похож на Google Maps.