Infinite Bitmap [chiuso]


10

Mi piacerebbe creare una bitmap durante il runtime. La bitmap dovrebbe essere scalabile su tutti i lati e l'accesso ai pixel dovrebbe essere silenzioso ed efficiente.

Qualche illustrazione http://img546.imageshack.us/img546/4995/maptm.jpg

Tra e dopo i comandi mostrati in figura, Map.setPixel () e Map.getPixel () dovrebbero impostare / restituire i dati salvati nella bitmap.

Non mi aspetto un'implementazione solo un concetto su come allocare la memoria in modo tale che setPixel () / getPixel sia il più veloce possibile.


Il campo grigio è sempre il punto (0,0) o può essere anche un'altra coordinata?
Falcon,

2
Ulteriori dettagli necessari. I pixel impostati saranno sparsi? Come lento siete disposti a rendere i extendXmetodi al fine di rendere le setPixele getPixelquelli veloci?
Peter Taylor,

1
La bitmap sarà troppo grande per adattarsi alla memoria? Quali dovrebbero essere le operazioni veloci - espansione, setPixel (), getPixel ()?

1
@Falcon: No, c'è abbastanza tempo disponibile
SecStone,

3
Sto votando per chiudere questa domanda come fuori tema perché la domanda dipende fortemente dall'immagine inclusa, che da allora è stata eliminata. Come attualmente scritto, non ha molto senso.

Risposte:


9

