Нескінченна растрова карта [закрита]


10

Я хотів би створити растрову карту під час виконання. Растрові карти повинні бути масштабованими з усіх боків, а доступ до пікселів повинен бути тихим.

Деякі ілюстрації http://img546.imageshack.us/img546/4995/maptm.jpg

Між та після команд, показаних на малюнку, Map.setPixel () і Map.getPixel () повинні встановити / повернути дані, збережені в растровій карті.

Я не очікую реалізації лише концепції, як розподілити пам'ять таким чином, щоб setPixel () / getPixel був максимально швидким.


Чи завжди сіре поле є точкою (0,0) або це може бути й інша координата?
Сокіл

2
Потрібно більше деталей. Чи будуть задані пікселі рідкі? Наскільки повільно ви готові робити extendXметоди для того, щоб зробити setPixelі getPixelті швидкі?
Пітер Тейлор

1
Чи буде растрова карта занадто великою, щоб вміститися в пам'яті? Якими повинні бути швидкі операції - розширення, setPixel (), getPixel ()?

1
@Falcon: Ні, часу є достатньо
SecStone

3
Я голосую, щоб закрити це питання поза темою, оскільки питання сильно залежить від зображення, яке було видалено. Як написано в даний час, це не має великого сенсу.

Відповіді:


9

Якщо extend()операція повинна бути досить швидкою, квадратура може добре підходити; він фактично навіть не вимагає явних операцій розширення. Правда, це не дало б оптимальної продуктивності для випадкового доступу до окремих пікселів, але ваш коментар говорить про те, що ваша основна операція - це ітерація над пікселями, що квадратур може зробити дуже швидко, можливо, майже так само швидко, як реалізація на основі матриці (і швидше якщо ітерація не завжди відбувається однаково, матриця викладається).

Ваші вимоги насправді звучать так, ніби ви намагаєтесь реалізувати стільниковий автомат, як Гра життя. Можливо, ви захочете поглянути на Hashlife , надзвичайно високоефективний спосіб втілити Гра життя в нескінченну сітку. Зауважте, що він заснований на квадраті, але робить деякі дуже розумні додаткові оптимізації, засновані на локальності правил гри.


Дякую за цю ідею! Я зроблю кілька тестувань і повідомлю результати.
SecStone

2

@SecStone заявив, що для операції розширення доступно достатньо часу, тому найпростішим та найефективнішим способом зберігання пікселів є використання одного плоского масиву або двовимірного масиву, оскільки пікселів можна отримати доступ у постійний час.


4
Я би проголосував за це, якщо ви зробите гарну пропозицію щодо того, як ви вважаєте, з яким розширенням слід боротися.
Doc Brown

@Doc Brown: Якщо часу вистачає, просто перемістіть масив. Або, можливо, ви можете щось розробити з фрагментами та функцією перекладача для точки до масиву та індексу фрагментів (яка працює також у постійний час).
Сокіл

2

Вручну

Якщо пам’ять не дуже рідкий ресурс, я вважаю, що я працюю в більших розмірах.
Ось псевдокод.

class Chunk {
    Chunk new(int size) {...}
    void setPixel(int x, int y, int value) {...}
    int getPixel(int x, int y) {...}
}

class Grid {
    Map<int, Map<Chunk>> chunks;
    Grid new(int chunkSize) {...}
    void setPixel(int x, int y, int value) {
         getChunk(x,y).setPixel(x % chunkSize, y % chunkSize, value);//actually the modulo could be right in Chunk::setPixel and getPixel for more safety
    }
    int getPixel(int x, int y) { /*along the lines of setPixel*/ }
    private Chunk getChunk(int x, int y) {
         x /= chunkSize;
         y /= chunkSize;
         Map<Chunk> row = chunks.get(y);
         if (row == null) chunks.set(y, row = new Map<Chunk>());
         Chunk ret = row.get(x);
         if (ret == null) row.set(x, ret = new Chunk(chunkSize));
         return ret;
    }
}

Ця реалізація досить наївна.
Для одного він створює шматки в getPixel (в основному було б добре просто повернути 0 або близько того, якби для цієї позиції не було визначено жодних фрагментів). По-друге, це ґрунтується на припущенні, що ви маєте досить швидку та масштабовану реалізацію Map. Наскільки мені відомо, кожна гідна мова має її.

Також вам доведеться пограти з розміром шматка. Для щільних растрових зображень хороший великий розмір, для розріджених растрових зображень - менший розмір шматка. Насправді, для дуже рідких, "розмір шматка" 1 є найкращим, що робить "шматки" самими застарілими і зменшує структуру даних до int-карти int-карти пікселів.

З полиці

Іншим рішенням може бути перегляд деяких графічних бібліотек. Вони насправді непогано малюють один 2D буфер в інший. Це означатиме, що ви просто виділите більший буфер і матимете оригінал у ньому за координатами.

