Bitmap infinita [chiusa]

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.

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

Tra e dopo i comandi mostrati nell'immagine, 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.

    
posta SecStone 30.08.2011 - 09:10
fonte

11 risposte

9

Se l'operazione extend() deve essere ragionevolmente veloce, un Quadtree potrebbe essere una buona idea; in realtà non richiederebbe nemmeno operazioni di estensione esplicite. Certo, non produrrebbe prestazioni ottimali per l'accesso casuale ai singoli pixel, ma il tuo commento dice che l'operazione principale è iterare sui pixel, che un quadruplo potrebbe fare molto velocemente, forse quasi alla stessa velocità di un implementazione basata su matrice (e più veloce se l'iterazione non avviene sempre nello stesso modo in cui la matrice è strutturata).

Le tue esigenze sembrano davvero come se stessimo 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 fa alcune ottimizzazioni aggiuntive molto intelligenti basate sulla localizzazione delle regole del gioco.

    
risposta data 30.08.2011 - 10:20
fonte
2

@SecStone ha detto che c'è abbastanza tempo a disposizione per l'operazione di espansione, quindi il modo più semplice ed efficiente per archiviare i pixel consiste nell'utilizzare un singolo array piatto o un array bidimensionale, poiché è possibile accedere ai pixel in un tempo costante.

    
risposta data 30.08.2011 - 09:29
fonte
2

A mano

Se la memoria non è una risorsa molto sparsa, considero di lavorare in blocchi più grandi.
Ecco alcuni pseudo-codice.

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 esempio, crea blocchi in getPixel (in pratica sarebbe meglio restituire 0 o così, se non fosse stato definito alcun blocco per quella posizione). In secondo luogo, si basa sul presupposto che si disponga di un'implementazione della mappa sufficientemente rapida e scalabile. A mia conoscenza, ogni lingua decente ne ha uno.

Inoltre dovrai giocare con la dimensione del blocco. Per bitmap densi, una grande dimensione del blocco è buona, per bitmap sparse una dimensione del blocco più piccola è migliore. Infatti per quelli molto sparsi, una "dimensione del blocco" di 1 è la migliore, rendendo i "blocchi" stessi obsoleti e riducendo la struttura dei dati a una mappa int di una mappa int di pixel.

Off the shelf

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

Come strategia generale: quando si ha un "blocco di memoria a crescita dinamica", è una buona idea allocarne un multiplo, una volta esaurito. Questo è piuttosto intenso nella memoria, ma riduce significativamente i costi di allocazione e copia . La maggior parte delle implementazioni di vettori assegnano il doppio delle loro dimensioni, quando viene superata. Quindi, soprattutto se si utilizza la soluzione standard, non estendere il buffer solo di 1 pixel, poiché è stato richiesto un solo pixel. La memoria allocata è economica. La riallocazione, la copia e il rilascio sono costosi.

    
risposta data 30.08.2011 - 11:00
fonte
1

Solo un paio di consigli:

  • Se si implementa questo come array di un tipo integrale (o di un array di matrici di ...) probabilmente si dovrebbe aumentare l'array di supporto di un numero di bit / pixel ogni volta per evitare di dover spostare il bit come li copi. Il lato negativo è che usi più spazio, ma la percentuale di spazio sprecato diminuisce man mano che la bitmap diventa più grande.

  • Se si utilizza una struttura dati basata sulla mappa, è possibile risolvere il problema della crescita della bitmap semplicemente riposizionando gli argomenti delle coordinate x, y delle chiamate getPixel e setPixel .

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

  • Non è necessario utilizzare una mappa di Maps. Puoi codificare una int x, una coppia y come un singolo long . Un processo analogo può essere utilizzato per mappare un array di array in un array.

Infine, devi bilanciare 3 cose:

  1. il rendimento di getPixel e setPixel ,
  2. il rendimento delle operazioni extend* e
  3. l'utilizzo dello spazio.
risposta data 30.08.2011 - 11:18
fonte
1

Prima di provare qualcos'altro più complicato, e a meno che non sia possibile tenere tutto in memoria, mantieni semplici le cose e usa un array bidimensionale insieme alle informazioni sull'origine del tuo sistema di coordinate. Per espanderlo, usa la stessa strategia come, per esempio, lo std :: vector di C ++: fai una distinzione tra la dimensione effettiva dell'array e la capacità dell'array, ed espandi la capacità in blocchi ogni volta che viene raggiunto il limite. la "capacità" qui dovrebbe essere definita in termini di intervalli (from_x, to_x), (from_y, to_y).

Potrebbe essere necessario un completo riallineamento della memoria di volta in volta, ma fintanto che ciò non accade troppo spesso, potrebbe essere abbastanza veloce per il tuo scopo (in effetti, devi provare / profilare questo).

    
risposta data 30.08.2011 - 11:20
fonte
1

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

