2011-05-08 8 views
7

Sto creando un visualizzatore di immagini che apre immagini grandi (2 gb +) in Qt. Lo sto facendo suddividendo l'immagine grande in più riquadri di 512X512. Carico quindi una QGraphicsScene della dimensione originale dell'immagine e uso addPixmap per aggiungere ciascuna tessera alla scena QGraphic. Quindi alla fine sembra un'enorme immagine per l'utente finale quando in realtà è una serie continua di immagini più piccole attaccate insieme sulla scena. Prima di tutto questo è un buon approccio?Piastrellatura con QGraphicsScene e QGraphicsView

Provare a caricare tutte le tessere sulla scena richiede molta memoria. Quindi sto pensando di caricare solo le tessere visibili nella vista. Sono già riuscito a suddividere QGraphicsScene e a scavalcare il suo evento di trascinamento, permettendomi così di sapere quali tessere devono essere caricate successivamente in base al movimento. Il mio problema è il monitoraggio del movimento sulle barre di scorrimento. C'è un modo per creare un evento che viene chiamato ogni volta che si sposta la barra di scorrimento. Sottoclasse QGraphicsView in non un'opzione.

risposta

7

QGraphicsScene basta non rendere ciò che non è visibile intelligente, quindi ecco cosa devi fare:

Invece di carico e pixmaps aggiunta, aggiungere le classi che avvolgono il pixmap, e solo caricare quando sono prima resi. (Gli informatici amano definirlo un "modello proxy"). È quindi possibile scaricare la pixmap in base a un timer. (Verrebbero re-caricati in modo trasparente se scaricati troppo presto.) Potresti anche notificare questo percorso del proxy del livello di zoom corrente, in modo che carichi immagini con una risoluzione inferiore quando saranno renderizzate più piccole.


Modifica: ecco un codice per iniziare. Si noti che tutto ciò che QGraphicsScene disegna è un QGraphicsItem, (se si chiama ::addPixmap, è convertito in un ...GraphicsItem dietro le quinte), ed è quello che si desidera creare una sottoclasse:

(non ho ancora compilato questa, in modo da "caveat lector", ma che sta facendo la cosa giusta;)

class MyPixmap: public QGraphicsItem{ 
    public: 
     // make sure to set `item` to NULL in the constructor 
     MyPixmap() 
      : QGraphicsItem(...), item(NULL){ 
     } 

     // you will need to add a destructor 
     // (and probably a copy constructor and assignment operator) 

     QRectF boundingRect() const{ 
      // return the size 
      return QRectF(...); 
     } 

     void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, 
        QWidget *widget){ 
      if(NULL == item){ 
       // load item: 
       item = new QGraphicsPixmapItem(...); 
      } 
      item->paint(painter, option, widget); 
     } 

    private: 
     // you'll probably want to store information about where you're 
     // going to load the pixmap from, too 

     QGraphicsPixmapItem *item; 
}; 

quindi è possibile aggiungere le mappe di pixel al QGraphicsScene utilizzando QGraphicsScene::addItem(...)

+0

Questo è fantastico. Salverebbe un sacco di lavoro. Mi stavo chiedendo se sarebbe stato lento o balbettante durante lo scorrimento. Quello che avevo inizialmente programmato era quello di tenere traccia di ciò che l'utente sta visualizzando usando l'evento mouse e scrollbar e quindi caricare le tessere circostanti e posizionarle sulla scena in anticipo usando un thread separato. Mentre l'utente si muove attraverso la scena, il thread in background semplicemente carica e scarica le tessere di conseguenza. QGraphicsItem dipinge già in background? –

+1

Se volevi caricare in un thread separato dovresti inviare un segnale dal tuo evento paint a un thread worker invece di caricare l'immagine, e poi ogni volta che paint viene chiamato traccia una specie di immagine vuota finché il thread non lo ha caricato . Potresti * restituire una dimensione più grande di quella effettiva per 'boundingRect()' per attivare gli eventi di disegno prima che le tessere siano effettivamente visibili. Ma questo potrebbe avere altri effetti collaterali se non stai attento. – James

+0

Grazie, sei stato di grande aiuto. Ho ottenuto il caricamento delle mattonelle quasi finito. Ora qual è il modo migliore per scaricare le tessere. Da quanto ho capito dal tuo post precedente, hai suggerito di eseguire un timer che distrugge ogni PixmapItem nella scena e lo imposta su NULL ogni pochi secondi. E poiché il metodo paint viene chiamato per ogni aggiornamento, ripristinerebbe automaticamente le tessere che sono attualmente visualizzate. Ho ragione? –

3

a meno che non è assolutamente necessario al fine di essere un QGraphicsView (ad esempio perché si inserisce altri oggettiin cima alla grande pixmap di sfondo), consiglio davvero di creare sottoclassi QAbstractScrollArea e di reimplementare scrollContentsBy() e paintEvent().