Як загальна стратегія: Якщо у вас є "блок, що динамічно зростає, пам'ять", це гарна ідея виділити їх кілька, як тільки він буде використаний. Це досить інтенсивна пам'ять, але значно скорочує витрати на розподіл та копіювання . Більшість реалізацій вектора виділяють удвічі більший розмір, коли його перевищують. Отже, особливо якщо ви використовуєте рішення, що не перебуває на полиці, не розширюйте буфер лише на 1 піксель, оскільки запитувався лише один піксель. Виділена пам'ять дешева. Перерозподіл, копіювання та випуск коштує дорого.


1

Кілька порад поради:

  • Якщо ви реалізуєте це як масив якогось цілісного типу (або масив масивів з ...), вам, ймовірно, слід щоразу збільшувати резервний масив на деяку кількість біт / пікселів, щоб уникнути необхідності зміщення бітів під час їх копіювання. Недоліком є ​​те, що ви використовуєте більше місця, але частка витраченого простору зменшується в міру збільшення растрової карти.

  • Якщо ви використовуєте структуру даних на основі Карти, ви можете вдосконалити проблему вирощування растрових зображень, просто перемістивши аргументи x, y координати getPixelта setPixelвикликів.

  • Якщо ви використовуєте структуру даних на основі Карти, вам потрібні записи карт лише для "тих". "Нулі" можуть позначатися відсутністю запису. Це економить значну кількість місця, особливо якщо растрові карти - це нулі.

  • Не потрібно використовувати Карту Карт. Ви можете кодувати intпару x, y як одиницю long. Аналогічний процес може бути використаний для відображення масиву масивів до масиву.


Нарешті, вам потрібно збалансувати 3 речі:

  1. виконання getPixelта setPixel,
  2. виконання extend*операцій та
  3. використання простору.

1

Перш ніж спробувати що-небудь ще складніше, і якщо ви не зможете зберегти все в пам'яті, зберігайте прості речі та використовуйте двовимірний масив разом із інформацією про походження вашої системи координат. Щоб розширити його, використовуйте таку саму стратегію, як, наприклад, C ++ std :: vector: робить розмежування між фактичним розміром масиву та ємністю масиву та розширюйте ємність у шматки, коли досягнуто межі. "Ємність" тут слід визначати через інтервали (from_x, to_x), (from_y, to_y).

Це може потребувати повного перерозподілу пам’яті час від часу, але поки це не відбувається занадто часто, це може бути досить швидким для вашої мети (насправді, ви повинні спробувати / профайлювати це).


1

Абсолютний найшвидший спосіб зробити доступ до пікселів - це двовимірний масив пікселів, адресованих індивідуально.

Для розширень почніть з простої реалізації, яка перерозподіляє і копіює кожен раз (оскільки цей код вам все одно знадобиться). Якщо профілювання не вказує на те, що ви витрачаєте на це багато часу, не потрібно додатково уточнювати його.

Якщо профілювання виявляє необхідність зменшити кількість перерозподілів, і ви не обмежені пам’яттю, подумайте про перерозподіл на відсоток у кожному напрямку та збереження зміщення до початку. (Наприклад, якщо ви запускаєте нову растрову карту на 1x1 та виділяєте масив 9x9 для її утримання, початкові xта yзсуви були б 4.) Компроміс тут повинен робити додаткову математику під час доступу до пікселів, щоб застосувати зміщення.

Якщо розширення виявляться дійсно дорогими, ви можете спробувати одне чи обидва:

  • Обробляйте вертикальні та горизонтальні розширення по-різному. Розширення масиву вертикально в будь-якому напрямку може бути досягнуто шляхом виділення нового блоку та виконання єдиної копії всього існуючого масиву до відповідного зміщення в новій пам'яті. Порівняйте це з горизонтальними розширеннями, де вам доведеться виконувати цю операцію один раз у ряд, оскільки наявні дані не є суміжними у новому блоці.

  • Слідкуйте за найбільш частою кількістю та напрямком розширення. Використовуйте цю інформацію для вибору нового розміру та зміщення, що зменшить ймовірність необхідності повторного виділення та копіювання будь-якого розширення.

Особисто я сумніваюся, що вам знадобиться будь-який із них, якщо коефіцієнт доступу пікселів до розширення не є низьким.


