2011-05-08 26 views
7

Estoy creando un visualizador de imágenes que abre imágenes grandes (2 gb +) en Qt. Estoy haciendo esto al dividir la imagen grande en varios mosaicos de 512X512. Luego cargo un QGraphicsScene del tamaño de imagen original y uso addPixmap para agregar cada mosaico a la escena QGraphic. Por lo tanto, en última instancia, parece una gran imagen para el usuario final cuando en realidad se trata de una serie continua de imágenes más pequeñas pegadas en la escena. En primer lugar, ¿es este un buen enfoque?Mosaico con QGraphicsScene y QGraphicsView

Intentar cargar todas las fichas en la escena ocupa una gran cantidad de memoria. Así que estoy pensando en solo cargar las fichas que son visibles en la vista. Ya he logrado subclasificar QGraphicsScene y anular su evento de arrastre, lo que me permite saber qué teselas se deben cargar a continuación según el movimiento. Mi problema es rastrear el movimiento en las barras de desplazamiento. ¿Hay alguna manera de que pueda crear un evento que se llame cada vez que se mueve la barra de desplazamiento? Subclasificar QGraphicsView no es una opción.

Respuesta

7

QGraphicsScene es suficiente para no hacer lo que no es visible inteligente, así que esto es lo que hay que hacer:

En lugar de carga y mapas de píxeles añadiendo, agregue clases que envuelven el mapa de píxeles, y la única carga cuando primero son rendidos. (A los informáticos les gusta llamarlo "patrón de proxy"). A continuación, puede descargar el mapa de píxeles en función de un temporizador. (Se volverían a cargar de forma transparente si se descargaban demasiado pronto). Incluso podría notificar a esta ruta proxy el nivel de zoom actual, de modo que cargue imágenes de menor resolución cuando se vuelvan más pequeñas.


Editar: aquí hay un código para empezar. Tenga en cuenta que todo lo que QGraphicsScene empates es un QGraphicsItem, (si se llama ::addPixmap, se convierte en un ...GraphicsItem detrás de las escenas), así que eso es lo que quiere subclase:

(Ni siquiera he recopilado esta, por lo que "advertencia lector", pero se está haciendo lo correcto;)

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

entonces usted puede añadir sus mapas de píxeles a la QGraphicsScene usando QGraphicsScene::addItem(...)

+0

Esto es genial. Ahorraría muchísimo trabajo. Me preguntaba si esto sería lento o tartamudea mientras se desplaza. Lo que había planeado originalmente era hacer un seguimiento de lo que el usuario está viendo con el mouse y el evento de la barra de desplazamiento y luego cargar los mosaicos circundantes y colocarlos en la escena con anticipación usando un hilo separado. A medida que el usuario se mueve a través de la escena, el hilo de fondo simplemente cargará y descargará los mosaicos en consecuencia. ¿La pintura de QGraphicsItem ya hace esto en el fondo? –

+1

Si desea cargar un hilo separado, deberá enviar una señal desde su evento de pintura a un hilo de trabajo en lugar de cargar la imagen, y luego, cuando se llame a pintura, dibuje algún tipo de imagen en blanco hasta que el hilo lo haya cargado . Usted * podría * devolver un tamaño grande que el real para el 'boundingRect()' para desencadenar eventos de pintura antes de que los mosaicos sean realmente visibles. Pero esto podría tener otros efectos secundarios si no tienes cuidado. – James

+0

Gracias, has sido de gran ayuda. Tengo la carga de baldosas casi terminada. Ahora, ¿cuál es la mejor forma de descargar los mosaicos? Por lo que entendí de su publicación anterior, sugirió que ejecutara un temporizador que destruye cada elemento de mapa de píxeles en la escena y lo establece en NULL cada pocos segundos. Y dado que se llama al método de pintura para cada actualización, restauraría automáticamente los mosaicos que se están viendo actualmente. ¿Estoy en lo cierto? –

3

a menos que sea absolutamente necesario el fin de ser un QGraphicsView (por ejemplo, porque se coloca otros objetos en la parte superior del mapa de fondo de fondo grande), realmente recomendaría simplemente subclasificar QAbstractScrollArea y volver a implementar scrollContentsBy() y paintEvent().

Agregue una caché LRU de pixmaps (consulte QPixmapCache para obtener inspiración, aunque esa es global), y haga que paintEvent() extraiga los mapas de píxeles usados ​​y configúrelos.

Si esto suena como más trabajo que el QGraphicsItem, créeme, no es :)

+0