Per le estensioni, inizia con una semplice implementazione che rialloca e copia ogni volta (dal momento che avrai comunque bisogno di quel codice). Se la creazione di profili non indica che trascorri molto tempo, non è necessario perfezionarla ulteriormente.

Se la profilazione rivela la necessità di mantenere il numero di ridistribuzioni verso il basso e non si è vincolati alla memoria, si consideri la sovra-allocazione 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 una matrice 9x9 per mantenerla, gli offset iniziale x e y sarebbero 4 .) Il compromesso qui è dover fare matematica extra durante il pixel accesso per applicare l'offset.

Se le estensioni risultano veramente costose, puoi provare una o entrambe:

  • Gestisci le estensioni verticali e orizzontali in modo diverso. Estendere un array verticalmente in qualsiasi direzione può essere ottenuto assegnando un nuovo blocco e facendo una singola copia dell'intero array esistente all'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 più frequenti dell'estensione. Utilizza queste informazioni per selezionare una nuova dimensione e un offset che ridurranno la probabilità di dover effettuare una nuova allocazione e copia per qualsiasi estensione.

Personalmente, dubito che avrai bisogno di entrambi quelli a meno che il rapporto pixel-accesso-estensione sia basso.

    
risposta data 30.08.2011 - 13:34
fonte
1
  • Dimensione costante piastrellatura (ad esempio, 256x256, ma con un numero infinito di tessere)
  • Fornisci un wrapper bitmap che consente le 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 delle coordinate esistenti)
    • La classe bitmap effettiva (che lavora sotto il wrapper), tuttavia, dovrebbe supportare solo le coordinate assolute (non negative).
  • Sotto il wrapper, fornire un accesso a livello di blocco (a livello di blocco) utilizzando I / O mappato in memoria
  • Oltre a Map.setPixel() e Map.getPixel() che modifica un singolo pixel alla volta, fornisci anche metodi che copiano e modificano un rettangolo di pixel alla volta. Ciò consentirà al chiamante di scegliere la forma più efficiente di accesso in base alle informazioni disponibili per il chiamante.
    • Le librerie commerciali forniscono anche metodi per aggiornare: una riga di pixel, una colonna di pixel, gli aggiornamenti scatter / gather e le operazioni aritmetiche / logiche blitter in un unico passaggio (per ridurre al minimo la copia dei dati).

(Non sovvertire le risposte esilaranti ...)

    
risposta data 30.08.2011 - 21:42
fonte
0

L'implementazione più flessibile e probabilmente affidabile è un elenco collegato con strutture che forniscono la coordinata x, la coordinata y e il valore di bit. Lo avrei costruito per primo e funzionato.

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

È più facile realizzare un'implementazione corretta e lenta più veloce di quella di correggere un'implementazione rapida e buggata. E mentre collaudi la tua seconda implementazione 'veloce' hai uno standard di riferimento per confrontarlo con.

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

    
risposta data 30.08.2011 - 09:50
fonte
0

Propongo la seguente implementazione in Python:

class Map(dict): pass

Ha i seguenti vantaggi:

  1. Ottieni / Imposta accesso tramite map[(1,2)] può essere considerato O(1) .
  2. La necessità di estendere esplicitamente la griglia scompare.
  3. C'è poco spazio per i bug.
  4. È facilmente aggiornato in 3D, se necessario.
risposta data 30.08.2011 - 16:21
fonte
0

Se in realtà hai bisogno di una bitmap di qualsiasi dimensione arbitraria - e intendo qualsiasi cosa da 1x1 a 1000000x1000000 amd up, e ne hai bisogno espandibile su richiesta ... un modo possibile è usare un database. All'inizio può sembrare controintuitivo, ma quello che hai veramente è un problema di archiviazione. Un database ti consentirà di accedere a singoli pixel e di archiviare in modo sostanziale qualsiasi quantità di dati. Non intendo necessariamente un db SQL, btw.

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

Detto questo, non posso fare a meno di chiedermi se stai facendo qualcosa di sbagliato. Dovresti forse invece usare la grafica vettoriale e tracciare le singole entità in memoria, e quindi rendere una bitmap grande quanto necessario per lo schermo?

    
risposta data 30.08.2011 - 20:20
fonte
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 verrà allocata al proprio memoria e fornisci un'altra interfaccia per esso
  3. l'implementazione di esempio potrebbe essere:

    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 l'implementazione reale, XSize () e YSize () non funzionerebbero, ma avrete bisogno di MinX (), MaxX (), MinY (), MaxY () o qualcosa del genere per mantenere i numeri indice coerenti.

    
risposta data 30.08.2011 - 22:31
fonte

Leggi altre domande sui tag