[prev in list] [next in list] [prev in thread] [next in thread] 

List:       kde-kimageshop
Subject:    Re: Scaling algorithms in Krita Bad (at least at downscaling)
From:       Boudewijn Rempt <boud () valdyas ! org>
Date:       2006-11-13 6:40:32
Message-ID: 200611130740.36494.boud () valdyas ! org
[Download RAW message or body]

[Attachment #2 (multipart/signed)]

[Attachment #4 (multipart/mixed)]


On Sunday 12 November 2006 17:39, Boudewijn Rempt wrote:

> I've got the distinct impression that the quality of our scaling is worse
> than it used to be.

For now, I've re-added Michael Thaler's special scaling code and special cased
it for rgba8, cmyk8 and graya8. The results are, as far as I can see, superior
to nearly everything else. Diff attached in case Cyrille wants to add this to 
the tag for 1.6.1 (which I'd advise, because it's tested code and does work 
beautifully).

Unless we can fix the generic transform code to give results at least as good 
as this code for scaling down, I'll transport it to trunk, too.

-- 
Boudewijn Rempt 
http://www.valdyas.org/fading/index.cgi

["scaling.diff" (text/x-diff)]

Index: core/Makefile.am
===================================================================
--- core/Makefile.am	(revision 604529)
+++ core/Makefile.am	(working copy)
@@ -26,7 +26,7 @@
 				kis_paint_device_iface.skel kis_image_iface.cc kis_image_iface.skel \
                kis_basic_math_toolbox.cpp \
 				kis_math_toolbox.cpp kis_exif_info.cc kis_thread_pool.cc kis_exif_value.cc \
 				kis_filter_strategy.h kis_random_accessor.cpp kis_random_sub_accessor.cpp \
-			kis_perspective_grid.cpp kis_perspectivetransform_worker.cpp \
kis_perspective_math.cpp +			kis_perspective_grid.cpp \
kis_perspectivetransform_worker.cpp kis_perspective_math.cpp kis_scale_visitor.cc  
 noinst_HEADERS = kis_rotate_visitor.h kis_selected_transaction.h \
 			kis_strategy_move.h kis_transform_worker.h kis_datamanager.h \
kis_iteratorpixeltrait.h \ @@ -42,7 +42,7 @@
 		kis_paint_device_iface.h kis_paint_device.h kis_painter.h kis_paintop.h \
kis_paintop_registry.h \  kis_palette.h kis_pattern.h kis_point.h kis_rect.h \
kis_resource.h kis_selection.h \  kis_transaction.h kis_types.h kis_vec.h \
                kis_filter_config_widget.h \
-		kis_filter_configuration.h kis_exif_info.h kis_exif_value.h kis_substrate.h \
kis_perspective_math.h +		kis_filter_configuration.h kis_exif_info.h kis_exif_value.h \
kis_substrate.h kis_perspective_math.h kis_scale_visitor.h  
 libkritaimage_la_LDFLAGS = -version-info 1:0:0 -no-undefined $(all_libraries)
 libkritaimage_la_LIBADD = ../sdk/libkritasdk.la ../kritacolor/libkritacolor.la \
tiles/libkritatile.la $(OPENEXR_LIBS) $(LCMS_LIBS) $(LIB_KOFFICECORE) \
                $(LIB_KOPAINTER) $(LIB_KDECORE) $(LIB_QT) $(OPENEXR_LIBS) 
Index: core/kis_scale_visitor.h
===================================================================
--- core/kis_scale_visitor.h	(revision 0)
+++ core/kis_scale_visitor.h	(revision 0)
@@ -0,0 +1,204 @@
+/*
+ *  copyright (c) 2004, 2005 Michael Thaler <michael.thaler@physik.tu-muenchen.de>
+ *
+ *  this program is free software; you can redistribute it and/or modify
+ *  it under the terms of the gnu general public license as published by
+ *  the free software foundation; either version 2 of the license, or
+ *  (at your option) any later version.
+ *
+ *  this program is distributed in the hope that it will be useful,
+ *  but without any warranty; without even the implied warranty of
+ *  merchantability or fitness for a particular purpose.  see the
+ *  gnu general public license for more details.
+ *
+ *  you should have received a copy of the gnu general public license
+ *  along with this program; if not, write to the free software
+ *  foundation, inc., 675 mass ave, cambridge, ma 02139, usa.
+ */
+
+#ifndef KIS_SCALE_VISITOR_H_
+#define KIS_SCALE_VISITOR_H_
+
+#include "klocale.h"
+
+#include "kis_progress_subject.h"
+#include "kis_progress_display_interface.h"
+#include "kis_thread.h"
+#include "kis_layer_visitor.h"
+#include "kis_types.h"
+#include "kis_layer.h"
+#include "kis_group_layer.h"
+#include "kis_paint_layer.h"
+#include "kis_adjustment_layer.h"
+#include "kis_transaction.h"
+#include "kis_undo_adapter.h"
+#include "kis_selection.h"
+
+class KisProgressDisplayInterface;
+class KisFilterStrategy;
+
+class KisScaleWorker : public KisThread {
+
+    /* Structs for the image rescaling routine */
+    class Contrib {
+    public:
+        Q_INT32 m_pixel;
+        double m_weight;
+    };
+
+    class ContribList {
+    public:
+        Q_INT32  n;  //number of contributors
+        Contrib *p; //pointer to list of contributions
+    };
+
+public:
+
+    KisScaleWorker(KisPaintDevice * dev, double sx, double sy,
+                   KisFilterStrategy *filterStrategy)
+        : KisThread()
+        , m_dev(dev)
+        , m_sx(sx)
+        , m_sy(sy)
+        , m_filterStrategy(filterStrategy) {};
+
+    virtual ~KisScaleWorker() {};
+
+    void run();
+
+private:
+    Q_INT32 m_pixelSize;
+    KisPaintDevice * m_dev;
+    double m_sx, m_sy;
+    KisFilterStrategy * m_filterStrategy;
+
+
+    /**
+     * calc_x_contrib()
+     *
+     * Calculates the filter weights for a single target column.
+     * contribX->p must be freed afterwards.
+     *
+     * Returns -1 if error, 0 otherwise.
+     */
+    int calcContrib(ContribList *contribX, double cale, double fwidth, int srcwidth, \
KisFilterStrategy *filterStrategy, Q_INT32 i); +
+    ContribList * contrib;  //array of contribution lists
+
+
+};
+
+
+class KisScaleVisitor : public KisLayerVisitor, KisProgressSubject {
+
+public:
+
+    KisScaleVisitor(KisImageSP img,
+                    double sx, 
+                    double sy, 
+                    KisProgressDisplayInterface *progress, 
+                    KisFilterStrategy *filterStrategy) 
+        : KisLayerVisitor()
+        , m_img(img)
+        , m_sx(sx)
+        , m_sy(sy)
+        , m_progress(progress)
+        , m_filterStrategy(filterStrategy)
+    {
+        if ( progress )
+            progress -> setSubject(this, true, true);
+        emit notifyProgressStage(i18n("Scaling..."),0);
+    }
+
+    virtual ~KisScaleVisitor()
+    {
+        // Wait for all threads to finish
+        KisThread * t;
+        int threadcount = m_scalethreads.count();
+        int i = 0;
+        for ( t = m_scalethreads.first(); t; t = m_scalethreads.next()) {
+            //progress info
+            if (t) t->wait();
+            emit notifyProgress((100 / threadcount) * i);
+            ++i;
+
+        }
+        emit notifyProgressDone();
+        // Delete all threads
+        m_scalethreads.setAutoDelete(true);
+        m_scalethreads.clear();
+    }
+
+    bool visit(KisPaintLayer *layer) 
+    {
+        // XXX: If all is well, then the image's undoadapter will have started a \
macro for us +        //      This will break in a more multi-threaded environment
+        if (m_img->undoAdapter() && m_img->undoAdapter()->undo()) {
+            KisTransaction * cmd = new KisTransaction("", layer->paintDevice());
+            m_img->undoAdapter()->addCommand(cmd);
+        }
+
+        KisScaleWorker * scaleThread = new KisScaleWorker(layer->paintDevice(),
+                                                     m_sx, m_sy, m_filterStrategy);
+        m_scalethreads.append(scaleThread);
+        scaleThread->start();
+        //scaleThread->run();
+        layer->setDirty();
+        return true;
+    }
+
+    bool visit(KisGroupLayer *layer)
+    {
+        //KisScaleVisitor visitor (m_img, m_sx, m_sy, m_progress, m_filterStrategy);
+
+        // XXX: Maybe faster to scale the projection and do something clever to \
avoid  +	//      recompositing everything?
+	layer->resetProjection(); 
+       
+
+        KisLayerSP child = layer->firstChild();
+        while (child) {
+            child->accept(*this);
+            child = child->nextSibling();
+        }
+
+        return true;
+    }
+
+    bool visit(KisPartLayer */*layer*/)
+    {
+        return true;
+    }
+
+    virtual bool visit(KisAdjustmentLayer* layer)
+    {
+        KisThread * scaleThread = new KisScaleWorker(layer->selection().data(), \
m_sx, m_sy, m_filterStrategy); +        m_scalethreads.append(scaleThread);
+        scaleThread->start();
+        layer->resetCache();
+        layer->setDirty();
+        return true;
+    }
+    
+    
+    // Implement KisProgressSubject
+    virtual void cancel() 
+    {
+        KisThread * t;
+        for ( t = m_scalethreads.first(); t; t = m_scalethreads.next()) {
+            t->cancel();
+        }      
+    }
+    
+
+private:
+
+    QPtrList<KisThread> m_scalethreads;
+    KisImageSP m_img;
+    double m_sx;
+    double m_sy;
+    KisProgressDisplayInterface * m_progress;
+    KisFilterStrategy * m_filterStrategy;
+};
+
+#endif // KIS_SCALE_VISITOR_H_
Index: core/kis_scale_visitor.cc
===================================================================
--- core/kis_scale_visitor.cc	(revision 0)
+++ core/kis_scale_visitor.cc	(revision 0)
@@ -0,0 +1,279 @@
+/*
+ *  Copyright (c) 2004, 2005 Michael Thaler <michael.thaler@physik.tu-muenchen.de>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+#include <qdatetime.h>
+
+#include <kdebug.h>
+#include <klocale.h>
+
+#include "kis_paint_device.h"
+#include "kis_scale_visitor.h"
+#include "kis_filter_strategy.h"
+
+
+void KisScaleWorker::run()
+{
+    double fwidth = m_filterStrategy->support();
+
+    QRect rect = m_dev -> exactBounds();
+    Q_INT32 width = rect.width();
+    Q_INT32 height =  rect.height();
+    m_pixelSize=m_dev -> pixelSize();
+
+    // compute size of target image
+    if ( m_sx == 1.0F && m_sy == 1.0F ) {
+        return;
+    }
+    Q_INT32 targetW = QABS( qRound( m_sx * width ) );
+    Q_INT32 targetH = QABS( qRound( m_sy * height ) );
+
+    Q_UINT8* newData = new Q_UINT8[targetW * targetH * m_pixelSize ];
+    Q_CHECK_PTR(newData);
+
+    double* weight = new double[ m_pixelSize ];    /* filter calculation variables \
*/ +
+    Q_UINT8* pel = new Q_UINT8[ m_pixelSize ];
+    Q_CHECK_PTR(pel);
+
+    Q_UINT8 *pel2 = new Q_UINT8[ m_pixelSize ];
+    Q_CHECK_PTR(pel2);
+
+    bool* bPelDelta = new bool[ m_pixelSize ];
+    ContribList    *contribX;
+    ContribList    contribY;
+    const Q_INT32 BLACK_PIXEL=0;
+    const Q_INT32 WHITE_PIXEL=255;
+
+
+    // create intermediate row to hold vertical dst row zoom
+    Q_UINT8 * tmp = new Q_UINT8[ width * m_pixelSize ];
+    Q_CHECK_PTR(tmp);
+
+    //create array of pointers to intermediate rows
+    Q_UINT8 **tmpRows = new Q_UINT8*[ height ];
+
+    //create array of pointers to intermediate rows that are actually used \
simultaneously and allocate memory for the rows +    Q_UINT8 **tmpRowsMem;
+    if(m_sy < 1.0)
+    {
+        tmpRowsMem = new Q_UINT8*[ (int)(fwidth / m_sy * 2 + 1) ];
+        for(int i = 0; i < (int)(fwidth / m_sy * 2 + 1); i++)
+        {
+             tmpRowsMem[i] = new Q_UINT8[ width * m_pixelSize ];
+             Q_CHECK_PTR(tmpRowsMem[i]);
+        }
+    }
+    else
+    {
+        tmpRowsMem = new Q_UINT8*[ (int)(fwidth * 2 + 1) ];
+        for(int i = 0; i < (int)(fwidth * 2 + 1); i++)
+        {
+            tmpRowsMem[i] = new Q_UINT8[ width * m_pixelSize ];
+            Q_CHECK_PTR(tmpRowsMem[i]);
+        }
+    }
+
+    // build x weights
+    contribX = new ContribList[ targetW ];
+    for(int x = 0; x < targetW; x++)
+    {
+        calcContrib(&contribX[x], m_sx, fwidth, width, m_filterStrategy, x);
+    }
+
+    QTime starttime = QTime::currentTime ();
+
+    for(int y = 0; y < targetH; y++)
+    {
+        // build y weights
+        calcContrib(&contribY, m_sy, fwidth, height, m_filterStrategy, y);
+
+        //copy pixel data to temporary arrays
+        for(int srcpos = 0; srcpos < contribY.n; srcpos++)
+        {
+            if (!(contribY.p[srcpos].m_pixel < 0 || contribY.p[srcpos].m_pixel >= \
height)) +            {
+                tmpRows[contribY.p[srcpos].m_pixel] = new Q_UINT8[ width * \
m_pixelSize ]; +                //tmpRows[ contribY.p[srcpos].m_pixel ] = tmpRowsMem[ \
srcpos ]; +                m_dev ->readBytes(tmpRows[contribY.p[srcpos].m_pixel], 0, \
contribY.p[srcpos].m_pixel, width, 1); +            }
+        }
+
+        /* Apply vert filter to make dst row in tmp. */
+        for(int x = 0; x < width; x++)
+        {
+            for(int channel = 0; channel < m_pixelSize; channel++){
+                weight[channel] = 0.0;
+                bPelDelta[channel] = FALSE;
+                pel[channel]=tmpRows[contribY.p[0].m_pixel][ x * m_pixelSize + \
channel ]; +            }
+            for(int srcpos = 0; srcpos < contribY.n; srcpos++)
+            {
+                if (!(contribY.p[srcpos].m_pixel < 0 || contribY.p[srcpos].m_pixel \
>= height)){ +                    for(int channel = 0; channel < m_pixelSize; \
> channel++)
+                    {
+                        pel2[channel]=tmpRows[contribY.p[srcpos].m_pixel][ x * \
m_pixelSize + channel ]; +                        if(pel2[channel] != pel[channel]) \
bPelDelta[channel] = TRUE; +                            weight[channel] += \
pel2[channel] * contribY.p[srcpos].m_weight; +                    }
+                }
+            }
+
+            for(int channel = 0; channel < m_pixelSize; channel++){
+                weight[channel] = bPelDelta[channel] ? \
static_cast<int>(qRound(weight[channel])) : pel[channel]; +                        \
tmp[ x * m_pixelSize + channel ] = static_cast<Q_UINT8>(CLAMP(weight[channel], \
BLACK_PIXEL, WHITE_PIXEL)); +            }
+        } /* next row in temp column */
+        delete[] contribY.p;
+
+        for(int x = 0; x < targetW; x++)
+        {
+            for(int channel = 0; channel < m_pixelSize; channel++){
+                weight[channel] = 0.0;
+                bPelDelta[channel] = FALSE;
+                pel[channel] = tmp[ contribX[x].p[0].m_pixel * m_pixelSize + channel \
]; +            }
+            for(int srcpos = 0; srcpos < contribX[x].n; srcpos++)
+            {
+                for(int channel = 0; channel < m_pixelSize; channel++){
+                    pel2[channel] = tmp[ contribX[x].p[srcpos].m_pixel * m_pixelSize \
+ channel ]; +                    if(pel2[channel] != pel[channel])
+                        bPelDelta[channel] = TRUE;
+                    weight[channel] += pel2[channel] * \
contribX[x].p[srcpos].m_weight; +                }
+            }
+            for(int channel = 0; channel < m_pixelSize; channel++){
+                weight[channel] = bPelDelta[channel] ? \
static_cast<int>(qRound(weight[channel])) : pel[channel]; +                int \
currentPos = (y*targetW+x) * m_pixelSize; // try to be at least a little efficient +  \
if (weight[channel]<0) +                    newData[currentPos + channel] = 0;
+                else if (weight[channel]>255)
+                    newData[currentPos + channel] = 255;
+                else
+                    newData[currentPos + channel] = (uchar)weight[channel];
+             }
+        } /* next dst row */
+    } /* next dst column */
+
+    // XXX: I'm thinking that we should be able to cancel earlier, in the look.
+    if(!isCanceled()){
+        m_dev -> writeBytes( newData, 0, 0, targetW, targetH);
+        m_dev -> crop(0, 0, targetW, targetH);
+    }
+
+    /* free the memory allocated for horizontal filter weights */
+    for(int x = 0; x < targetW; x++)
+        delete[] contribX[x].p;
+    delete[] contribX;
+
+    delete[] newData;
+    delete[] pel;
+    delete[] pel2;
+    delete[] tmp;
+    delete[] weight;
+    delete[] bPelDelta;
+
+    if(m_sy < 1.0)
+    {
+        for(int i = 0; i < (int)(fwidth / m_sy * 2 + 1); i++)
+        {
+            delete[] tmpRowsMem[i];
+        }
+    }
+    else
+    {
+        for(int i = 0; i < (int)(fwidth * 2 + 1); i++)
+        {
+            delete[] tmpRowsMem[i];
+        }
+    }
+
+    QTime stoptime = QTime::currentTime ();
+    return;
+}
+
+int KisScaleWorker::calcContrib(ContribList *contrib, double scale, double fwidth, \
int srcwidth, KisFilterStrategy* filterStrategy, Q_INT32 i) +{
+        //ContribList* contribX: receiver of contrib info
+        //double m_sx: horizontal zooming scale
+        //double fwidth: Filter sampling width
+        //int dstwidth: Target bitmap width
+        //int srcwidth: Source bitmap width
+        //double (*filterf)(double): Filter proc
+        //int i: Pixel column in source bitmap being processed
+
+        double width;
+        double fscale;
+        double center, begin, end;
+        double weight;
+        Q_INT32 k, n;
+
+        if(scale < 1.0)
+        {
+                //Shrinking image
+                width = fwidth / scale;
+                fscale = 1.0 / scale;
+
+                contrib->n = 0;
+                contrib->p = new Contrib[ (int)(width * 2 + 1) ];
+
+                center = (double) i / scale;
+                begin = ceil(center - width);
+                end = floor(center + width);
+                for(int srcpos = (int)begin; srcpos <= end; ++srcpos)
+                {
+                        weight = center - (double) srcpos;
+                        weight = filterStrategy->valueAt(weight / fscale) / fscale;
+                        if(srcpos < 0)
+                                n = -srcpos;
+                        else if(srcpos >= srcwidth)
+                                n = (srcwidth - srcpos) + srcwidth - 1;
+                        else
+                                n = srcpos;
+
+                        k = contrib->n++;
+                        contrib->p[k].m_pixel = n;
+                        contrib->p[k].m_weight = weight;
+                }
+        }
+        else
+        {
+                // Expanding image
+                contrib->n = 0;
+                contrib->p = new Contrib[ (int)(fwidth * 2 + 1) ];
+
+                center = (double) i / scale;
+                begin = ceil(center - fwidth);
+                end = floor(center + fwidth);
+
+                for(int srcpos = (int)begin; srcpos <= end; ++srcpos)
+                {
+                        weight = center - (double) srcpos;
+                        weight = filterStrategy->valueAt(weight);
+                        if(srcpos < 0) {
+                                n = -srcpos;
+                        } else if(srcpos >= srcwidth) {
+                                n = (srcwidth - srcpos) + srcwidth - 1;
+                        } else {
+                                n = srcpos;
+                        }
+                        k = contrib->n++;
+                        contrib->p[k].m_pixel = n;
+                        contrib->p[k].m_weight = weight;
+                }
+        }
+        return 0;
+} /* calc_x_contrib */
Index: core/kis_image.cc
===================================================================
--- core/kis_image.cc	(revision 604529)
+++ core/kis_image.cc	(working copy)
@@ -54,6 +54,7 @@
 #include "kis_colorspace_convert_visitor.h"
 #include "kis_background.h"
 #include "kis_substrate.h"