Bueno, desde un punto de vista de desarrollo futuro, tendré que agregar una superposición vectorial sobre mis datos ráster en algún punto del tiempo. Necesitaré muchas funciones de dibujo de formas para dibujar líneas y polígonos. QGraphicsView parece tener muchas cosas disponibles de fábrica. Así que me estoy quedando con QGraphicsView por ahora –

4

Aunque una respuesta ya ha sido elegido, me gustaría expresar mi opinión.

No me gusta la respuesta seleccionada, especialmente por el uso de temporizadores. ¿Un temporizador para descargar los pixmaps? Supongamos que el usuario realmente desea echar un buen vistazo a la imagen, y después de unos segundos, bam, la imagen está descargada, tendrá que hacer algo para que la imagen vuelva a aparecer. ¿O puede ser que va a poner otro temporizador, que carga los pixmaps después de otro par de segundos? ¿O consultará entre sus miles de artículos si son visibles? Esto no solo es muy irritante e incorrecto, sino que significa que su programa utilizará recursos todo el tiempo.Supongamos que el usuario minimiza su programa y reproduce una película, se preguntará por qué demonios mi película se congela cada dos segundos ...

Bueno, si malinterpreté la idea propuesta de usar temporizadores, execuseme.

En realidad, la idea que mmutz sugirió es mejor. Me recordó el Mandelbrot example. Mira esto. En lugar de calcular qué dibujar, puede reescribir esta parte para cargar la parte de la imagen que necesita mostrar.

En conclusión voy a proponer otra solución utilizando QGraphicsView de una manera mucho más simple:

1) comprobar el tamaño de la imagen sin cargar la imagen (usar QImageReader)

2) hacer que el tamaño de su escena igual al de la imagen

3) en lugar de utilizar elementos de pixmap, vuelva a implementar la función DrawBackground(). Uno de los parámetros le dará el nuevo rectángulo expuesto, lo que significa que si el usuario se desplaza solo un poco, solo cargará y dibujará esta nueva parte (para cargar solo parte de una imagen use los métodos setClipRect() y read() de la clase QImageReader). Si hay algunas transformaciones, puede obtenerlas del otro parámetro (que es QPainter) y aplicarlas a la imagen antes de dibujarla.

En mi opinión, la mejor solución será combinar mi solución con la rosca que se muestra en el Mandelbrot example.

El único problema que se me ocurre ahora es si el usuario se aleja con un factor de gran escala. Entonces necesitará una gran cantidad de recursos por algún tiempo para cargar y escalar una imagen enorme. Bueno, ahora veo que hay alguna función de QImageReader que aún no he probado: setScaledSize(), que tal vez haga exactamente lo que necesitamos: si configuras un tamaño de escala y luego cargas la imagen, tal vez no se cargue primero toda la imagen, pruébalo Otra forma es limitar el factor de escala, algo que deberías hacer de todos modos si te apegas al método con los elementos del mapa de píxeles.

Espero que esto ayude.

+0

En realidad, la sugerencia de Autopulated de subclasificar QGraphicsItem y sobrecargar pintura funcionó muy bien. Significa que puedo pegar QGraphicsItem vacío en la escena. Esto no debería ocupar mucha memoria. El método de pintura de un QGraphicsItem se llama solo cuando está visible dentro de la vista. Así que lo he implementado de manera que sus datos de imagen reales solo se carguen en el objeto cuando esté visible en la vista. Así que cargar las fichas es bastante cuidado. Ahora el único problema permanece en la descarga de teselas no utilizadas. –

+0

Después de considerar varios enfoques, creo que voy a usar un enfoque Queue para los mosaicos. Mantendré una cola de tamaño constante que toma un nuevo mosaico y elimina fichas antiguas (FIFO). Esto debería ser mucho mejor que usar un temporizador. ¿Qué piensas? –

+0

Y he considerado el problema del zoom desde el principio. Lo que he planeado hacer es construir teselas de varios niveles de zoom (como nivel de mosaico 0 -> 100% nivel 1-> 50%, etc.). Siempre que el usuario esté haciendo zoom entre 50-100% de zoom, usaré la función setScale en QGraphicsView (Otra ventaja de usar QGraphicsView sobre QAbstractScrollArea). Una vez que el usuario entra en el nivel de zoom del 25-50%, se carga un nuevo juego de fichas (fichas de nivel 1) y la escala vuelve a 1. En resumen, la piramidación debería resolver esto. También la pintura predominante en QGraphicsItem me permite integrar perfectamente el zoom piramidal –

Cuestiones relacionadas