Aggiungere una cache LRU di pixmaps (vedere QPixmapCache per l'ispirazione, sebbene quella sia globale) e fare in modo che lo paintEvent() utilizzi pixmap in primo piano e vengano impostate.

Se questo suona come più lavoro rispetto alla QGraphicsItem, mi creda, non è :)

+0

Bene da un punto di vista dello sviluppo futuro, avrò bisogno di aggiungere una sovrapposizione vettoriale sui miei dati raster in un determinato momento. Quindi avrò bisogno di molte funzioni di disegno di forme per disegnare linee e poligoni. QGraphicsView sembra avere un sacco di cose disponibili fuori dalla scatola. Quindi mi sto attenendo a QGraphicsView per ora –

4

Anche se una risposta è già stato scelto, vorrei esprimere il mio parere.

Non mi piace la risposta selezionata, soprattutto a causa dell'uso dei timer. Un timer per scaricare le pixmaps? Dite che l'utente vuole effettivamente dare una buona occhiata all'immagine, e dopo un paio di secondi - bam, l'immagine viene scaricata, dovrà fare qualcosa in modo che l'immagine riappaia. Oppure potresti mettere un altro timer, che carica le pixmap dopo un altro paio di secondi? O controllerai tra i tuoi mille oggetti se sono visibili? Non solo è molto irritante e sbagliato, ma significa che il tuo programma userà sempre risorse.Supponiamo che l'utente minimizzi il programma e riproduca un film, si chiederà perché diavolo il mio film si blocca ogni paio di secondi ...

Bene, se ho frainteso l'idea proposta di usare i timer, eseguimi.

In realtà l'idea suggerita da mmutz è migliore. Mi ha ricordato lo Mandelbrot example. Dai un'occhiata a questo. Invece di calcolare cosa disegnare, puoi riscrivere questa parte per caricare quella parte dell'immagine che devi mostrare.

In conclusione vi proporrò un'altra soluzione utilizzando QGraphicsView in un modo molto più semplice:

1) controllare le dimensioni dell'immagine senza caricare l'immagine (utilizzare QImageReader)

2) fare il formato del vostro scena uguale a quello dell'immagine

3) invece di utilizzare elementi pixmap reimplementare la funzione DrawBackground(). Uno dei parametri ti darà il nuovo rettangolo esposto - il che significa che se l'utente scorre solo un po ', caricerai e disegnerai solo questa nuova parte (per caricare solo parte di un'immagine usa i metodi setClipRect() e read() della classe QImageReader). Se ci sono delle trasformazioni puoi ottenerle dall'altra parametro (che è QPainter) e applicarle all'immagine prima di disegnarla.

A mio parere la soluzione migliore sarà combinare la mia soluzione con la filettatura mostrata nello Mandelbrot example.

L'unico problema a cui riesco a pensare ora è se l'utente ingrandisce con un fattore di scala elevato. Quindi avrai bisogno di un sacco di risorse per un po 'di tempo per caricare e ridimensionare un'immagine enorme. Bene, ora vedo che c'è una funzione del QImageReader che non ho ancora provato - setScaledSize(), che forse fa proprio quello di cui abbiamo bisogno - se imposti una dimensione di scala e poi carichi l'immagine forse non verrà caricata prima l'intera immagine - provalo. Un altro modo è solo per limitare il fattore di scala, una cosa che dovresti fare comunque se ti attieni al metodo con gli oggetti pixmap.

Spero che questo aiuti.

+0

In realtà, il suggerimento di SubClass di QGraphicsItem e la vernice di sovraccarico hanno funzionato molto bene. Significa che posso incollare QGraphicsItem vuoto sulla scena. Questo non dovrebbe richiedere molta memoria. Il metodo paint di un oggetto QGraphics viene chiamato solo quando è visibile all'interno della vista. Quindi l'ho implementato in modo tale che i dati dell'immagine reale vengano caricati nell'oggetto solo quando è visibile nella vista. Quindi caricare le tessere è praticamente risolto. Ora l'unico problema rimane nello scaricare le piastrelle inutilizzate. –

+0

Dopo aver considerato diversi approcci, penso che userò un approccio in coda alle tessere. Terrò una coda di dimensioni costanti che prende una tessera nuova e cancella le tessere vecchie (FIFO). Questo dovrebbe essere molto meglio di usare un timer. Cosa ne pensi? –

+0

E ho considerato il problema dello zoom nelle prime fasi. Quello che ho pianificato di fare è costruire tessere con diversi livelli di zoom (come livello piastrella 0 -> 100% livello 1-> 50% ecc.). Finché l'utente sta eseguendo lo zoom tra il 50-100% dello zoom userò la funzione setScale in QGraphicsView (Un altro vantaggio dell'utilizzo di QGraphicsView su QAbstractScrollArea). Una volta che l'utente entra nel livello di zoom 25-50%, viene caricato un nuovo set di tessere (livello 1 tessere) e la scala viene impostata su 1. In breve la piramide dovrebbe risolvere questo problema. Anche la sovrascrittura della vernice in QGraphicsItem mi consente di integrare senza problemi lo zoom piramidale –

Problemi correlati