1
  • Постійний розмір плитки (скажімо, 256x256, але з нескінченним числом плиток)
  • Надайте обгортку растрових зображень, що дозволяє негативні координати пікселів (щоб створити враження, що зображення можна розширювати у всіх чотирьох напрямках без необхідності перерахування / синхронізації посилань на існуючі значення координат)
    • Фактичний клас растрових зображень (працює під обгорткою), однак, повинен підтримувати лише абсолютні (негативні) координати.
  • Під обгорткою забезпечте доступ на рівні плитки (рівень блоку) за допомогою вводу / виводу, відображеного на пам'яті
  • На додаток до Map.setPixel()і Map.getPixel()який модифікує один піксель за один раз, а також створення способів , який копіює і модифікує один прямокутник пікселів в той час. Це дозволить абоненту вибрати більш ефективну форму доступу залежно від інформації, що доступна абоненту.
    • Комерційні бібліотеки також надають методи оновлення: один рядок пікселів, один стовпець пікселів, розсіювання / збирання оновлень та операції з арифметичним / логічним розмиванням за один крок (щоб мінімізувати копіювання даних).

(Не будемо підтримувати веселі відповіді ...)


0

Найбільш гнучка і, можливо, надійна реалізація - це пов'язаний список зі структурами, що дають x-координату, y-координату та бітове значення. Я би створив це спочатку і змусив би його працювати.

Потім, якщо він занадто повільний і / або великий, спробуйте звичайні способи його прискорення: масив, матриця, растрова карта, стиснення, кешування, інверсія, зберігання лише значень '1' тощо.

Простіше зробити повільну правильну реалізацію швидше, ніж виправити помилку швидкої реалізації. І тестуючи свою "швидку" другу реалізацію, ви отримали стандарт порівняння з порівнянням.

І, хто знає, ви, ймовірно, виявите, що повільна версія досить швидка. Поки вся структура вписується в пам’ять, речі вже напрочуд швидкі.


3
-1: "змусити його працювати, перш ніж зробити його швидким" - це не вагомий привід почати з найгіршого можливого впровадження. Крім того, практично не буде коду, який не потрібно буде повністю змінювати з базовою структурою даних, тому ітерація на цьому рівні є цілком асиніновою пропозицією.
Майкл Боргвардт

Ось чому у вас є API. API ховає основну реалізацію. SetValue (MyMatrix, X, Y, Value) та GetValue (MyMatrix, X, Y) приховує, чи є MyMatrix 1 або 2 розмірним масивом, або пов'язаним списком, чи кешованим на диску, або SQL-таблицею, або будь-чим іншим. Користувачеві може знадобитися перекомпілювати, але не змінювати код.
Енді Кенфілд

0

Я пропоную наступну реалізацію в Python:

class Map(dict): pass

Він має такі переваги:

  1. Отримати / встановити доступ через map[(1,2)]можна розглядати O(1).
  2. Необхідність явного розширення сітки зникає.
  3. Для клопів мало місця.
  4. Його легко модернізувати до 3D, якщо це необхідно.

0

Якщо вам дійсно потрібна растрова карта будь-якого довільного розміру - і я маю на увазі що-небудь від 1x1 до 1000000x1000000 amd вгору, і вам потрібно розширюватись на вимогу ... одним із можливих способів є використання бази даних. Спочатку це може здатися протилежним інтуїтивно зрозумілим, але те, що ви насправді маєте, - це проблема зберігання. База даних дозволить вам отримувати доступ до окремих пікселів і зберігати по суті будь-яку кількість даних. Я не обов'язково маю на увазі SQL db, btw.

Чи буде це досить швидко у ваших цілях? На це я не можу відповісти, оскільки тут немає контексту щодо того, що ви робите з цією растровою картою. Але якщо це стосується екранного відображення, врахуйте, що вам, як правило, потрібно лише відтягнути додаткові рядки сканування, які відображатимуться як прокрутка екрану, а не всі дані.

Це, як кажуть, я не можу допомогти, але не замислююся, чи йдеш ти про щось не так. Якщо ви, можливо, замість цього використовуєте графічну векторну графіку та відстежуєте окремі столітності в пам'яті, а потім надаєте растрові карти лише настільки великі, як потрібно для екрана?


Можливо, сервер карт OSGeo.
rwong

0

Ось такі кроки, щоб зробити його належним чином:

  1. використовувати переадресацію з одного інтерфейсу на інший для побудови дерева об'єктів, які разом представляють растрові карти
  2. кожне "розширення" растрової карти буде виділяти власну пам'ять і надавати інший інтерфейс для неї
  3. Приклад реалізації може бути:

    template<class T, class P>
    class ExtendBottom {
    public:
       ExtendBottom(T &t, int count) : t(t), count(count),k(t.XSize(), count) { }
       P &At(int x, int y) const { if (y<t.YSize()) return t.At(x,y); else return k.At(x, y-t.YSize()); }
       int XSize() const { return t.XSize(); }
       int YSize() const { return t.YSize()+count; }
    private:
       T &t;
       int count;
       MemoryBitmap k;
    };
    

Очевидно, що для реальної реалізації XSize () та YSize () не працюватимуть, але вам знадобляться MinX (), MaxX (), MinY (), MaxY () або щось подібне, щоб тримати число індексів.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.