[calligra/kexi-altertable-staniek] krita: Added caching for dabs into the brush paintop

Jaroslaw Staniek staniek at kde.org
Wed Oct 31 11:26:25 UTC 2012


Git commit 6ed608a40dd2fffb5b21b5951dba96349b4a35be by Jaroslaw Staniek, on behalf of Dmitry Kazakov.
Committed on 05/10/2012 at 13:39.
Pushed by staniek into branch 'kexi-altertable-staniek'.

Added caching for dabs into the brush paintop

This patch adds caching of the dabs to the paint op system of Krita.
Such cache makes the execution of the benchmarks up to 2 times faster.
Subjectively, the real painting becomes much faster, especially with
huge brushes. Artists report up to 20% speed gain while painting.

Of course, such caching makes the painting a bit less precise: we need
to tolerate subpixel differences to allow the cache to work. Sometimes
small difference in the size of a dab can also be acceptable. That is
why I introduced levels of precision. They are graded from 1 to 5: from
the fastest and less precise to the slowest, but with the best quality.
You can see the slider in the paintop settings dialog. The ToolTip text
explains which features of the brush are sacrificed on each precision
level.

The texturing and mirroring problems are solved.

My next steps are: make this cache tolerate bug 307588 and port it to
other brush-based paitops.

CC:kimageshop at kde.org
REVIEW:106724

M  +8    -0    krita/image/kis_fixed_paint_device.cpp
M  +5    -2    krita/image/kis_fixed_paint_device.h
M  +11   -0    krita/image/kis_painter.cc
M  +11   -0    krita/image/kis_painter.h
M  +21   -32   krita/plugins/paintops/defaultpaintops/brush/kis_brushop.cpp
M  +4    -0    krita/plugins/paintops/defaultpaintops/brush/kis_brushop.h
M  +1    -0    krita/plugins/paintops/defaultpaintops/brush/kis_brushop_settings_widget.cpp
M  +2    -0    krita/plugins/paintops/libpaintop/CMakeLists.txt
M  +36   -1    krita/plugins/paintops/libpaintop/forms/wdgbrushchooser.ui
M  +5    -0    krita/plugins/paintops/libpaintop/kis_brush_based_paintop_options_widget.cpp
M  +2    -0    krita/plugins/paintops/libpaintop/kis_brush_based_paintop_options_widget.h
M  +7    -1    krita/plugins/paintops/libpaintop/kis_brush_option_widget.cpp
M  +2    -1    krita/plugins/paintops/libpaintop/kis_brush_option_widget.h
M  +74   -0    krita/plugins/paintops/libpaintop/kis_brush_selection_widget.cpp
M  +9    -0    krita/plugins/paintops/libpaintop/kis_brush_selection_widget.h
A  +292  -0    krita/plugins/paintops/libpaintop/kis_dab_cache.cpp     [License: GPL (v2+)]
A  +132  -0    krita/plugins/paintops/libpaintop/kis_dab_cache.h     [License: GPL (v2+)]
A  +41   -0    krita/plugins/paintops/libpaintop/kis_precision_option.cpp     [License: GPL (v2+)]
C  +14   -18   krita/plugins/paintops/libpaintop/kis_precision_option.h [from: krita/plugins/paintops/libpaintop/kis_brush_based_paintop_options_widget.h - 052% similarity]
M  +1    -1    krita/plugins/paintops/libpaintop/kis_pressure_sharpness_option.cpp
M  +1    -1    krita/plugins/paintops/libpaintop/kis_pressure_sharpness_option.h

http://commits.kde.org/calligra/6ed608a40dd2fffb5b21b5951dba96349b4a35be

diff --git a/krita/image/kis_fixed_paint_device.cpp b/krita/image/kis_fixed_paint_device.cpp
index 5ef55af..0887902 100644
--- a/krita/image/kis_fixed_paint_device.cpp
+++ b/krita/image/kis_fixed_paint_device.cpp
@@ -42,6 +42,14 @@ KisFixedPaintDevice::KisFixedPaintDevice(const KisFixedPaintDevice& rhs)
     m_data = rhs.m_data;
 }
 
+KisFixedPaintDevice& KisFixedPaintDevice::operator=(const KisFixedPaintDevice& rhs)
+{
+    m_bounds = rhs.m_bounds;
+    m_colorSpace = rhs.m_colorSpace;
+    m_data = rhs.m_data;
+    return *this;
+}
+
 void KisFixedPaintDevice::setRect(const QRect& rc)
 {
     m_bounds = rc;
diff --git a/krita/image/kis_fixed_paint_device.h b/krita/image/kis_fixed_paint_device.h
index feeed0b..14d41e0 100644
--- a/krita/image/kis_fixed_paint_device.h
+++ b/krita/image/kis_fixed_paint_device.h
@@ -48,6 +48,11 @@ public:
     KisFixedPaintDevice(const KisFixedPaintDevice& rhs);
 
     /**
+     * Deep copy the fixed paint device, including the data.
+     */
+    KisFixedPaintDevice& operator=(const KisFixedPaintDevice& rhs);
+
+    /**
      * setRect sets the rect of the fixed paint device to rect.
      * This will _not_ create the associated data area.
      *
@@ -169,8 +174,6 @@ public:
 
 private:
 
-    KisFixedPaintDevice& operator=(const KisFixedPaintDevice& rhs);
-
     const KoColorSpace* m_colorSpace;
     QRect m_bounds;
     QVector<quint8> m_data;
diff --git a/krita/image/kis_painter.cc b/krita/image/kis_painter.cc
index bec3776..23af53c 100644
--- a/krita/image/kis_painter.cc
+++ b/krita/image/kis_painter.cc
@@ -2476,6 +2476,17 @@ void KisPainter::setColorConversionFlags(KoColorConversionTransformation::Conver
     d->conversionFlags = conversionFlags;
 }
 
+void KisPainter::renderMirrorMaskSafe(QRect rc, KisFixedPaintDeviceSP dab, bool preserveDab)
+{
+    if (!d->mirrorHorizontaly && !d->mirrorVerticaly) return;
+
+    KisFixedPaintDeviceSP dabToProcess = dab;
+    if (preserveDab) {
+        dabToProcess = new KisFixedPaintDevice(*dab);
+    }
+    renderMirrorMask(rc, dabToProcess);
+}
+
 void KisPainter::renderMirrorMask(QRect rc, KisFixedPaintDeviceSP dab)
 {
     int x = rc.topLeft().x();
diff --git a/krita/image/kis_painter.h b/krita/image/kis_painter.h
index 25a11c9..73133c3 100644
--- a/krita/image/kis_painter.h
+++ b/krita/image/kis_painter.h
@@ -364,6 +364,17 @@ public:
     void renderMirrorMask(QRect rc, KisPaintDeviceSP dab, int sx, int sy, KisFixedPaintDeviceSP mask);
 
     /**
+     * Convinience method for renderMirrorMask(), allows to choose whether
+     * we need to preserve out dab or do the transformations in-place.
+     *
+     * @param rc rectangle area covered by dab
+     * @param dab the device to render
+     * @param preserveDab states whether a temporary device should be
+     *                    created to do the transformations
+     */
+    void renderMirrorMaskSafe(QRect rc, KisFixedPaintDeviceSP dab, bool preserveDab);
+
+    /**
      * Special method for some paintop that needs to know which areas where covered by the dab
      * E.g. experimental (shape) paintop needs to know it to be able to copy appriate regions from
      * internal device to the layer device
diff --git a/krita/plugins/paintops/defaultpaintops/brush/kis_brushop.cpp b/krita/plugins/paintops/defaultpaintops/brush/kis_brushop.cpp
index 177fa7e..4690570 100644
--- a/krita/plugins/paintops/defaultpaintops/brush/kis_brushop.cpp
+++ b/krita/plugins/paintops/defaultpaintops/brush/kis_brushop.cpp
@@ -41,8 +41,10 @@
 #include <kis_pressure_sharpness_option.h>
 #include <KoColorSpaceRegistry.h>
 
+#include "kis_dab_cache.h"
+
 KisBrushOp::KisBrushOp(const KisBrushBasedPaintOpSettings *settings, KisPainter *painter, KisImageWSP image)
-        : KisBrushBasedPaintOp(settings, painter), m_hsvTransformation(0)
+    : KisBrushBasedPaintOp(settings, painter), m_hsvTransformation(0), m_dabCache(new KisDabCache(m_brush))
 {
     Q_UNUSED(image);
     Q_ASSERT(settings);
@@ -77,6 +79,7 @@ KisBrushOp::KisBrushOp(const KisBrushBasedPaintOpSettings *settings, KisPainter
     m_scatterOption.readOptionSetting(settings);
     m_mirrorOption.readOptionSetting(settings);
     m_textureProperties.fillProperties(settings);
+    m_precisionOption.readOptionSetting(settings);
 
     m_opacityOption.sensor()->reset();
     m_sizeOption.sensor()->reset();
@@ -86,6 +89,11 @@ KisBrushOp::KisBrushOp(const KisBrushBasedPaintOpSettings *settings, KisPainter
     m_darkenOption.sensor()->reset();
     m_rotationOption.sensor()->reset();
     m_scatterOption.sensor()->reset();
+
+    m_dabCache->setMirrorPostprocessing(&m_mirrorOption);
+    m_dabCache->setSharpnessPostprocessing(&m_sharpnessOption);
+    m_dabCache->setTexturePostprocessing(&m_textureProperties);
+    m_dabCache->setPrecisionOption(&m_precisionOption);
 }
 
 KisBrushOp::~KisBrushOp()
@@ -93,6 +101,7 @@ KisBrushOp::~KisBrushOp()
     qDeleteAll(m_hsvOptions);
     delete m_colorSource;
     delete m_hsvTransformation;
+    delete m_dabCache;
 }
 
 qreal KisBrushOp::paintAt(const KisPaintInformation& info)
@@ -146,39 +155,19 @@ qreal KisBrushOp::paintAt(const KisPaintInformation& info)
         m_colorSource->applyColorTransformation(m_hsvTransformation);
     }
 
-    KisFixedPaintDeviceSP dab = cachedDab(device->colorSpace());
-
-    if (brush->brushType() == IMAGE || brush->brushType() == PIPE_IMAGE) {
-        dab = brush->paintDevice(device->colorSpace(), scale, rotation, info, xFraction, yFraction);
-    }
-    else {
-        if (m_colorSource->isUniformColor()) {
-            KoColor color = m_colorSource->uniformColor();
-            color.convertTo(dab->colorSpace());
-            brush->mask(dab, color, scale, scale, rotation, info, xFraction, yFraction, m_softnessOption.apply(info));
-        }
-        else {
-            if (!m_colorSourceDevice) {
-                m_colorSourceDevice = new KisPaintDevice(dab->colorSpace());
-            }
-            else {
-                m_colorSourceDevice->clear();
-            }
-            m_colorSource->colorize(m_colorSourceDevice, QRect(0, 0, brush->maskWidth(scale, rotation), brush->maskHeight(scale, rotation)), info.pos().toPoint() );
-            brush->mask(dab, m_colorSourceDevice, scale, scale, rotation, info, xFraction, yFraction, m_softnessOption.apply(info));
-        }
-    }
-
-    MirrorProperties mirrors = m_mirrorOption.apply(info);
-    dab->mirror(mirrors.horizontalMirror, mirrors.verticalMirror);
-
-    m_sharpnessOption.applyTreshold( dab );
-
-    // after everything, apply the texturing
-    m_textureProperties.apply(dab, info.pos().toPoint());
+    KisFixedPaintDeviceSP dab = m_dabCache->fetchDab(device->colorSpace(),
+                                                    m_colorSource,
+                                                    scale, scale,
+                                                    rotation,
+                                                    info,
+                                                    xFraction, yFraction,
+                                                    m_softnessOption.apply(info));
 
     painter()->bltFixed(QPoint(x, y), dab, dab->bounds());
-    painter()->renderMirrorMask(QRect(QPoint(x,y), QSize(dab->bounds().width(),dab->bounds().height())),dab);
+
+    painter()->renderMirrorMaskSafe(QRect(QPoint(x,y), QSize(dab->bounds().width(),dab->bounds().height())),
+                                    dab,
+                                    !m_dabCache->needSeparateOriginal());
     painter()->setOpacity(origOpacity);
     painter()->setFlow(origFlow);
 
diff --git a/krita/plugins/paintops/defaultpaintops/brush/kis_brushop.h b/krita/plugins/paintops/defaultpaintops/brush/kis_brushop.h
index 1b545ef..21c56a7 100644
--- a/krita/plugins/paintops/defaultpaintops/brush/kis_brushop.h
+++ b/krita/plugins/paintops/defaultpaintops/brush/kis_brushop.h
@@ -37,6 +37,7 @@
 #include <kis_color_source_option.h>
 #include <kis_pressure_spacing_option.h>
 #include <kis_texture_option.h>
+#include <kis_precision_option.h>
 
 class KisBrushBasedPaintOpSettings;
 
@@ -44,6 +45,7 @@ class QWidget;
 class QPointF;
 class KisPainter;
 class KisColorSource;
+class KisDabCache;
 
 
 class KisBrushOp : public KisBrushBasedPaintOp
@@ -71,10 +73,12 @@ private:
     KisPressureScatterOption m_scatterOption;
     QList<KisPressureHSVOption*> m_hsvOptions;
     KisTextureProperties m_textureProperties;
+    KisPrecisionOption m_precisionOption;
 
     KoColorTransformation *m_hsvTransformation;
     KisPaintDeviceSP m_dab;
     KisPaintDeviceSP m_colorSourceDevice;
+    KisDabCache *m_dabCache;
 };
 
 #endif // KIS_BRUSHOP_H_
diff --git a/krita/plugins/paintops/defaultpaintops/brush/kis_brushop_settings_widget.cpp b/krita/plugins/paintops/defaultpaintops/brush/kis_brushop_settings_widget.cpp
index cbd6667..a622995 100644
--- a/krita/plugins/paintops/defaultpaintops/brush/kis_brushop_settings_widget.cpp
+++ b/krita/plugins/paintops/defaultpaintops/brush/kis_brushop_settings_widget.cpp
@@ -45,6 +45,7 @@ KisBrushOpSettingsWidget::KisBrushOpSettingsWidget(QWidget* parent)
         : KisBrushBasedPaintopOptionWidget(parent)
 {
     setObjectName("brush option widget");
+    setPrecisionEnabled(true);
 
     // Brush tip options
     addPaintOpOption(new KisCompositeOpOption(true));
diff --git a/krita/plugins/paintops/libpaintop/CMakeLists.txt b/krita/plugins/paintops/libpaintop/CMakeLists.txt
index bd1b021..957df35 100644
--- a/krita/plugins/paintops/libpaintop/CMakeLists.txt
+++ b/krita/plugins/paintops/libpaintop/CMakeLists.txt
@@ -18,6 +18,7 @@ set(kritalibpaintop_LIB_SRCS
     kis_curve_option_widget.cpp
     kis_custom_brush_widget.cpp
     kis_dynamic_sensor.cc
+    kis_dab_cache.cpp
     kis_filter_option.cpp
     kis_multi_sensors_model_p.cpp
     kis_multi_sensors_selector.cpp
@@ -25,6 +26,7 @@ set(kritalibpaintop_LIB_SRCS
     kis_paintop_option.cpp
     kis_paintop_options_model.cpp
     kis_paintop_options_widget.cpp
+    kis_precision_option.cpp
     kis_pressure_darken_option.cpp
     kis_pressure_hsv_option.cpp
     kis_pressure_opacity_option.cpp
diff --git a/krita/plugins/paintops/libpaintop/forms/wdgbrushchooser.ui b/krita/plugins/paintops/libpaintop/forms/wdgbrushchooser.ui
index b19828f..c0ff93c 100644
--- a/krita/plugins/paintops/libpaintop/forms/wdgbrushchooser.ui
+++ b/krita/plugins/paintops/libpaintop/forms/wdgbrushchooser.ui
@@ -10,7 +10,7 @@
     <height>300</height>
    </rect>
   </property>
-  <layout class="QVBoxLayout" name="verticalLayout" stretch="0,1">
+  <layout class="QVBoxLayout" name="verticalLayout" stretch="0,1,0">
    <item>
     <layout class="QHBoxLayout" name="horizontalLayout">
      <item>
@@ -56,8 +56,43 @@
      </property>
     </widget>
    </item>
+   <item>
+    <layout class="QHBoxLayout" name="horizontalLayout_3">
+     <item>
+      <widget class="QLabel" name="lblPrecision">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <property name="text">
+        <string>Precision:</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="KisSliderSpinBox" name="sliderPrecision" native="true">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
   </layout>
  </widget>
+ <customwidgets>
+  <customwidget>
+   <class>KisSliderSpinBox</class>
+   <extends>QWidget</extends>
+   <header>kis_slider_spin_box.h</header>
+   <container>1</container>
+  </customwidget>
+ </customwidgets>
  <resources/>
  <connections/>
 </ui>
diff --git a/krita/plugins/paintops/libpaintop/kis_brush_based_paintop_options_widget.cpp b/krita/plugins/paintops/libpaintop/kis_brush_based_paintop_options_widget.cpp
index 51cc2d2..42ab9c1 100644
--- a/krita/plugins/paintops/libpaintop/kis_brush_based_paintop_options_widget.cpp
+++ b/krita/plugins/paintops/libpaintop/kis_brush_based_paintop_options_widget.cpp
@@ -31,6 +31,11 @@ KisBrushBasedPaintopOptionWidget::~KisBrushBasedPaintopOptionWidget()
 {
 }
 
+void KisBrushBasedPaintopOptionWidget::setPrecisionEnabled(bool value)
+{
+    m_brushOption->setPrecisionEnabled(value);
+}
+
 KisBrushSP KisBrushBasedPaintopOptionWidget::brush()
 {
     return m_brushOption->brush();
diff --git a/krita/plugins/paintops/libpaintop/kis_brush_based_paintop_options_widget.h b/krita/plugins/paintops/libpaintop/kis_brush_based_paintop_options_widget.h
index 734df21..26ec756 100644
--- a/krita/plugins/paintops/libpaintop/kis_brush_based_paintop_options_widget.h
+++ b/krita/plugins/paintops/libpaintop/kis_brush_based_paintop_options_widget.h
@@ -32,6 +32,8 @@ public:
     KisBrushBasedPaintopOptionWidget(QWidget* parent = 0);
     virtual ~KisBrushBasedPaintopOptionWidget();
 
+    void setPrecisionEnabled(bool value);
+
     KisBrushSP brush();
 
     void changePaintOpSize(qreal x, qreal y);
diff --git a/krita/plugins/paintops/libpaintop/kis_brush_option_widget.cpp b/krita/plugins/paintops/libpaintop/kis_brush_option_widget.cpp
index c0e171e..b31e9b6 100644
--- a/krita/plugins/paintops/libpaintop/kis_brush_option_widget.cpp
+++ b/krita/plugins/paintops/libpaintop/kis_brush_option_widget.cpp
@@ -30,6 +30,7 @@ KisBrushOptionWidget::KisBrushOptionWidget()
 {
     m_checkable = false;
     m_brushSelectionWidget = new KisBrushSelectionWidget();
+    connect(m_brushSelectionWidget, SIGNAL(sigPrecisionChanged()), SIGNAL(sigSettingChanged()));
     connect(m_brushSelectionWidget, SIGNAL(sigBrushChanged()), SLOT(brushChanged()));
     m_brushSelectionWidget->hide();
     setConfigurationPage(m_brushSelectionWidget);
@@ -66,14 +67,20 @@ void KisBrushOptionWidget::setImage(KisImageWSP image)
     m_brushSelectionWidget->setImage(image);
 }
 
+void KisBrushOptionWidget::setPrecisionEnabled(bool value)
+{
+    m_brushSelectionWidget->setPrecisionEnabled(value);
+}
 
 void KisBrushOptionWidget::writeOptionSetting(KisPropertiesConfiguration* settings) const
 {
+    m_brushSelectionWidget->writeOptionSetting(settings);
     m_brushOption.writeOptionSetting(settings);
 }
 
 void KisBrushOptionWidget::readOptionSetting(const KisPropertiesConfiguration* setting)
 {
+    m_brushSelectionWidget->readOptionSetting(setting);
     m_brushOption.readOptionSetting(setting);
     m_brushSelectionWidget->setCurrentBrush(m_brushOption.brush());
 }
@@ -90,7 +97,6 @@ QSizeF KisBrushOptionWidget::brushSize() const
     return m_brushSelectionWidget->brushSize();
 }
 
-
 void KisBrushOptionWidget::brushChanged()
 {
     m_brushOption.setBrush(brush());
diff --git a/krita/plugins/paintops/libpaintop/kis_brush_option_widget.h b/krita/plugins/paintops/libpaintop/kis_brush_option_widget.h
index cf426cb..9fbbbb5 100644
--- a/krita/plugins/paintops/libpaintop/kis_brush_option_widget.h
+++ b/krita/plugins/paintops/libpaintop/kis_brush_option_widget.h
@@ -50,8 +50,9 @@ public:
 
     void setImage(KisImageWSP image);
 
-    void writeOptionSetting(KisPropertiesConfiguration* setting) const;
+    void setPrecisionEnabled(bool value);
 
+    void writeOptionSetting(KisPropertiesConfiguration* setting) const;
     void readOptionSetting(const KisPropertiesConfiguration* setting);
 
     void setBrushSize(qreal dxPixels, qreal dyPixels);
diff --git a/krita/plugins/paintops/libpaintop/kis_brush_selection_widget.cpp b/krita/plugins/paintops/libpaintop/kis_brush_selection_widget.cpp
index e0bedd8..cb5cd9e 100644
--- a/krita/plugins/paintops/libpaintop/kis_brush_selection_widget.cpp
+++ b/krita/plugins/paintops/libpaintop/kis_brush_selection_widget.cpp
@@ -76,6 +76,13 @@ KisBrushSelectionWidget::KisBrushSelectionWidget(QWidget * parent)
 
     setCurrentWidget(m_autoBrushWidget);
 
+    uiWdgBrushChooser.sliderPrecision->setRange(1,5);
+    uiWdgBrushChooser.sliderPrecision->setSingleStep(1);
+    uiWdgBrushChooser.sliderPrecision->setPageStep(1);
+    connect(uiWdgBrushChooser.sliderPrecision, SIGNAL(valueChanged(int)), SLOT(precisionChanged(int)));
+    uiWdgBrushChooser.sliderPrecision->setValue(4);
+    setPrecisionEnabled(false);
+
     m_presetIsValid = true;
 }
 
@@ -194,6 +201,73 @@ void KisBrushSelectionWidget::buttonClicked(int id)
     emit sigBrushChanged();
 }
 
+void KisBrushSelectionWidget::precisionChanged(int value)
+{
+    QString toolTip;
+
+    switch(value) {
+    case 1:
+        toolTip =
+            i18n("Precision Level 1 (fastest)\n"
+                 "Subpixel precision: disabled\n"
+                 "Brush size precision: 5%\n"
+                 "\n"
+                 "Optimal for very big brushes");
+        break;
+    case 2:
+        toolTip =
+            i18n("Precision Level 2\n"
+                 "Subpixel precision: disabled\n"
+                 "Brush size precision: 1%\n"
+                 "\n"
+                 "Optimal for big brushes");
+        break;
+    case 3:
+        toolTip =
+            i18n("Precision Level 3\n"
+                 "Subpixel precision: disabled\n"
+                 "Brush size precision: exact");
+        break;
+    case 4:
+        toolTip =
+            i18n("Precision Level 4 (optimal)\n"
+                 "Subpixel precision: 50%\n"
+                 "Brush size precision: exact\n"
+                 "\n"
+                 "Gives up to 50% better performance in comparison to Level 5");
+        break;
+    case 5:
+        toolTip =
+            i18n("Precision Level 5 (best quality)\n"
+                 "Subpixel precision: exact\n"
+                 "Brush size precision: exact\n"
+                 "\n"
+                 "The slowest performance. Best quality.");
+        break;
+    }
+
+    uiWdgBrushChooser.sliderPrecision->setToolTip(toolTip);
+    m_precisionOption.setPrecisionLevel(value);
+    emit sigPrecisionChanged();
+}
+
+void KisBrushSelectionWidget::writeOptionSetting(KisPropertiesConfiguration* settings) const
+{
+    m_precisionOption.writeOptionSetting(settings);
+}
+
+void KisBrushSelectionWidget::readOptionSetting(const KisPropertiesConfiguration* setting)
+{
+    m_precisionOption.readOptionSetting(setting);
+    uiWdgBrushChooser.sliderPrecision->setValue(m_precisionOption.precisionLevel());
+}
+
+void KisBrushSelectionWidget::setPrecisionEnabled(bool value)
+{
+    uiWdgBrushChooser.sliderPrecision->setVisible(value);
+    uiWdgBrushChooser.lblPrecision->setVisible(value);
+}
+
 void KisBrushSelectionWidget::setCurrentWidget(QWidget* widget)
 {
     if (m_currentBrushWidget) {
diff --git a/krita/plugins/paintops/libpaintop/kis_brush_selection_widget.h b/krita/plugins/paintops/libpaintop/kis_brush_selection_widget.h
index d25729f..a98d86e 100644
--- a/krita/plugins/paintops/libpaintop/kis_brush_selection_widget.h
+++ b/krita/plugins/paintops/libpaintop/kis_brush_selection_widget.h
@@ -20,6 +20,7 @@
 
 #include <QWidget>
 #include "kis_brush.h"
+#include "kis_precision_option.h"
 #include "ui_wdgbrushchooser.h"
 
 class QTabWidget;
@@ -58,12 +59,19 @@ public:
     QSizeF brushSize() const;
     bool presetIsValid() { return m_presetIsValid; }
 
+    void writeOptionSetting(KisPropertiesConfiguration* settings) const;
+    void readOptionSetting(const KisPropertiesConfiguration* setting);
+
+    void setPrecisionEnabled(bool value);
+
 signals:
 
     void sigBrushChanged();
+    void sigPrecisionChanged();
 
 private slots:
     void buttonClicked(int id);
+    void precisionChanged(int value);
 
 private:
     void setCurrentWidget(QWidget * widget);
@@ -91,6 +99,7 @@ private:
     KisTextBrushChooser * m_textBrushWidget;
     KisCustomBrushWidget * m_customBrushWidget;
 
+    KisPrecisionOption m_precisionOption;
 };
 
 #endif
diff --git a/krita/plugins/paintops/libpaintop/kis_dab_cache.cpp b/krita/plugins/paintops/libpaintop/kis_dab_cache.cpp
new file mode 100644
index 0000000..fbb6af0
--- /dev/null
+++ b/krita/plugins/paintops/libpaintop/kis_dab_cache.cpp
@@ -0,0 +1,292 @@
+/*
+ *  Copyright (c) 2012 Dmitry Kazakov <dimula73 at gmail.com>
+ *
+ *  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 "kis_dab_cache.h"
+
+#include <KoColor.h>
+#include "kis_color_source.h"
+#include "kis_paint_device.h"
+
+#include <kis_pressure_mirror_option.h>
+#include <kis_pressure_sharpness_option.h>
+#include <kis_texture_option.h>
+#include <kis_precision_option.h>
+
+
+struct PrecisionValues {
+    qreal angle;
+    qreal sizeFrac;
+    qreal subPixel;
+    qreal softnessFactor;
+};
+
+const qreal eps = 1e-6;
+static PrecisionValues precisionLevels[] =
+{
+    {M_PI/180, 0.05,   1, 0.01},
+    {M_PI/180, 0.01,   1, 0.01},
+    {M_PI/180,    0,   1, 0.01},
+    {M_PI/180,    0, 0.5, 0.01},
+    {eps,         0, eps,  eps}
+};
+
+struct KisDabCache::SavedDabParameters {
+    KoColor color;
+    qreal angle;
+    int width;
+    int height;
+    qreal subPixelX;
+    qreal subPixelY;
+    qreal softnessFactor;
+
+    bool compare(const SavedDabParameters &rhs, int precisionLevel) {
+        PrecisionValues &prec = precisionLevels[precisionLevel];
+
+        return color == rhs.color &&
+            qAbs(angle - rhs.angle) <= prec.angle &&
+            qAbs(width - rhs.width) <= (int)(prec.sizeFrac * width) &&
+            qAbs(height - rhs.height) <= (int)(prec.sizeFrac * height) &&
+            qAbs(subPixelX - rhs.subPixelX) <= prec.subPixel &&
+            qAbs(subPixelY - rhs.subPixelY) <= prec.subPixel &&
+            qAbs(softnessFactor - rhs.softnessFactor) <= prec.softnessFactor;
+    }
+};
+
+KisDabCache::KisDabCache(KisBrushSP brush)
+    : m_brush(brush),
+      m_mirrorOption(0),
+      m_sharpnessOption(0),
+      m_textureOption(0),
+      m_precisionOption(0),
+      m_cachedDabParameters(new SavedDabParameters)
+{
+    INIT_HIT_RATE_VARS();
+}
+
+KisDabCache::~KisDabCache()
+{
+    PRINT_HIT_RATE();
+    delete m_cachedDabParameters;
+}
+
+void KisDabCache::setMirrorPostprocessing(KisPressureMirrorOption *option)
+{
+    m_mirrorOption = option;
+}
+
+void KisDabCache::setSharpnessPostprocessing(KisPressureSharpnessOption *option)
+{
+    m_sharpnessOption = option;
+}
+
+void KisDabCache::setTexturePostprocessing(KisTextureProperties *option)
+{
+    m_textureOption = option;
+}
+
+void KisDabCache::setPrecisionOption(KisPrecisionOption *option)
+{
+    m_precisionOption = option;
+}
+
+inline KisDabCache::SavedDabParameters
+KisDabCache::getDabParameters(const KoColor& color,
+                              double scaleX, double scaleY,
+                              double angle,
+                              const KisPaintInformation& info,
+                              double subPixelX, double subPixelY,
+                              qreal softnessFactor)
+{
+    Q_UNUSED(info);
+
+    SavedDabParameters params;
+
+    params.color = color;
+    params.angle = angle;
+    params.width = m_brush->maskWidth(scaleX, angle);
+    params.height = m_brush->maskHeight(scaleY, angle);
+    params.subPixelX = subPixelX;
+    params.subPixelY = subPixelY;
+    params.softnessFactor = softnessFactor;
+
+    return params;
+}
+
+KisFixedPaintDeviceSP KisDabCache::fetchDab(const KoColorSpace *cs,
+                                            const KoColor& color,
+                                            double scaleX, double scaleY,
+                                            double angle,
+                                            const KisPaintInformation& info,
+                                            double subPixelX, double subPixelY,
+                                            qreal softnessFactor)
+{
+    return fetchDabCommon(cs, 0, color, scaleX, scaleY, angle,
+                          info, subPixelX, subPixelY, softnessFactor);
+}
+
+KisFixedPaintDeviceSP KisDabCache::fetchDab(const KoColorSpace *cs,
+                                            const KisColorSource *colorSource,
+                                            double scaleX, double scaleY,
+                                            double angle,
+                                            const KisPaintInformation& info,
+                                            double subPixelX, double subPixelY,
+                                            qreal softnessFactor)
+{
+    return fetchDabCommon(cs, colorSource, KoColor(), scaleX, scaleY, angle,
+                          info, subPixelX, subPixelY, softnessFactor);
+}
+
+bool KisDabCache::needSeparateOriginal()
+{
+    return (m_mirrorOption->isChecked() &&
+            (m_mirrorOption->isHorizontalMirrorEnabled() ||
+             m_mirrorOption->isVerticalMirrorEnabled())) ||
+        m_textureOption->enabled;
+}
+
+inline
+KisFixedPaintDeviceSP KisDabCache::tryFetchFromCache(const KisColorSource *colorSource,
+                                                     const KoColor& color,
+                                                     double scaleX, double scaleY,
+                                                     double angle,
+                                                     const KisPaintInformation& info,
+                                                     double subPixelX, double subPixelY,
+                                                     qreal softnessFactor)
+{
+    if (colorSource && !colorSource->isUniformColor()) {
+        COUNT_MISS();
+        return 0;
+    }
+
+    KoColor newColor = colorSource ? colorSource->uniformColor() : color;
+
+    SavedDabParameters newParams = getDabParameters(newColor,
+                                                    scaleX, scaleY,
+                                                    angle, info,
+                                                    subPixelX, subPixelY,
+                                                    softnessFactor);
+
+    int precisionLevel = m_precisionOption ? m_precisionOption->precisionLevel() - 1 : 3;
+
+    if (!newParams.compare(*m_cachedDabParameters, precisionLevel)) {
+        COUNT_MISS();
+        return 0;
+    }
+
+    if (needSeparateOriginal()) {
+        *m_dab = *m_dabOriginal;
+        postProcessDab(m_dab, info);
+        COUNT_HALF_HIT();
+    } else {
+        COUNT_HIT();
+    }
+
+    return m_dab;
+}
+
+inline
+KisFixedPaintDeviceSP KisDabCache::fetchDabCommon(const KoColorSpace *cs,
+                                                  const KisColorSource *colorSource,
+                                                  const KoColor& color,
+                                                  double scaleX, double scaleY,
+                                                  double angle,
+                                                  const KisPaintInformation& info,
+                                                  double subPixelX, double subPixelY,
+                                                  qreal softnessFactor)
+{
+    if (!m_dab || !(*m_dab->colorSpace() == *cs)) {
+        m_dab = new KisFixedPaintDevice(cs);
+    } else {
+        KisFixedPaintDeviceSP cachedDab =
+            tryFetchFromCache(colorSource, KoColor(), scaleX, scaleY, angle,
+                              info, subPixelX, subPixelY, softnessFactor);
+        if (cachedDab) return cachedDab;
+    }
+
+    if (m_brush->brushType() == IMAGE || m_brush->brushType() == PIPE_IMAGE) {
+        m_dab = m_brush->paintDevice(cs, scaleX, angle, info,
+                                     subPixelX, subPixelY);
+    }
+    else {
+        if (!colorSource) {
+            Q_ASSERT(*color.colorSpace() == *cs);
+            m_brush->mask(m_dab, color, scaleX, scaleY, angle,
+                          info, subPixelX, subPixelY, softnessFactor);
+
+            *m_cachedDabParameters = getDabParameters(color,
+                                                      scaleX, scaleY,
+                                                      angle, info,
+                                                      subPixelX, subPixelY,
+                                                      softnessFactor);
+
+        } else if (colorSource->isUniformColor()) {
+            Q_ASSERT(*colorSource->colorSpace() == *cs);
+            KoColor color = colorSource->uniformColor();
+            m_brush->mask(m_dab, color, scaleX, scaleY, angle,
+                          info, subPixelX, subPixelY, softnessFactor);
+
+            *m_cachedDabParameters = getDabParameters(color,
+                                                      scaleX, scaleY,
+                                                      angle, info,
+                                                      subPixelX, subPixelY,
+                                                      softnessFactor);
+
+        } else {
+            if (!m_colorSourceDevice || !(*cs == *m_colorSourceDevice->colorSpace())) {
+                m_colorSourceDevice = new KisPaintDevice(cs);
+            } else {
+                m_colorSourceDevice->clear();
+            }
+
+            QRect maskRect(0, 0, m_brush->maskWidth(scaleX, angle), m_brush->maskHeight(scaleY, angle));
+            colorSource->colorize(m_colorSourceDevice, maskRect, info.pos().toPoint());
+
+            m_brush->mask(m_dab, m_colorSourceDevice, scaleX, scaleY, angle,
+                          info, subPixelX, subPixelY, softnessFactor);
+        }
+    }
+
+    if (needSeparateOriginal()) {
+        if (!m_dabOriginal || !(*cs == *m_dabOriginal->colorSpace())) {
+            m_dabOriginal = new KisFixedPaintDevice(cs);
+        }
+
+        *m_dabOriginal = *m_dab;
+    }
+
+    postProcessDab(m_dab, info);
+
+    return m_dab;
+}
+
+void KisDabCache::postProcessDab(KisFixedPaintDeviceSP dab,
+                                 const KisPaintInformation& info)
+{
+    if (m_mirrorOption) {
+        MirrorProperties mirror = m_mirrorOption->apply(info);
+        dab->mirror(mirror.horizontalMirror, mirror.verticalMirror);
+    }
+
+    if (m_sharpnessOption) {
+        m_sharpnessOption->applyThreshold(dab);
+    }
+
+    if (m_textureOption) {
+        m_textureOption->apply(dab, info.pos().toPoint());
+    }
+}
diff --git a/krita/plugins/paintops/libpaintop/kis_dab_cache.h b/krita/plugins/paintops/libpaintop/kis_dab_cache.h
new file mode 100644
index 0000000..4fd8bca
--- /dev/null
+++ b/krita/plugins/paintops/libpaintop/kis_dab_cache.h
@@ -0,0 +1,132 @@
+/*
+ *  Copyright (c) 2012 Dmitry Kazakov <dimula73 at gmail.com>
+ *
+ *  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.
+ */
+
+#ifndef __KIS_DAB_CACHE_H
+#define __KIS_DAB_CACHE_H
+
+#include "krita_export.h"
+#include "kis_brush.h"
+
+class KisColorSource;
+class KisPressureSharpnessOption;
+class KisTextureProperties;
+class KisPressureMirrorOption;
+class KisPrecisionOption;
+
+//#define DEBUG_HIT_RATE
+
+#ifdef DEBUG_HIT_RATE
+#define DECLARE_HIT_RATE_VARS() int m_hitRate; int m_halfHitRate; int m_missRate
+#define INIT_HIT_RATE_VARS() m_hitRate = 0; m_halfHitRate = 0; m_missRate = 0
+#define COUNT_HIT() m_hitRate++
+#define COUNT_HALF_HIT() m_halfHitRate++
+#define COUNT_MISS() m_missRate++
+#define PRINT_HIT_RATE() if(m_hitRate + m_halfHitRate + m_missRate > 0) { \
+    qDebug() << "Hits:" << m_hitRate                                    \
+             << "HalfHits:" << m_halfHitRate                            \
+             << "Misses:" << m_missRate                                 \
+             << "HitRate:" << qreal(m_hitRate) / (m_hitRate + m_halfHitRate + m_missRate) \
+             << "HalfHitRate:" << qreal(m_halfHitRate) / (m_hitRate + m_halfHitRate + m_missRate); \
+    }
+#else
+#define DECLARE_HIT_RATE_VARS()
+#define INIT_HIT_RATE_VARS()
+#define COUNT_HIT()
+#define COUNT_HALF_HIT()
+#define COUNT_MISS()
+#define PRINT_HIT_RATE()
+#endif
+
+
+class PAINTOP_EXPORT KisDabCache
+{
+public:
+    KisDabCache(KisBrushSP brush);
+    ~KisDabCache();
+
+    void setMirrorPostprocessing(KisPressureMirrorOption *option);
+    void setSharpnessPostprocessing(KisPressureSharpnessOption *option);
+    void setTexturePostprocessing(KisTextureProperties *option);
+    void setPrecisionOption(KisPrecisionOption *option);
+
+    bool needSeparateOriginal();
+
+    KisFixedPaintDeviceSP fetchDab(const KoColorSpace *cs,
+                                   const KoColor& color,
+                                   double scaleX, double scaleY,
+                                   double angle,
+                                   const KisPaintInformation& info,
+                                   double subPixelX, double subPixelY,
+                                   qreal softnessFactor);
+
+    KisFixedPaintDeviceSP fetchDab(const KoColorSpace *cs,
+                                   const KisColorSource *colorSource,
+                                   double scaleX, double scaleY,
+                                   double angle,
+                                   const KisPaintInformation& info,
+                                   double subPixelX, double subPixelY,
+                                   qreal softnessFactor);
+
+private:
+    struct SavedDabParameters;
+
+private:
+    inline SavedDabParameters getDabParameters(const KoColor& color,
+                                               double scaleX, double scaleY,
+                                               double angle,
+                                               const KisPaintInformation& info,
+                                               double subPixelX, double subPixelY,
+                                               qreal softnessFactor);
+
+    inline KisFixedPaintDeviceSP tryFetchFromCache(const KisColorSource *colorSource,
+                                                   const KoColor& color,
+                                                   double scaleX, double scaleY,
+                                                   double angle,
+                                                   const KisPaintInformation& info,
+                                                   double subPixelX, double subPixelY,
+                                                   qreal softnessFactor);
+
+    inline KisFixedPaintDeviceSP fetchDabCommon(const KoColorSpace *cs,
+                                                const KisColorSource *colorSource,
+                                                const KoColor& color,
+                                                double scaleX, double scaleY,
+                                                double angle,
+                                                const KisPaintInformation& info,
+                                                double subPixelX, double subPixelY,
+                                                qreal softnessFactor);
+
+    void postProcessDab(KisFixedPaintDeviceSP dab, const KisPaintInformation& info);
+
+private:
+    KisFixedPaintDeviceSP m_dab;
+    KisFixedPaintDeviceSP m_dabOriginal;
+
+    KisBrushSP m_brush;
+    KisPaintDeviceSP m_colorSourceDevice;
+
+    KisPressureMirrorOption *m_mirrorOption;
+    KisPressureSharpnessOption *m_sharpnessOption;
+    KisTextureProperties *m_textureOption;
+    KisPrecisionOption *m_precisionOption;
+
+    SavedDabParameters *m_cachedDabParameters;
+
+    DECLARE_HIT_RATE_VARS();
+};
+
+#endif /* __KIS_DAB_CACHE_H */
diff --git a/krita/plugins/paintops/libpaintop/kis_precision_option.cpp b/krita/plugins/paintops/libpaintop/kis_precision_option.cpp
new file mode 100644
index 0000000..c7823c8
--- /dev/null
+++ b/krita/plugins/paintops/libpaintop/kis_precision_option.cpp
@@ -0,0 +1,41 @@
+/*
+ *  Copyright (c) 2012 Dmitry Kazakov <dimula73 at gmail.com>
+ *
+ *  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 "kis_precision_option.h"
+
+#include "kis_properties_configuration.h"
+
+void KisPrecisionOption::writeOptionSetting(KisPropertiesConfiguration* settings) const
+{
+    settings->setProperty(PRECISION_LEVEL, m_precisionLevel);
+}
+
+void KisPrecisionOption::readOptionSetting(const KisPropertiesConfiguration* settings)
+{
+    m_precisionLevel = settings->getInt(PRECISION_LEVEL, 4);
+}
+
+int KisPrecisionOption::precisionLevel() const
+{
+    return m_precisionLevel;
+}
+
+void KisPrecisionOption::setPrecisionLevel(int precisionLevel)
+{
+    m_precisionLevel = precisionLevel;
+}
diff --git a/krita/plugins/paintops/libpaintop/kis_brush_based_paintop_options_widget.h b/krita/plugins/paintops/libpaintop/kis_precision_option.h
similarity index 52%
copy from krita/plugins/paintops/libpaintop/kis_brush_based_paintop_options_widget.h
copy to krita/plugins/paintops/libpaintop/kis_precision_option.h
index 734df21..dc969cb 100644
--- a/krita/plugins/paintops/libpaintop/kis_brush_based_paintop_options_widget.h
+++ b/krita/plugins/paintops/libpaintop/kis_precision_option.h
@@ -1,5 +1,5 @@
 /*
- *  Copyright (c) 2010 Sven Langkamp <sven.langkamp at gmail.com>
+ *  Copyright (c) 2012 Dmitry Kazakov <dimula73 at gmail.com>
  *
  *  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
@@ -16,31 +16,27 @@
  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  */
 