+#include "kis_scale_visitor.h"
 #include "kis_nameserver.h"
 #include "kis_undo_adapter.h"
 #include "kis_merge_visitor.h"
@@ -638,7 +639,7 @@
     m_private->selectionChangedWhileLocked = false;
     m_private->substrate = 0;
     m_private->perspectiveGrid = new KisPerspectiveGrid();
-            
+
     m_adapter = adapter;
 
     m_nserver = new KisNameServer(i18n("Layer %1"), 1);
@@ -770,7 +771,12 @@
             m_adapter->addCommand(new LockImageCommand(this, true));
         }
 
-        {
+        if ( colorSpace()->id() == KisID("RGBA") || colorSpace()->id() == \
KisID("CMYK") || colorSpace()->id() == KisID("GRAYA")) { +          KisScaleVisitor v \
(this, sx, sy, progress, filterStrategy); +            m_rootLayer->accept( v );
+        }
+        else {
+
             KisTransformVisitor visitor (this, sx, sy, 0.0, 0.0, 0.0, 0, 0, \
progress, filterStrategy);  m_rootLayer->accept(visitor);
         }
@@ -1496,7 +1502,7 @@
       data += 4;
     }
 #endif
-    
+
     if (paintFlags & PAINT_BACKGROUND) {
         m_bkg->paintBackground(image, r, scaledImageSize, QSize(imageWidth, \
imageHeight));  image.setAlphaBuffer(false);
Index: ui/kis_view.cc
===================================================================
--- ui/kis_view.cc	(revision 604529)
+++ ui/kis_view.cc	(working copy)
@@ -101,7 +101,7 @@
 #include "kis_paint_device.h"
 #include "kis_tool_freehand.h"
 //#include "kis_guide.h"
-
+#include "kis_scale_visitor.h"
 #include "kis_layerbox.h"
 #include "kis_import_catcher.h"
 #include "kis_layer.h"
@@ -1838,9 +1838,15 @@
         t = new KisTransaction(i18n("Scale Layer"), dev);
         Q_CHECK_PTR(t);
     }
+    if ( dev->colorSpace()->id() == KisID("RGBA") || dev->colorSpace()->id() == \
KisID("CMYK") || dev->colorSpace()->id() == KisID("GRAYA")) { +        KisScaleWorker \
w (dev, sx, sy, filterStrategy); +        w.run();
+    }
+    else {
 
-    KisTransformWorker worker(dev, sx, sy, 0, 0, 0.0, 0, 0, m_progress, \
                filterStrategy);
-    worker.run();
+        KisTransformWorker worker(dev, sx, sy, 0, 0, 0.0, 0, 0, m_progress, \
filterStrategy); +        worker.run();
+    }
 
     if (t) undoAdapter()->addCommand(t);
 


[Attachment #8 (application/pgp-signature)]

_______________________________________________
kimageshop mailing list
kimageshop@kde.org
https://mail.kde.org/mailman/listinfo/kimageshop


[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic