Рендеринг больших полотен в UserControl

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

В основном я рендерил плитки на сетку на моем UserControl учебный класс. Это для моего редактора мира на основе Tile Engine, который я разрабатываю. Вот скриншот документа с открытым миром и нескольких плиток.

Первоначально я собирался использовать Bitmap под моим контролем это будет предварительный просмотр мира. Используя инструмент кисти, например, когда вы перемещаете мышь и нажимаете левую кнопку, он устанавливает ближайшую плитку под курсором на плитку кисти и рисует ее на layer битовая карта. Контроль OnPaint метод переопределяется туда, где layer Растровое изображение рисуется относительно прямоугольника отсечения события рисования.

Проблема этого метода заключается в том, что при работе с большими мирами растровое изображение будет чрезвычайно большим. Мне нужно, чтобы это приложение было универсальным с мировыми размерами, и совершенно очевидно, что существуют проблемы с производительностью при рендеринге больших растровых изображений в элемент управления каждый раз, когда он аннулируется.

В настоящее время я рисую плитки на элементе управления прямо в переопределенном элементе управления. OnPaint событие. Это здорово, потому что не требует много памяти. Например, (1000, 1000) мир в (20, 20) за плитку (общий размер холста (20000, 20000)) занимает около 18 МБ памяти для всего приложения. Несмотря на то, что он не потребляет много памяти, он довольно интенсивно использует процессор, потому что каждый раз, когда элемент управления становится недействительным, он перебирает каждую плитку в области просмотра. Это производит очень раздражающее мерцание.

То, чего я хочу достичь, - это способ посередине в отношении использования памяти и производительности. По сути, двойная буферизация мира, чтобы не было мерцания при перерисовке элемента управления (изменение размера, фокусировка и размытие, прокрутка и т. Д.). Возьмем, к примеру, Photoshop - как он отображает открытый документ, когда он переполняет область просмотра контейнера?

Для справки, вот мой контроль OnPaint переопределить, используя прямой метод рисования, упомянутый выше.

getRenderBounds возвращает прямоугольник относительно PaintEventArgs.ClipRectangle это используется для визуализации видимых плиток, вместо того, чтобы перебирать все плитки в мире и проверять, видимы ли они.

protected override void OnPaint(PaintEventArgs e)
{
    WorldSettings settings = worldSettings();

    Rectangle bounds = getRenderBounds(e.ClipRectangle),
        drawLocation = new Rectangle(Point.Empty, settings.TileSize);

    e.Graphics.InterpolationMode = 
        System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;
    e.Graphics.SmoothingMode = 
        System.Drawing.Drawing2D.SmoothingMode.None;
    e.Graphics.PixelOffsetMode = 
        System.Drawing.Drawing2D.PixelOffsetMode.None;
    e.Graphics.CompositingQuality = 
        System.Drawing.Drawing2D.CompositingQuality.HighSpeed;

    for (int x = bounds.X; x < bounds.Width; x++)
    {
        for (int y = bounds.Y; y < bounds.Height; y++)
        {
            if (!inWorld(x, y))
                continue;

            Tile tile = getTile(x, y);

            if (tile == null)
                continue;

            drawLocation.X = x * settings.TileSize.Width;
            drawLocation.Y = y * settings.TileSize.Height;

            e.Graphics.DrawImage(img, 
                drawLocation, 
                tileRectangle, 
                GraphicsUnit.Pixel);
        }
    }
}

Просто прокомментируйте, если вам нужно больше контекста из моего кода.

1 ответ

Решение

Хитрость заключается в том, чтобы вообще не использовать большое растровое изображение. Вам нужно только растровое изображение, охватывающее видимую область. Затем вы рисуете все, что видно.

Для этого вам нужно будет хранить данные отдельно от растрового изображения. Это может быть простой массив или массив / список с простым классом, содержащим информацию для каждого блока, такую ​​как мировая позиция.

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

Чтобы добавить новый блок в массив, вычислите его положение холста с мировыми координатами, добавьте его, а затем снова визуализируйте массив (или область, где нарисован блок).

Именно так система рисует элементы управления с прокручиваемыми областями.

Включите двойную буферизацию, чтобы сохранить ее четкой и без мерцания.

В этом случае я бы также использовал панель с отдельными полосами прокрутки и рассчитал относительное положение полос прокрутки.

Другие вопросы по тегам