-#ifndef KIS_BRUSH_BASED_PAINTOP_OPTIONS_WIDGET_H
-#define KIS_BRUSH_BASED_PAINTOP_OPTIONS_WIDGET_H
+#ifndef __KIS_PRECISION_OPTION_H
+#define __KIS_PRECISION_OPTION_H
 
-#include "kis_paintop_options_widget.h"
-#include "kis_types.h"
-#include "kis_brush.h"
+#include <QString>
 #include <krita_export.h>
+class KisPropertiesConfiguration;
 
-class KisBrushOptionWidget;
+const QString PRECISION_LEVEL = "KisPresisionOption/precisionLevel";
 
-class PAINTOP_EXPORT KisBrushBasedPaintopOptionWidget : public KisPaintOpOptionsWidget
+
+class PAINTOP_EXPORT KisPrecisionOption
 {
 public:
-    KisBrushBasedPaintopOptionWidget(QWidget* parent = 0);
-    virtual ~KisBrushBasedPaintopOptionWidget();
-
-    KisBrushSP brush();
-
-    void changePaintOpSize(qreal x, qreal y);
-    virtual QSizeF paintOpSize() const;
-    virtual bool presetIsValid();
+    void writeOptionSetting(KisPropertiesConfiguration* settings) const;
+    void readOptionSetting(const KisPropertiesConfiguration* settings);
 
+    int precisionLevel() const;
+    void setPrecisionLevel(int precisionLevel);
 
 private:
-    KisBrushOptionWidget * m_brushOption;
+    int m_precisionLevel;
 };
 