Se l' extend()operazione deve essere ragionevolmente veloce, un Quadtree potrebbe essere adatto; in realtà non richiederebbe nemmeno operazioni esplicite di estensione. Certo, non produrrebbe prestazioni ottimali per l'accesso casuale ai singoli pixel, ma il tuo commento afferma che l'operazione primaria sta iterando sui pixel, cosa che un quadrifoglio potrebbe fare molto velocemente, forse quasi quanto un'implementazione basata su matrice (e più veloce se l'iterazione non avviene sempre nello stesso modo in cui è disposta la matrice).

Le tue esigenze suonano come se stessi cercando di implementare un automa cellulare come il Gioco della vita. Potresti dare un'occhiata a Hashlife , un modo estremamente performante per implementare il Gioco della Vita su una griglia infinita. Nota che si basa su un Quadtree, ma offre alcune ottimizzazioni aggiuntive molto intelligenti in base alla località delle regole del gioco.


Grazie per questa idea! Farò alcuni test e riferirò i risultati.
SecStone,

2

@SecStone ha detto che c'è abbastanza tempo disponibile per l'operazione di espansione, quindi il modo più semplice ed efficiente per archiviare i pixel è usare un singolo array piatto o un array bidimensionale, poiché i pixel sono accessibili in tempo costante allora.


4
Vorrei votare questo se darai un buon suggerimento su come pensi che l'espansione dovrebbe essere gestita.
Doc Brown,

@Doc Brown: se c'è abbastanza tempo, sposta l'array. O forse puoi risolvere qualcosa con blocchi e una funzione di traduttore per un indice da punto a array e chunk (che funziona anche a tempo costante).
Falcon,

2

A mano

Se la memoria non è una risorsa molto scarsa, penso di lavorare in blocchi più grandi.
Ecco alcuni pseudo-codici.

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

Questa implementazione è abbastanza ingenua.
Per uno, crea blocchi in getPixel (in pratica sarebbe bene restituire semplicemente 0 o giù di lì, se non fosse stato definito alcun blocco per quella posizione). In secondo luogo, si basa sul presupposto che si disponga di un'implementazione sufficientemente rapida e scalabile di Map. Per quanto ne sappia, ogni lingua decente ne ha una.

Inoltre dovrai giocare con le dimensioni del pezzo. Per bitmap dense, una buona dimensione del pezzo è buona, per bitmap sparse una dimensione del pezzo più piccola è migliore. In effetti per quelli molto sparsi, una "dimensione del blocco" di 1 è la migliore, rendendo obsoleti i "blocchi" e riducendo la struttura dei dati in una mappa int di una mappa int di pixel.

Prêt-à-porter

Un'altra soluzione potrebbe essere quella di esaminare alcune librerie grafiche. Sono in realtà abbastanza bravi a disegnare un buffer 2D in un altro. Ciò significherebbe che dovresti semplicemente allocare un buffer più grande e farti disegnare l'originale nelle coordinate corrispondenti.

Come strategia generale: quando si dispone di un "blocco di memoria a crescita dinamica", è consigliabile allocare un multiplo di esso, una volta esaurito. Questo è piuttosto intenso di memoria, ma riduce significativamente i costi di allocazione e copia . La maggior parte delle implementazioni vettoriali allocano il doppio della loro dimensione, quando viene superata. Quindi, specialmente se si utilizza la soluzione standard, non estendere il buffer solo di 1 pixel, poiché è stato richiesto solo un pixel. La memoria allocata è economica. La riallocazione, la copia e il rilascio sono costosi.


1

Solo un paio di consigli:

  • Se lo implementate come un array di un tipo integrale (o un array di array di ...) probabilmente dovreste aumentare l'array di supporto di un numero di bit / pixel ogni volta per evitare di dover spostare i bit mentre li copiate. Il lato negativo è che usi più spazio, ma la proporzione di spazio sprecato diminuisce man mano che la bitmap diventa più grande.

  • Se si utilizza una struttura di dati basata su mappa, è possibile affinare il problema della crescita della bitmap semplicemente riposizionando gli argomenti delle coordinate x, y delle chiamate getPixele setPixel.

  • Se si utilizza una struttura di dati basata su mappa, sono necessarie solo le voci della mappa per "quelle". Gli "zeri" possono essere indicati dall'assenza di una voce. Ciò consente di risparmiare una notevole quantità di spazio, soprattutto se la bitmap è principalmente zeri.

  • Non è necessario utilizzare una mappa di mappe. È possibile codificare una intcoppia x, y come singola long. Un processo analogo può essere utilizzato per mappare un array di array su un array.


Infine, devi bilanciare 3 cose:

  1. le prestazioni di getPixele setPixel,
  2. lo svolgimento delle extend*operazioni, e
  3. l'utilizzo dello spazio.

1

Prima di provare qualsiasi altra cosa più complicata e, a meno che tu non riesca a tenere tutto in memoria, mantieni le cose semplici e usa un array bidimensionale insieme alle informazioni sull'origine del tuo sistema di coordinate. Per espanderlo, usa la stessa strategia come, ad esempio, lo standard C ++ std :: vector: fai una distinzione tra le dimensioni effettive dell'array e la capacità dell'array ed espandi la capacità in blocchi ogni volta che viene raggiunto il limite. "capacità" qui dovrebbe essere definita in termini di intervalli (da_x, a_x), (da_y, a_y).

Questo potrebbe richiedere una completa riallocazione della memoria di volta in volta, ma finché ciò non accade troppo spesso, potrebbe essere abbastanza veloce per il tuo scopo (in effetti, devi provare / profilare questo).


1

Il modo più veloce in assoluto per accedere ai pixel è una matrice bidimensionale di pixel indirizzabili individualmente.

Per le estensioni, inizia con una semplice implementazione che rialloca e copia ogni volta (poiché avrai comunque bisogno di quel codice). Se la profilazione non indica che ci stai dedicando molto tempo, non è necessario perfezionarla ulteriormente.

Se la profilatura rivela la necessità di mantenere basso il numero di riassegnazioni e non si è vincolati dalla memoria, considerare l'allocazione eccessiva di una percentuale in ciascuna direzione e la memorizzazione di un offset rispetto all'origine. (Ad esempio, se si avvia una nuova bitmap su 1x1 e si alloca un array 9x9 per trattenerlo, l'iniziale xe gli yoffset sarebbero 4.) Il compromesso qui deve fare matematica extra durante l'accesso ai pixel per applicare l'offset.

Se le estensioni risultano essere molto costose, puoi provare una o entrambe queste:

  • Gestire le estensioni verticali e orizzontali in modo diverso. L'estensione di un array verticalmente in qualsiasi direzione può essere ottenuta allocando un nuovo blocco ed eseguendo una singola copia dell'intero array esistente sull'offset appropriato nella nuova memoria. Confrontalo con le estensioni orizzontali, dove devi fare quell'operazione una volta per riga perché i dati esistenti non sono contigui nel nuovo blocco.

  • Tieni traccia della quantità e della direzione dell'estensione più frequenti. Utilizzare tali informazioni per selezionare una nuova dimensione e offset che ridurranno la probabilità di dover effettuare una nuova allocazione e copia per qualsiasi estensione.

Personalmente, dubito che avrai bisogno di uno di questi a meno che il rapporto pixel-access-to-extension sia basso.


1
  • Dimensione costante piastrelle (ad esempio, 256x256, ma con un numero infinito di piastrelle)
  • Fornisci un wrapper bitmap che consenta coordinate pixel negative (per dare l'impressione che un'immagine possa essere espansa in tutte e quattro le direzioni senza dover ricalcolare / sincronizzare i riferimenti ai valori di coordinate esistenti)
    • La classe bitmap effettiva (che funziona sotto il wrapper), tuttavia, dovrebbe supportare solo coordinate assolute (non negative).
  • Sotto il wrapper, fornire l'accesso a livello di riquadro (a livello di blocco) utilizzando l'I / O mappato in memoria
  • Oltre a Map.setPixel()e Map.getPixel()che modifica un singolo pixel alla volta, fornisce anche metodi che copiano e modificano un rettangolo di pixel alla volta. Ciò consentirà al chiamante di scegliere la forma di accesso più efficiente in base alle informazioni disponibili per il chiamante.
    • Le librerie commerciali forniscono anche metodi per l'aggiornamento: una riga di pixel, una colonna di pixel, aggiornamenti scatter / gather e operazioni di blitter aritmetico / logico in un unico passaggio (per ridurre al minimo la copia dei dati).

(Non valorizziamo le risposte esilaranti ...)


0

L'implementazione più flessibile e forse affidabile è un elenco collegato con strutture che forniscono coordinate x, coordinate y e valore bit. Prima lo costruivo e lo facevo funzionare.

Quindi, se è troppo lento e / o grande, prova i soliti modi per accelerarlo: array, matrice, bitmap, compressione, cache, inversione, memorizzazione solo di valori "1", ecc.

È più semplice eseguire un'implementazione lenta e corretta più velocemente di quanto non sia correggere un'implementazione rapida con errori. E mentre testate la vostra seconda implementazione "veloce", avete uno standard di riferimento per confrontarlo.

E, chissà, probabilmente scoprirai che la versione lenta è abbastanza veloce. Finché l'intera struttura si adatta alla memoria, le cose sono già incredibilmente veloci.


3
-1: "farlo funzionare prima di farlo veloce" non è una buona ragione per iniziare con la peggiore implementazione possibile. Inoltre, non ci sarà praticamente alcun codice che non dovrà cambiare completamente con la struttura di dati sottostante, quindi l'iterazione a quel livello è un suggerimento completamente asinino qui.
Michael Borgwardt,

Ecco perché hai un'API. L'API nasconde l'implementazione sottostante. SetValue (MyMatrix, X, Y, Value) e GetValue (MyMatrix, X, Y) nasconde se MyMatrix è un array dimensionale 1 o 2, o un elenco collegato, o memorizzato nella cache su disco, o una tabella SQL o altro. Potrebbe essere necessario ricompilare il chiamante, ma non modificare il codice.
Andy Canfield,

0

Propongo la seguente implementazione in Python:

class Map(dict): pass

Ha i seguenti vantaggi:

  1. È map[(1,2)]possibile prendere in considerazione l' accesso / impostazione tramite O(1).
  2. La necessità di estendere esplicitamente la griglia svanisce.
  3. C'è poco spazio per i bug.
  4. È facilmente aggiornato a 3D, se mai necessario.

0

Se in realtà hai bisogno di una bitmap di qualsiasi dimensione arbitraria e intendo qualsiasi cosa da 1x1 a 1000000x1000000 e ho bisogno di espandibile su richiesta ... un modo possibile è utilizzare un database. All'inizio può sembrare contro intuitivo, ma quello che hai davvero è un problema di archiviazione. Un database ti permetterà di accedere a singoli pixel e di archiviare praticamente qualsiasi quantità di dati. Non intendo necessariamente un db SQL, tra l'altro.

Sarà abbastanza veloce per i tuoi scopi? Non posso rispondere, dato che qui non c'è alcun contesto riguardo a cosa stai facendo con questa bitmap. Ma se questo è per la visualizzazione dello schermo, considera che in genere dovrai solo ritirare le righe di scansione aggiuntive per visualizzarle mentre scorre lo schermo, non tutti i dati.

Detto questo, non posso fare a meno di chiedermi se stai sbagliando qualcosa. Forse dovresti invece utilizzare la grafica vettoriale e tenere traccia delle singole entità in memoria e quindi renderizzare una bitmap grande quanto basta per lo schermo?


Forse un server di mappe OSGeo.
rwong

0

Ecco i passaggi per farlo funzionare correttamente:

  1. usa l'inoltro da un'interfaccia all'altra per costruire un albero di oggetti che insieme rappresentano la bitmap
  2. ogni "estensione" della bitmap alloca la propria memoria e fornisce un'altra interfaccia per essa
  3. esempio di implementazione sarebbe:

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

Ovviamente per una vera implementazione, XSize () e YSize () non funzionerebbero, ma avrai bisogno di MinX (), MaxX (), MinY (), MaxY () o qualcosa del genere per mantenere coerenti i numeri di indice.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.