Mipmapping for canvas. Ideas and comments.

Boudewijn Rempt boud at valdyas.org
Wed Jul 22 08:28:27 CEST 2009

On Tuesday 21 July 2009, Dmitry Kazakov wrote:

> * The second  - in  KisPrescaledProjection. We merge  original images,
>  then create a pyramid of a projection and scale at last.

It's actually two steps: we have a projection that is optionally (and the 
default is on) cached at 100% in one big QImage, and then there is the 
prescaled projection.

Your work probably should be done at the projection level, not the prescaled 
projection level; the prescaled projection is only used to move pixels as 
quickly as possible to the canvas, and it is never bigger than the canvas size 
in pixels.

Now there is a second issue: the opengl canvas. The opengl canvas doesn't use 
the 100% QImage projection cache, nor the prescaled projection, but divides 
the projection into textures and those textures are fed to the gpu.

I actually think you are working at too high a level here: I think a 
KisProjectionPyramid should be in krita/image, not krita/ui. Then the 
prescaled projection would resquest a QImage for the updated area at a certain 
zoom level, and the KMMP would return that, computed from the nearest tile.

Question: have you taken a look at the way Gimp 2.6 has implemented its 
display pyramid? It is fairly simple and might be very instructive.

I'd suggest having KisProjectionPyramid in krita/image. So, if an area is 
recomposited, KisImage emits an sigImageUpdated(const QRect& rc) signal. It is 
connected to KisProjectionPyramid, which starts recomputing the pyramid for 
the tiles in the affected area (you have to have tiles here, you cannot cache 
the entire image in a QImage for every level). Through KisCanvas2, 
KisPrescaledProjection is notified and asks KisProjectionPyramid for the 
udpated area at the correct scaling level. KisProjectionPyramid takes the 
projection at the right mipmap level and returns a QImage at exactly the right 
zoom level, with KisPrescaledPProjection pastes in its cache and then asks the 
canvas to update.

> Pros:
>      + not so much overhead as we create only one pyramid for a view
>      + zooming works fast thanks to the pyramid
>      + no scale-merge artefacts.
>      + no problems with alignment
> Cons:
>      - no help to filter layers

Yes, that sucks. But the if we implement region-of-interest in KisProjection, 
then we'd only filter the visible area of an image, and if the image isn't 
scaled down too much, it won't be too bad. I wonder whether GEGL optimizes for 

> I think the  second variant is better. At least  for the beginning. So
> i'm going to add it to KisPrescaledProjection. More than that, i could
> try to make this image pyramid as encapsulated of the canvas subsystem
> as possible,  so in fueature it could  be ported to a  paint device if
> needed, or even made as a template.
> As i investigated, KisPrescaledProjection have three main entry points
> (input methods):

The real task of the prescaledprojection is have a QImage as big as the 
canvas, scaled to the current zoom level that can be used to blt to the 
QPainter canvas. It doesn't work with the OpenGL canvas (it is debatable 
whether it is necessary there, but the OpenGL canvas scaling down is pretty 
bad now because it uses simple nearest neighbour).

> ->updateCanvasProjection(rect)
> ->preScale()
> ->resizePrescaledImage(size)
> These methods fit for image pyramid very well:
> updateCanvasProjection()  would start a  thread that  would eventually
> update pyramid
> preScale() would request scaled image from pyramid.
> It would look like:
> QPainter gc(m_prescaledQImage);
> ...
> m_imagePyramid.drawScaledImage(gc, ..., ..., scale);
> Here is a mockup of an interface of a KisImagePyramidClass:
> class KisImagePyramid {
> public:
>        void updateCanvasProjection(QRect rect);
>        /**
>         * Here are some problems with scaleX/scaleY
>         * as for image pyramid they should be equal
>         */
>        void drawScaledImage(QPainter gc,
>             QPointF topLeftScaled /* target topLeft point */,
>             QRect rectUnscaled /* source rect in image */,
>             qreal scale);
> private:
>        QVector<QImage> m_pyramid;
>        KisProjectionCache /* or QImage */ m_original;
>        QThreadPool m_pyramidUpdater;
> };
> What do you think about this idea? Maybe i've missed something?
> As always, comments are welcome! =)
> PS:
> There are some points where i'm in doubt now:
> 1)  Should we  use KisProjectionCache  for storing  original  image?
> I  guess not,  because  it  could  be used  much  in
> drawScaledImage much.
> 2) Which  zoom-levels should  be stored inside  m_pyramid? I  saw that
> when you  press Ctrl+'+'/'-' Krita switches across  some finite number
> of levels. Which part of Krita/Koffice decides, which levels to use? I
> guess, these levels and levels in m_pyramid should agree :)

Traditionally, you'd take steps of 50%, so: 100, 50, 25, 12.5, I believe

Boudewijn Rempt | http://www.valdyas.org

More information about the kimageshop mailing list