-#endif // KIS_BRUSH_BASED_PAINTOP_OPTIONS_WIDGET_H
+#endif /* __KIS_PRECISION_OPTION_H */
diff --git a/krita/plugins/paintops/libpaintop/kis_pressure_sharpness_option.cpp b/krita/plugins/paintops/libpaintop/kis_pressure_sharpness_option.cpp
index 6157e5e..54aabdc 100644
--- a/krita/plugins/paintops/libpaintop/kis_pressure_sharpness_option.cpp
+++ b/krita/plugins/paintops/libpaintop/kis_pressure_sharpness_option.cpp
@@ -80,7 +80,7 @@ void KisPressureSharpnessOption::apply(const KisPaintInformation &info, const QP
     }
 }
 
-void KisPressureSharpnessOption::applyTreshold(KisFixedPaintDeviceSP dab)
+void KisPressureSharpnessOption::applyThreshold(KisFixedPaintDeviceSP dab)
 {
     if (!isChecked()) return;
     const KoColorSpace * cs = dab->colorSpace();
diff --git a/krita/plugins/paintops/libpaintop/kis_pressure_sharpness_option.h b/krita/plugins/paintops/libpaintop/kis_pressure_sharpness_option.h
index 8281da4..73d4f02 100644
--- a/krita/plugins/paintops/libpaintop/kis_pressure_sharpness_option.h
+++ b/krita/plugins/paintops/libpaintop/kis_pressure_sharpness_option.h
@@ -45,7 +45,7 @@ public:
     /**
     * Apply threshold specified by user
     */
-    void applyTreshold(KisFixedPaintDeviceSP dab);
+    void applyThreshold(KisFixedPaintDeviceSP dab);
 
     void writeOptionSetting(KisPropertiesConfiguration* setting) const;
     void readOptionSetting(const KisPropertiesConfiguration* setting);



More information about the kimageshop mailing list