diff options
author | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-01-20 01:29:50 +0000 |
---|---|---|
committer | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-01-20 01:29:50 +0000 |
commit | 8362bf63dea22bbf6736609b0f49c152f975eb63 (patch) | |
tree | 0eea3928e39e50fae91d4e68b21b1e6cbae25604 /krita/core | |
download | koffice-8362bf63dea22bbf6736609b0f49c152f975eb63.tar.gz koffice-8362bf63dea22bbf6736609b0f49c152f975eb63.zip |
Added old abandoned KDE3 version of koffice
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/applications/koffice@1077364 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'krita/core')
154 files changed, 30734 insertions, 0 deletions
diff --git a/krita/core/Makefile.am b/krita/core/Makefile.am new file mode 100644 index 00000000..56a0865e --- /dev/null +++ b/krita/core/Makefile.am @@ -0,0 +1,59 @@ +# all_includes must remain last! +INCLUDES = \ + -I$(srcdir)/../sdk \ + -I$(srcdir)/tiles \ + -I$(srcdir)/../kritacolor \ + $(KOFFICE_INCLUDES) \ + $(KOPAINTER_INCLUDES) \ + $(OPENEXR_CFLAGS) \ + $(all_includes) + +#CXXFLAGS = -shared -fPIC + +lib_LTLIBRARIES = libkritaimage.la + +libkritaimage_la_SOURCES = kis_adjustment_layer.cc kis_alpha_mask.cc \ + kis_autobrush_resource.cc kis_autogradient_resource.cc kis_background.cc kis_boundary.cc \ + kis_brush.cc kis_command.cc kis_convolution_painter.cc kis_fill_painter.cc \ + kis_filter.cc kis_filter_registry.cc kis_filter_strategy.cc \ + kis_filter_configuration.cc kis_filter_config_widget.cc kis_gradient.cc kis_gradient_painter.cc \ + kis_histogram.cc kis_image.cc kis_imagepipe_brush.cc kis_iterator.cc \ + kis_iterators_pixel.cc kis_layer.cc kis_group_layer.cc kis_paint_layer.cc kis_meta_registry.cc \ + kis_nameserver.cc kis_painter.cc kis_paintop.cc kis_paintop_registry.cc kis_palette.cc \ + kis_pattern.cc kis_rect.cc kis_resource.cc kis_rotate_visitor.cc \ + kis_selected_transaction.cc kis_selection.cc kis_strategy_move.cc kis_transaction.cc \ + kis_transform_worker.cc kis_vec.cc kis_paint_device.cc kis_paint_device_iface.cc \ + 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_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 \ + kis_merge_visitor.h kis_thread.h kis_thread_pool.h kis_change_profile_visitor.h \ + kis_perspective_grid.h kis_perspectivetransform_worker.h + +include_HEADERS = kis_adjustment_layer.h kis_alpha_mask.h \ + kis_autobrush_resource.h kis_autogradient_resource.h kis_background.h kis_boundary.h kis_brush.h \ + kis_command.h kis_convolution_painter.h kis_fill_painter.h kis_filter.h \ + kis_filter_registry.h kis_gradient.h kis_gradient_painter.h kis_histogram.h kis_image.h \ + kis_image_iface.h kis_imagepipe_brush.h kis_iterator.h kis_iterators_pixel.h \ + kis_iteratorpixeltrait.h kis_layer.h kis_meta_registry.h kis_nameserver.h \ + 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_scale_visitor.h kis_paint_layer.h kis_layer_visitor.h kis_filter_strategy.h kis_transform_worker.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) + +if include_kunittest_tests +TESTSDIR = tests +endif + +SUBDIRS = tiles . $(TESTSDIR) + +libkritaimage_la_METASOURCES = AUTO + +KDE_OPTIONS = nofinal + diff --git a/krita/core/createdcop.py b/krita/core/createdcop.py new file mode 100755 index 00000000..b3395ea2 --- /dev/null +++ b/krita/core/createdcop.py @@ -0,0 +1,171 @@ +#!/usr/bin/env python + +import os, sys + +dcopiface_header = """/* This file is part of the KDE project + * Copyright (C) 2005 Boudewijn Rempt <boud@valdyas.org> + * + * 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 %(classname_upper)sIFACE_H +#define %(classname_upper)sIFACE_H + +#include <dcopref.h> +#include <dcopobj.h> + +#include <qstring.h> + +class %(classname)s; + +class %(classname)sIface : virtual public DCOPObject +{ + K_DCOP +public: + %(classname)sIface( %(classname)s * parent ); +k_dcop: + +private: + + %(classname)s *m_parent; +}; + +#endif +""" + +dcopiface_template = """/* + * This file is part of the KDE project + * + * Copyright (C) 2005 Boudewijn Rempt <boud@valdyas.org> + * + * 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 <kapplication.h> + + +#include "%(ifaceheader)s" + +#include "%(classheader)s" + +#include <dcopclient.h> + +%(classname)sIface::%(classname)sIface( %(classname)s * parent ) + : DCOPObject() +{ + m_parent = parent; +} + +""" + +def parseHeader(headerfile, classname): + # parse the source class header to get a list of functions we're going to wrap + functions = [] + if (headerfile.find("private:") > -1): + lines = headerfile[headerfile.find(classname):headerfile.find("private")].splitlines() + else: + lines = headerfile[headerfile.find(classname):headerfile.find("#endif")].splitlines() + i = 0 + while i < len(lines): + line = lines[i].strip() + if (line.startswith("/") or + line.startswith("public:") or + line.startswith("*") or + line.startswith(classname) or + line.startswith("class") or + line.startswith("Q_OBJECT") or + line.startswith("#") or + line.startswith("}") or + line.startswith("public slots:") or + line.find("~") != -1 or + len(line) == 0 + ): + i+=1 + continue + if (line.startswith("protected")): + return functions + # by now we are reasonable sure that this is a function. We need to find the end of the function definition, and then + # if the return type is not primitive, replace it with dcopref. + function = line + complete = 0 + # strip the inline implementation + if (line.find("{") > -1): + function = line[:line.find("{")] + if function.find("}") > -1: + function += line[line.find("}") + 1:] + complete = 1 + else: + i += 1 + # search for the missing } on the next lines + while i < len(lines): + if (lines[i].find("}") > -1): + function += lines[i][lines[i].find("}") + 1:] + complete = 1 + i += 1 + else: + complete = 1 + + if complete == 0: + i+=1 + continue + + if (function.endswith("= 0;")): + function = function[:-4] + ";" + print "\t", function + i+=1 + + +def createDCOP(header): + + # Determine filenames and classnames + + implementation = header[:-1] + "cc" + classname = "" + classname_upper ="_" + for part in header[:-2].split("_"): + classname = classname + part.capitalize() + classname_upper = classname_upper + part.upper() + "_" + ifaceheader = header[:-2] + "_iface.h" + ifaceimplementation = header[:-2] + "_iface.cc" + ifaceclass = classname + "Iface" + + #print "with: ", implementation, classname, classname_upper, ifaceheader, ifaceimplementation, ifaceclass + file(ifaceheader, "w+").write(dcopiface_header % { "classname_upper" : classname_upper, + "classname" : classname}) + file(ifaceimplementation, "w+").write(dcopiface_template % {"ifaceheader" : ifaceheader, + "classheader" : header, + "classname" : classname }) + functions = parseHeader(open(header).read(), classname) + +def main(args): + for line in args[1:]: + print "Going to create a dcop interface for:", line[:-1] + createDCOP(line.strip()) + +if __name__=="__main__": + main(sys.argv) + + diff --git a/krita/core/kis_adjustment_layer.cc b/krita/core/kis_adjustment_layer.cc new file mode 100644 index 00000000..873b24cf --- /dev/null +++ b/krita/core/kis_adjustment_layer.cc @@ -0,0 +1,252 @@ +/* + * Copyright (c) 2002 Patrick Julien <freak@codepimps.org> + * Copyright (c) 2005 Casper Boemann <cbr@boemann.dk> + * + * 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. + */ + +#include <kdebug.h> +#include <qimage.h> + +#include "kis_debug_areas.h" +#include "kis_group_layer.h" +#include "kis_image.h" +#include "kis_layer.h" +#include "kis_adjustment_layer.h" +#include "kis_painter.h" +#include "kis_undo_adapter.h" +#include "kis_selection.h" +#include "kis_fill_painter.h" + +KisAdjustmentLayer::KisAdjustmentLayer(KisImageSP img, const QString &name, KisFilterConfiguration * kfc, KisSelectionSP selection) + : KisLayer (img, name, OPACITY_OPAQUE) +{ + m_filterConfig = kfc; + setSelection( selection ); + m_cachedPaintDev = new KisPaintDevice( img->colorSpace(), name.latin1()); + m_showSelection = true; + Q_ASSERT(m_cachedPaintDev); + connect(img, SIGNAL(sigSelectionChanged(KisImageSP)), + this, SLOT(slotSelectionChanged(KisImageSP))); +} + +KisAdjustmentLayer::KisAdjustmentLayer(const KisAdjustmentLayer& rhs) + : KisLayer(rhs), KisLayerSupportsIndirectPainting(rhs) +{ + m_filterConfig = new KisFilterConfiguration(*rhs.m_filterConfig); + if (rhs.m_selection) { + m_selection = new KisSelection( *rhs.m_selection.data() ); + m_selection->setParentLayer(this); + m_selection->setInterestedInDirtyness(true); + connect(rhs.image(), SIGNAL(sigSelectionChanged(KisImageSP)), + this, SLOT(slotSelectionChanged(KisImageSP))); + } + m_cachedPaintDev = new KisPaintDevice( *rhs.m_cachedPaintDev.data() ); + m_showSelection = false; +} + + +KisAdjustmentLayer::~KisAdjustmentLayer() +{ + delete m_filterConfig; +} + + +KisLayerSP KisAdjustmentLayer::clone() const +{ + return new KisAdjustmentLayer(*this); +} + + +void KisAdjustmentLayer::resetCache() +{ + m_cachedPaintDev = new KisPaintDevice(image()->colorSpace(), name().latin1()); +} + +KisFilterConfiguration * KisAdjustmentLayer::filter() +{ + Q_ASSERT(m_filterConfig); + return m_filterConfig; +} + + +void KisAdjustmentLayer::setFilter(KisFilterConfiguration * filterConfig) +{ + Q_ASSERT(filterConfig); + m_filterConfig = filterConfig; +} + + +KisSelectionSP KisAdjustmentLayer::selection() +{ + return m_selection; +} + +void KisAdjustmentLayer::setSelection(KisSelectionSP selection) +{ + m_selection = new KisSelection(); + KisFillPainter gc(m_selection.data()); + KisColorSpace * cs = KisMetaRegistry::instance()->csRegistry()->getRGB8(); + + if (selection) { + gc.bitBlt(0, 0, COMPOSITE_COPY, selection.data(), + 0, 0, image()->bounds().width(), image()->bounds().height()); + } else { + gc.fillRect(image()->bounds(), KisColor(Qt::white, cs), MAX_SELECTED); + } + + gc.end(); + + m_selection->setParentLayer(this); + m_selection->setInterestedInDirtyness(true); +} + +void KisAdjustmentLayer::clearSelection() +{ + KisFillPainter gc(m_selection.data()); + KisColorSpace * cs = KisMetaRegistry::instance()->csRegistry()->getRGB8(); + + QRect bounds = extent(); + bounds |= image()->bounds(); + gc.fillRect(bounds, KisColor(Qt::white, cs), MIN_SELECTED); + gc.end(); +} + + +Q_INT32 KisAdjustmentLayer::x() const +{ + if (m_selection) + return m_selection->getX(); + else + return 0; +} + +void KisAdjustmentLayer::setX(Q_INT32 x) +{ + if (m_selection) { + m_selection->setX(x); + resetCache(); + } + +} + +Q_INT32 KisAdjustmentLayer::y() const +{ + if (m_selection) + return m_selection->getY(); + else + return 0; +} + +void KisAdjustmentLayer::setY(Q_INT32 y) +{ + if (m_selection) { + m_selection->setY(y); + resetCache(); + } +} + +QRect KisAdjustmentLayer::extent() const +{ + if (m_selection) + return m_selection->selectedRect(); + else if (image()) + return image()->bounds(); + else + return QRect(); +} + +QRect KisAdjustmentLayer::exactBounds() const +{ + if (m_selection) + return m_selection->selectedRect(); + else if (image()) + return image()->bounds(); + else + return QRect(); +} + +bool KisAdjustmentLayer::accept(KisLayerVisitor & v) +{ + return v.visit( this ); +} + +void KisAdjustmentLayer::paintSelection(QImage &img, Q_INT32 x, Q_INT32 y, Q_INT32 w, Q_INT32 h) +{ + if (showSelection() && selection()) + selection()->paintSelection(img, x, y, w, h); +} + +void KisAdjustmentLayer::paintSelection(QImage &img, const QRect& scaledImageRect, const QSize& scaledImageSize, const QSize& imageSize) +{ + if (showSelection() && selection()) + selection()->paintSelection(img, scaledImageRect, scaledImageSize, imageSize); +} + +QImage KisAdjustmentLayer::createThumbnail(Q_INT32 w, Q_INT32 h) +{ + if (!selection()) + return QImage(); + + int srcw, srch; + if( image() ) + { + srcw = image()->width(); + srch = image()->height(); + } + else + { + const QRect e = extent(); + srcw = e.width(); + srch = e.height(); + } + + if (w > srcw) + { + w = srcw; + h = Q_INT32(double(srcw) / w * h); + } + if (h > srch) + { + h = srch; + w = Q_INT32(double(srch) / h * w); + } + + if (srcw > srch) + h = Q_INT32(double(srch) / srcw * w); + else if (srch > srcw) + w = Q_INT32(double(srcw) / srch * h); + + QColor c; + Q_UINT8 opacity; + QImage img(w,h,32); + + for (Q_INT32 y=0; y < h; ++y) { + Q_INT32 iY = (y * srch ) / h; + for (Q_INT32 x=0; x < w; ++x) { + Q_INT32 iX = (x * srcw ) / w; + m_selection->pixel(iX, iY, &c, &opacity); + img.setPixel(x, y, qRgb(opacity, opacity, opacity)); + } + } + + return img; +} + +void KisAdjustmentLayer::slotSelectionChanged(KisImageSP image) { + image->setModified(); +} + +#include "kis_adjustment_layer.moc" diff --git a/krita/core/kis_adjustment_layer.h b/krita/core/kis_adjustment_layer.h new file mode 100644 index 00000000..0137bc8f --- /dev/null +++ b/krita/core/kis_adjustment_layer.h @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2006 Boudewijn Rempt + * + * 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_ADJUSTMENT_LAYER_H_ +#define KIS_ADJUSTMENT_LAYER_H_ + +#include <qobject.h> +#include "kis_types.h" +#include "kis_layer_visitor.h" +#include "kis_composite_op.h" +#include <koffice_export.h> + +class KNamedCommand; +class QPainter; +class KisUndoAdapter; +class KisGroupLayer; +class KisFilterConfiguration; + +/** + * Class that contains a KisFilter and optionally a KisSelection. The combination + * is used by to influence the rendering of the layers under this layer in the + * layerstack + **/ +class KRITACORE_EXPORT KisAdjustmentLayer : public KisLayer, public KisLayerSupportsIndirectPainting +{ + Q_OBJECT + +public: + /** + * Create a new adjustment layer with the given configuration and selection. + * Note that the selection will be _copied_. + */ + KisAdjustmentLayer(KisImageSP img, const QString &name, KisFilterConfiguration * kfc, KisSelectionSP selection); + KisAdjustmentLayer(const KisAdjustmentLayer& rhs); + virtual ~KisAdjustmentLayer(); + + /// Return a copy of this layer + virtual KisLayerSP clone() const; + +public: + + KisFilterConfiguration * filter(); + void setFilter(KisFilterConfiguration * filterConfig); + + KisSelectionSP selection(); + + /// Set the selction of this adjustment layer to a copy of selection. + void setSelection(KisSelectionSP selection); + + /// Clears the selection (doesn't call any of the update or dirty methods) + void clearSelection(); + + virtual void paintSelection(QImage &img, Q_INT32 x, Q_INT32 y, Q_INT32 w, Q_INT32 h); + virtual void paintSelection(QImage &img, const QRect& scaledImageRect, const QSize& scaledImageSize, const QSize& imageSize); +public: + + virtual Q_INT32 x() const; + virtual void setX(Q_INT32); + + virtual Q_INT32 y() const; + virtual void setY(Q_INT32); + + /// Returns an approximation of where the bounds on actual data are in this layer + virtual QRect extent() const; + + /// Returns the exact bounds of where the actual data resides in this layer + virtual QRect exactBounds() const; + + virtual bool accept(KisLayerVisitor &); + + virtual void resetCache(); + virtual KisPaintDeviceSP cachedPaintDevice() { return m_cachedPaintDev; } + + bool showSelection() const { return m_showSelection; } + void setSelection(bool b) { m_showSelection = b; } + + virtual QImage createThumbnail(Q_INT32 w, Q_INT32 h); + + // KisLayerSupportsIndirectPainting + virtual KisLayer* layer() { return this; } +private: + bool m_showSelection; + KisFilterConfiguration * m_filterConfig; + KisSelectionSP m_selection; + KisPaintDeviceSP m_cachedPaintDev; +private slots: + void slotSelectionChanged(KisImageSP image); +}; + +#endif // KIS_ADJUSTMENT_LAYER_H_ + diff --git a/krita/core/kis_alpha_mask.cc b/krita/core/kis_alpha_mask.cc new file mode 100644 index 00000000..7a0fcd81 --- /dev/null +++ b/krita/core/kis_alpha_mask.cc @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2004 Boudewijn Rempt <boud@valdyas.org> + * + * 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 <cfloat> +#include <qimage.h> +#include <qvaluevector.h> + +#include <kdebug.h> + +#include "kis_global.h" +#include "kis_alpha_mask.h" + +KisAlphaMask::KisAlphaMask(const QImage& img, bool hasColor) +{ + m_width = img.width(); + m_height = img.height(); + + if (hasColor) { + copyAlpha(img); + } + else { + computeAlpha(img); + } +} + +KisAlphaMask::KisAlphaMask(const QImage& img) +{ + m_width = img.width(); + m_height = img.height(); + + if (!img.allGray()) { + copyAlpha(img); + } + else { + computeAlpha(img); + } +} + +KisAlphaMask::KisAlphaMask(Q_INT32 width, Q_INT32 height) +{ + m_width = width; + m_height = height; + + m_data.resize(width * height, OPACITY_TRANSPARENT); +} + +KisAlphaMask::~KisAlphaMask() +{ +} + +Q_INT32 KisAlphaMask::width() const +{ + return m_width; +} + +Q_INT32 KisAlphaMask::height() const +{ + return m_height; +} + +void KisAlphaMask::setAlphaAt(Q_INT32 x, Q_INT32 y, Q_UINT8 alpha) +{ + if (y >= 0 && y < m_height && x >= 0 && x < m_width) { + m_data[(y * m_width) + x] = alpha; + } +} + +void KisAlphaMask::copyAlpha(const QImage& img) +{ + for (int y = 0; y < img.height(); y++) { + for (int x = 0; x < img.width(); x++) { + QRgb c = img.pixel(x,y); + Q_UINT8 a = (qGray(c) * qAlpha(c)) / 255; + m_data.push_back(a); + + } + } +} + +void KisAlphaMask::computeAlpha(const QImage& img) +{ + // The brushes are mostly grayscale on a white background, + // although some do have a colors. The alpha channel is seldom + // used, so we take the average gray value of this pixel of + // the brush as the setting for the opacitiy. We need to + // invert it, because 255, 255, 255 is white, which is + // completely transparent, but 255 corresponds to + // OPACITY_OPAQUE. + + for (int y = 0; y < img.height(); y++) { + for (int x = 0; x < img.width(); x++) { + m_data.push_back(255 - qRed(img.pixel(x, y))); + } + } +} + +KisAlphaMaskSP KisAlphaMask::interpolate(KisAlphaMaskSP mask1, KisAlphaMaskSP mask2, double t) +{ + Q_ASSERT((mask1->width() == mask2->width()) && (mask1->height() == mask2->height())); + Q_ASSERT(t > -DBL_EPSILON && t < 1 + DBL_EPSILON); + + int width = mask1->width(); + int height = mask1->height(); + KisAlphaMaskSP outputMask = new KisAlphaMask(width, height); + Q_CHECK_PTR(outputMask); + + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + Q_UINT8 d = static_cast<Q_UINT8>((1 - t) * mask1->alphaAt(x, y) + t * mask2->alphaAt(x, y)); + outputMask->setAlphaAt(x, y, d); + } + } + + return outputMask; +} + + diff --git a/krita/core/kis_alpha_mask.h b/krita/core/kis_alpha_mask.h new file mode 100644 index 00000000..1da3384d --- /dev/null +++ b/krita/core/kis_alpha_mask.h @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2004 Boudewijn Rempt <boud@valdyas.org> + * + * 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_ALPHA_MASK_ +#define KIS_ALPHA_MASK_ + +#include <qimage.h> +#include <qvaluevector.h> + +#include <ksharedptr.h> + +#include "kis_global.h" +#include "kis_types.h" + +/** + * KisAlphaMask is intended to create alpha values from a QImage for use + * in brush creation. It is not a generic alpha mask that can be used with + * KisPaintDevices: use a KisSelection for that. + */ +class KisAlphaMask : public KShared { + + public: + /** + Create an alpha mask based on the specified QImage. If the image is + not a grayscale, the mask value is calculated from the effective grey + level and alpha value. + */ + KisAlphaMask(const QImage& img); + + /** + As above except quicker as the image does not need to be scanned + to see if it has any colour pixels. + */ + KisAlphaMask(const QImage& img, bool hasColor); + + /** + Create a transparent mask. + */ + KisAlphaMask(Q_INT32 width, Q_INT32 height); + + virtual ~KisAlphaMask(); + + /** + @return the number of alpha values in a scanline. + */ + Q_INT32 height() const; + + /** + @return the number of lines in the mask. + */ + Q_INT32 width() const; + + /** + @return the alpha value at the specified position. + + Returns Q_UINT8 OPACITY_TRANSPARENT if the value is + outside the bounds of the mask. + + XXX: this is, of course, not the best way of masking. + Better would be to let KisAlphaMask fill a chunk of memory + with the alpha values at the right position, something like + void applyMask(Q_UINT8 *pixeldata, Q_INT32 pixelWidth, + Q_INT32 alphaPos). That would be fastest, or we could + provide an iterator over the mask, that would be nice, too. + */ + inline Q_UINT8 alphaAt(Q_INT32 x, Q_INT32 y) const + { + if (y >= 0 && y < m_height && x >= 0 && x < m_width) { + return m_data[(y * m_width) + x]; + } + else { + return OPACITY_TRANSPARENT; + } + } + + void setAlphaAt(Q_INT32 x, Q_INT32 y, Q_UINT8 alpha); + + // Create a new mask by interpolating between mask1 and mask2 as t + // goes from 0 to 1. + static KisAlphaMaskSP interpolate(KisAlphaMaskSP mask1, KisAlphaMaskSP mask2, double t); + +private: + void computeAlpha(const QImage& img); + void copyAlpha(const QImage& img); + + QValueVector<Q_UINT8> m_data; + Q_INT32 m_width; + Q_INT32 m_height; +}; + +#endif // KIS_ALPHA_MASK_ + diff --git a/krita/core/kis_autobrush_resource.cc b/krita/core/kis_autobrush_resource.cc new file mode 100644 index 00000000..6e45c64a --- /dev/null +++ b/krita/core/kis_autobrush_resource.cc @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2004 Cyrille Berger <cberger@cberger.net> + * + * 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_autobrush_resource.h" +#include <kdebug.h> + +void KisAutobrushShape::createBrush( QImage* img) +{ + img->create(m_w, m_h, 32); + for(int j = 0; j < m_h; j++) + { + for(int i = 0; i < m_w; i++) + { + Q_INT8 v = valueAt(i,j); + img->setPixel( i, j, qRgb(v,v,v)); + } + } +} + +KisAutobrushCircleShape::KisAutobrushCircleShape(Q_INT32 w, Q_INT32 h, double fh, double fv) + : KisAutobrushShape( w, h, w / 2.0 - fh, h / 2.0 - fv), + m_xcentre ( w / 2.0 ), + m_ycentre ( h / 2.0 ), + m_xcoef ( 2.0 / w ), + m_ycoef ( 2.0 / h ), + m_xfadecoef ( (m_fh == 0) ? 1 : ( 1.0 / m_fh)), + m_yfadecoef ( (m_fv == 0) ? 1 : ( 1.0 / m_fv)) +{ +} +Q_INT8 KisAutobrushCircleShape::valueAt(Q_INT32 x, Q_INT32 y) +{ + double xr = (x - m_xcentre) + 0.5; + double yr = (y - m_ycentre) + 0.5; + double n = norme( xr * m_xcoef, yr * m_ycoef); + if( n > 1 ) + { + return 255; + } + else + { + double normeFade = norme( xr * m_xfadecoef, yr * m_yfadecoef ); + if( normeFade > 1) + { + double xle, yle; + // xle stands for x-coordinate limit exterior + // yle stands for y-coordinate limit exterior + // we are computing the coordinate on the external ellipse in order to compute + // the fade value + if( xr == 0 ) + { + xle = 0; + yle = yr > 0 ? 1/m_ycoef : -1/m_ycoef; + } else { + double c = yr / (double)xr; + xle = sqrt(1 / norme( m_xcoef, c * m_ycoef )); + xle = xr > 0 ? xle : -xle; + yle = xle * c; + } + // On the internal limit of the fade area, normeFade is equal to 1 + double normeFadeLimitE = norme( xle * m_xfadecoef, yle * m_yfadecoef ); + return (uchar)(255 * ( normeFade - 1 ) / ( normeFadeLimitE - 1 )); + } else { + return 0; + } + } +} + +KisAutobrushRectShape::KisAutobrushRectShape(Q_INT32 w, Q_INT32 h, double fh, double fv) + : KisAutobrushShape( w, h, w / 2.0 - fh, h / 2.0 - fv), + m_xcentre ( w / 2.0 ), + m_ycentre ( h / 2.0 ), + m_c( fv/fh) +{ +} +Q_INT8 KisAutobrushRectShape::valueAt(Q_INT32 x, Q_INT32 y) +{ + double xr = QABS(x - m_xcentre); + double yr = QABS(y - m_ycentre); + if( xr > m_fh || yr > m_fv ) + { + if( yr <= ((xr - m_fh) * m_c + m_fv ) ) + { + return (uchar)(255 * (xr - m_fh) / (m_w - m_fh)); + } else { + return (uchar)(255 * (yr - m_fv) / (m_w - m_fv)); + } + } + else { + return 0; + } +} diff --git a/krita/core/kis_autobrush_resource.h b/krita/core/kis_autobrush_resource.h new file mode 100644 index 00000000..d0c77767 --- /dev/null +++ b/krita/core/kis_autobrush_resource.h @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2004 Cyrille Berger <cberger@cberger.net> + * + * 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_AUTOBRUSH_RESOURCE_H_ +#define _KIS_AUTOBRUSH_RESOURCE_H_ + +#include "kis_brush.h" + +class KisAutobrushShape { + public: + KisAutobrushShape(Q_INT32 w, Q_INT32 h, double fh, double fv) : m_w(w), m_h(h), m_fh(fh), m_fv(fv) + { }; + void createBrush( QImage* img); + protected: + virtual Q_INT8 valueAt(Q_INT32 x, Q_INT32 y) =0; + Q_INT32 m_w, m_h; + double m_fh, m_fv; +}; + +class KisAutobrushCircleShape : public KisAutobrushShape { + public: + KisAutobrushCircleShape(Q_INT32 w, Q_INT32 h, double fh, double fv); + public: + virtual Q_INT8 valueAt(Q_INT32 x, Q_INT32 y); + private: + double norme(double a, double b) + { + return a*a + b * b; + } + private: + double m_xcentre, m_ycentre; + double m_xcoef, m_ycoef; + double m_xfadecoef, m_yfadecoef; +}; + +class KisAutobrushRectShape : public KisAutobrushShape { + public: + KisAutobrushRectShape(Q_INT32 w, Q_INT32 h, double fh, double fv); + protected: + virtual Q_INT8 valueAt(Q_INT32 x, Q_INT32 y); + private: + double m_xcentre, m_ycentre, m_c; +}; + +class KisAutobrushResource : public KisBrush +{ + public: + KisAutobrushResource(QImage& img) : KisBrush("") + { + setImage(img); + setBrushType(MASK); + }; + public: + virtual bool load() { return false; }; +}; +#endif // _KIS_AUTOBRUSH_RESOURCE_H_ diff --git a/krita/core/kis_autogradient_resource.cc b/krita/core/kis_autogradient_resource.cc new file mode 100644 index 00000000..f021222d --- /dev/null +++ b/krita/core/kis_autogradient_resource.cc @@ -0,0 +1,221 @@ +/* + * Copyright (c) 2004 Cyrille Berger <cberger@cberger.net> + * 2004 Sven Langkamp <longamp@reallygood.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 "kis_gradient.h" +#include "kis_autogradient_resource.h" + +// FIXME: use the same #define as in kis_gradient.cc, probably best customizable? +#define PREVIEW_WIDTH 64 +#define PREVIEW_HEIGHT 64 + + +void KisAutogradientResource::createSegment( int interpolation, int colorInterpolation, double startOffset, double endOffset, double middleOffset, QColor left, QColor right ) +{ + pushSegment(new KisGradientSegment(interpolation, colorInterpolation, startOffset, middleOffset, endOffset, Color( left, 1 ), Color( right, 1 ))); + +} + +const QValueVector<double> KisAutogradientResource::getHandlePositions() const +{ + QValueVector<double> handlePositions; + + handlePositions.push_back(m_segments[0]->startOffset()); + for (uint i = 0; i < m_segments.count(); i++) + { + handlePositions.push_back(m_segments[i]->endOffset()); + } + return handlePositions; +} + +const QValueVector<double> KisAutogradientResource::getMiddleHandlePositions() const +{ + QValueVector<double> middleHandlePositions; + + for (uint i = 0; i < m_segments.count(); i++) + { + middleHandlePositions.push_back(m_segments[i]->middleOffset()); + } + return middleHandlePositions; +} + +void KisAutogradientResource::moveSegmentStartOffset( KisGradientSegment* segment, double t) +{ + QValueVector<KisGradientSegment*>::iterator it = qFind( m_segments.begin(), m_segments.end(), segment ); + if ( it != m_segments.end() ) + { + if ( it == m_segments.begin() ) + { + segment->setStartOffset( 0.0 ); + return; + } + KisGradientSegment* previousSegment = (*(it-1)); + if ( t > segment->startOffset() ) + { + if( t > segment->middleOffset() ) + t = segment->middleOffset(); + } + else { + if( t < previousSegment->middleOffset() ) + t = previousSegment->middleOffset(); + } + previousSegment->setEndOffset( t ); + segment->setStartOffset( t ); + } +} + +void KisAutogradientResource::moveSegmentEndOffset( KisGradientSegment* segment, double t) +{ + QValueVector<KisGradientSegment*>::iterator it = qFind( m_segments.begin(), m_segments.end(), segment ); + if ( it != m_segments.end() ) + { + if ( it+1 == m_segments.end() ) + { + segment->setEndOffset( 1.0 ); + return; + } + KisGradientSegment* followingSegment = (*(it+1)); + if ( t < segment->endOffset() ) + { + if( t < segment->middleOffset() ) + t = segment->middleOffset(); + } + else { + if( t > followingSegment->middleOffset() ) + t = followingSegment->middleOffset(); + } + followingSegment->setStartOffset( t ); + segment->setEndOffset( t ); + } +} + +void KisAutogradientResource::moveSegmentMiddleOffset( KisGradientSegment* segment, double t) +{ + if( segment ) + { + if( t > segment->endOffset() ) + segment->setMiddleOffset( segment->endOffset() ); + else if( t < segment->startOffset() ) + segment->setMiddleOffset( segment->startOffset() ); + else + segment->setMiddleOffset( t ); + } +} + +void KisAutogradientResource::splitSegment( KisGradientSegment* segment ) +{ + Q_ASSERT(segment != 0); + QValueVector<KisGradientSegment*>::iterator it = qFind( m_segments.begin(), m_segments.end(), segment ); + if ( it != m_segments.end() ) + { + KisGradientSegment* newSegment = new KisGradientSegment( + segment->interpolation(), segment->colorInterpolation(), + segment ->startOffset(), + ( segment->middleOffset() - segment->startOffset() ) / 2 + segment->startOffset(), + segment->middleOffset(), + segment->startColor(), + segment->colorAt( segment->middleOffset() ) ); + m_segments.insert( it, newSegment ); + segment->setStartColor( segment->colorAt( segment->middleOffset() ) ); + segment->setStartOffset( segment->middleOffset() ); + segment->setMiddleOffset( ( segment->endOffset() - segment->startOffset() ) / 2 + segment->startOffset() ); + } +} + +void KisAutogradientResource::duplicateSegment( KisGradientSegment* segment ) +{ + Q_ASSERT(segment != 0); + QValueVector<KisGradientSegment*>::iterator it = qFind( m_segments.begin(), m_segments.end(), segment ); + if ( it != m_segments.end() ) + { + double middlePostionPercentage = ( segment->middleOffset() - segment->startOffset() ) / segment->length(); + double center = segment->startOffset() + segment->length() / 2; + KisGradientSegment* newSegment = new KisGradientSegment( + segment->interpolation(), segment->colorInterpolation(), + segment ->startOffset(), + segment->length() / 2 * middlePostionPercentage + segment->startOffset(), + center, segment->startColor(), + segment->endColor() ); + m_segments.insert( it, newSegment ); + segment->setStartOffset( center ); + segment->setMiddleOffset( segment->length() * middlePostionPercentage + segment->startOffset() ); + } +} + +void KisAutogradientResource::mirrorSegment( KisGradientSegment* segment ) +{ + Q_ASSERT(segment != 0); + Color tmpColor = segment->startColor(); + segment->setStartColor( segment->endColor() ); + segment->setEndColor( tmpColor ); + segment->setMiddleOffset( segment->endOffset() - ( segment->middleOffset() - segment->startOffset() ) ); + + if( segment->interpolation() == INTERP_SPHERE_INCREASING ) + segment->setInterpolation( INTERP_SPHERE_DECREASING ); + else if( segment->interpolation() == INTERP_SPHERE_DECREASING ) + segment->setInterpolation( INTERP_SPHERE_INCREASING ); + + if( segment->colorInterpolation() == COLOR_INTERP_HSV_CW ) + segment->setColorInterpolation( COLOR_INTERP_HSV_CCW ); + else if( segment->colorInterpolation() == COLOR_INTERP_HSV_CCW ) + segment->setColorInterpolation( COLOR_INTERP_HSV_CW ); +} + +KisGradientSegment* KisAutogradientResource::removeSegment( KisGradientSegment* segment ) +{ + Q_ASSERT(segment != 0); + if( m_segments.count() < 2 ) + return 0; + QValueVector<KisGradientSegment*>::iterator it = qFind( m_segments.begin(), m_segments.end(), segment ); + if ( it != m_segments.end() ) + { + double middlePostionPercentage; + KisGradientSegment* nextSegment; + if( it == m_segments.begin() ) + { + nextSegment = (*(it+1)); + middlePostionPercentage = ( nextSegment->middleOffset() - nextSegment->startOffset() ) / nextSegment->length(); + nextSegment->setStartOffset( segment->startOffset() ); + nextSegment->setMiddleOffset( middlePostionPercentage * nextSegment->length() + nextSegment->startOffset() ); + } + else + { + nextSegment = (*(it-1)); + middlePostionPercentage = ( nextSegment->middleOffset() - nextSegment->startOffset() ) / nextSegment->length(); + nextSegment->setEndOffset( segment->endOffset() ); + nextSegment->setMiddleOffset( middlePostionPercentage * nextSegment->length() + nextSegment->startOffset() ); + } + + delete segment; + m_segments.erase( it ); + return nextSegment; + } + return 0; +} + +bool KisAutogradientResource::removeSegmentPossible() const +{ + if( m_segments.count() < 2 ) + return false; + return true; +} + +void KisAutogradientResource::updatePreview() +{ + setImage( generatePreview( PREVIEW_WIDTH, PREVIEW_HEIGHT ) ); +} diff --git a/krita/core/kis_autogradient_resource.h b/krita/core/kis_autogradient_resource.h new file mode 100644 index 00000000..972ec8f6 --- /dev/null +++ b/krita/core/kis_autogradient_resource.h @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2004 Cyrille Berger <cberger@cberger.net> + * 2004 Sven Langkamp <longamp@reallygood.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. + */ + +#ifndef _KIS_AUTOGRADIENT_RESOURCE_H_ +#define _KIS_AUTOGRADIENT_RESOURCE_H_ + +#include "kis_gradient.h" + +class KisAutogradientResource : public KisGradient +{ + +public: + KisAutogradientResource() : KisGradient("") {} + +public: + + void createSegment( int interpolation, int colorInterpolation, double startOffset, double endOffset, double middleOffset, QColor left, QColor right ); + + const QValueVector<double> getHandlePositions() const; + const QValueVector<double> getMiddleHandlePositions() const; + + /** + * Moves the StartOffset of the specified segment to the specified value + * and corrects the endoffset of the previous segment. + * If the segment is the first Segment the startoffset will be set to 0.0 . + * The offset will maximally be moved till the middle of the current or the previous + * segment + */ + void moveSegmentStartOffset( KisGradientSegment* segment, double t); + + /** + * Moves the endoffset of the specified segment to the specified value + * and corrects the startoffset of the following segment. + * If the segment is the last segment the endoffset will be set to 1.0 . + * The offset will maximally be moved till the middle of the current or the following + * segment + */ + void moveSegmentEndOffset( KisGradientSegment* segment, double t); + + /** + * Moves the Middle of the specified segment to the specified value + * The offset will maximally be moved till the endoffset or startoffset of the segment + */ + void moveSegmentMiddleOffset( KisGradientSegment* segment, double t); + + + void splitSegment( KisGradientSegment* segment ); + void duplicateSegment( KisGradientSegment* segment ); + void mirrorSegment( KisGradientSegment* segment ); + + /** + * Removes the specific segment from the gradient. + * @return The segment which will be at the place of the old segment. + * 0 if the segment is not in the gradient or it is not possible to remove the segment. + */ + KisGradientSegment* removeSegment( KisGradientSegment* segment ); + + /** + * Checks if it's possible to remove an segment(at least two segments in the gradient) + * @return true if it's possible to remove an segment + */ + bool removeSegmentPossible() const; + + /** + * Recreates the preview of the gradient + */ + void updatePreview(); +public: + virtual bool load() { return false; }; +}; + +#endif // _KIS_AUTOGRADIENT_RESOURCE_H_ diff --git a/krita/core/kis_background.cc b/krita/core/kis_background.cc new file mode 100644 index 00000000..8fff32ec --- /dev/null +++ b/krita/core/kis_background.cc @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2002 Patrick Julien <freak@codepimps.org> + * + * 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. + */ +#include "kis_global.h" +#include "kis_background.h" +#include "kis_integer_maths.h" + +KisBackground::KisBackground() + : KShared() +{ + m_patternTile = QImage(PATTERN_WIDTH, PATTERN_HEIGHT, 32); + m_patternTile.setAlphaBuffer(false); + + for (int y = 0; y < PATTERN_HEIGHT; y++) + { + for (int x = 0; x < PATTERN_WIDTH; x++) + { + Q_UINT8 v = 128 + 63 * ((x / 16 + y / 16) % 2); + m_patternTile.setPixel(x, y, qRgb(v, v, v)); + } + } +} + +KisBackground::~KisBackground() +{ +} + +const QImage& KisBackground::patternTile() const +{ + return m_patternTile; +} + +void KisBackground::paintBackground(QImage image, int imageLeftX, int imageTopY) +{ + int patternLeftX; + + if (imageLeftX >= 0) { + patternLeftX = imageLeftX % PATTERN_WIDTH; + } else { + patternLeftX = (PATTERN_WIDTH - (-imageLeftX % PATTERN_WIDTH)) % PATTERN_WIDTH; + } + + int patternTopY; + + if (imageTopY >= 0) { + patternTopY = imageTopY % PATTERN_HEIGHT; + } else { + patternTopY = (PATTERN_HEIGHT - (-imageTopY % PATTERN_HEIGHT)) % PATTERN_HEIGHT; + } + + int imageWidth = image.width(); + int imageHeight = image.height(); + + int patternY = patternTopY; + + for (int y = 0; y < imageHeight; y++) + { + QRgb *imagePixelPtr = reinterpret_cast<QRgb *>(image.scanLine(y)); + const QRgb *patternScanLine = reinterpret_cast<const QRgb *>(m_patternTile.scanLine(patternY)); + int patternX = patternLeftX; + + for (int x = 0; x < imageWidth; x++) + { + QRgb imagePixel = *imagePixelPtr; + Q_UINT8 imagePixelAlpha = qAlpha(imagePixel); + + if (imagePixelAlpha != 255) { + + QRgb patternPixel = patternScanLine[patternX]; + Q_UINT8 imageRed = UINT8_BLEND(qRed(imagePixel), qRed(patternPixel), imagePixelAlpha); + Q_UINT8 imageGreen = UINT8_BLEND(qGreen(imagePixel), qGreen(patternPixel), imagePixelAlpha); + Q_UINT8 imageBlue = UINT8_BLEND(qBlue(imagePixel), qBlue(patternPixel), imagePixelAlpha); + + *imagePixelPtr = qRgba(imageRed, imageGreen, imageBlue, 255); + } + + ++imagePixelPtr; + ++patternX; + + if (patternX == PATTERN_WIDTH) { + patternX = 0; + } + } + + ++patternY; + + if (patternY == PATTERN_HEIGHT) { + patternY = 0; + } + } +} + +void KisBackground::paintBackground(QImage img, const QRect& scaledImageRect, const QSize& scaledImageSize, const QSize& imageSize) +{ + if (scaledImageRect.isEmpty() || scaledImageSize.isEmpty() || imageSize.isEmpty()) { + return; + } + + Q_ASSERT(img.size() == scaledImageRect.size()); + + if (img.size() != scaledImageRect.size()) { + return; + } + + Q_INT32 imageWidth = imageSize.width(); + Q_INT32 imageHeight = imageSize.height(); + + for (Q_INT32 y = 0; y < scaledImageRect.height(); ++y) { + + Q_INT32 scaledY = scaledImageRect.y() + y; + Q_INT32 srcY = (scaledY * imageHeight) / scaledImageSize.height(); + Q_INT32 patternY = srcY % PATTERN_HEIGHT; + + QRgb *imagePixelPtr = reinterpret_cast<QRgb *>(img.scanLine(y)); + const QRgb *patternScanLine = reinterpret_cast<const QRgb *>(m_patternTile.scanLine(patternY)); + + for (Q_INT32 x = 0; x < scaledImageRect.width(); ++x) { + + QRgb imagePixel = *imagePixelPtr; + Q_UINT8 imagePixelAlpha = qAlpha(imagePixel); + + if (imagePixelAlpha != 255) { + + Q_INT32 scaledX = scaledImageRect.x() + x; + Q_INT32 srcX = (scaledX * imageWidth) / scaledImageSize.width(); + Q_INT32 patternX = srcX % PATTERN_WIDTH; + + QRgb patternPixel = patternScanLine[patternX]; + Q_UINT8 imageRed = UINT8_BLEND(qRed(imagePixel), qRed(patternPixel), imagePixelAlpha); + Q_UINT8 imageGreen = UINT8_BLEND(qGreen(imagePixel), qGreen(patternPixel), imagePixelAlpha); + Q_UINT8 imageBlue = UINT8_BLEND(qBlue(imagePixel), qBlue(patternPixel), imagePixelAlpha); + + *imagePixelPtr = qRgba(imageRed, imageGreen, imageBlue, 255); + } + + ++imagePixelPtr; + } + } +} + + diff --git a/krita/core/kis_background.h b/krita/core/kis_background.h new file mode 100644 index 00000000..59016ffb --- /dev/null +++ b/krita/core/kis_background.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2002 Patrick Julien <freak@codepimps.org> + * + * 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_BACKGROUND_H_ +#define KIS_BACKGROUND_H_ + +#include <qimage.h> + +#include <ksharedptr.h> + +class KisBackground : public KShared { + +public: + KisBackground(); + virtual ~KisBackground(); + + // Paint the background pattern into the image, 'behind' the image + // contents. The coordinates are for the image's top-left corner + // in image space. + void paintBackground(QImage image, int leftX, int topY); + + void paintBackground(QImage image, const QRect& scaledImageRect, const QSize& scaledImageSize, const QSize& imageSize); + + // Returns the pattern tile. + const QImage& patternTile() const; + +protected: + static const int PATTERN_WIDTH = 32; + static const int PATTERN_HEIGHT = 32; + + QImage m_patternTile; +}; + +#endif // KIS_BACKGROUND_H_ + diff --git a/krita/core/kis_basic_math_toolbox.cpp b/krita/core/kis_basic_math_toolbox.cpp new file mode 100644 index 00000000..d20544af --- /dev/null +++ b/krita/core/kis_basic_math_toolbox.cpp @@ -0,0 +1,137 @@ +/* + * This file is part of the KDE project + * + * Copyright (c) 2005 Cyrille Berger <cberger@cberger.net> + * + * 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_basic_math_toolbox.h" + +KisBasicMathToolbox::KisBasicMathToolbox() + : KisMathToolbox(KisID("Basic")) +{ +} + + +KisBasicMathToolbox::~KisBasicMathToolbox() +{ +} + + +void KisBasicMathToolbox::wavetrans(KisMathToolbox::KisWavelet* wav, KisMathToolbox::KisWavelet* buff, uint halfsize) +{ + uint l = (2*halfsize)*wav->depth*sizeof(float); + for(uint i = 0; i < halfsize; i++) + { + float * itLL = buff->coeffs + i*buff->size*buff->depth; + float * itHL = buff->coeffs + (i*buff->size + halfsize)*buff->depth; + float * itLH = buff->coeffs + (halfsize+i)*buff->size*buff->depth; + float * itHH = buff->coeffs + ( (halfsize+i)*buff->size + halfsize)*buff->depth; + float * itS11 = wav->coeffs + 2*i*wav->size*wav->depth; + float * itS12 = wav->coeffs + (2*i*wav->size+1)*wav->depth; + float * itS21 = wav->coeffs + (2*i+1)*wav->size*wav->depth; + float * itS22 = wav->coeffs + ((2*i+1)*wav->size+1)*wav->depth; + for(uint j = 0; j < halfsize; j++) + { + for( uint k = 0; k < wav->depth; k++) + { + *(itLL++) = (*itS11 + *itS12 + *itS21 + *itS22) * M_SQRT1_2; + *(itHL++) = (*itS11 - *itS12 + *itS21 - *itS22) * M_SQRT1_2; + *(itLH++) = (*itS11 + *itS12 - *itS21 - *itS22) * M_SQRT1_2; + *(itHH++) = (*(itS11++) - *(itS12++) - *(itS21++) + *(itS22++)) * M_SQRT1_2; + } + itS11 += wav->depth; itS12 += wav->depth; + itS21 += wav->depth; itS22 += wav->depth; + } + emit nextStep(); + } + for(uint i = 0; i < halfsize; i++) + { + uint p = i*wav->size*wav->depth; + memcpy(wav->coeffs + p, buff->coeffs + p, l); + p = (i + halfsize )*wav->size*wav->depth; + memcpy(wav->coeffs + p, buff->coeffs + p, l); + } + if(halfsize != 1) + { + wavetrans(wav, buff, halfsize/2); + } +} + +void KisBasicMathToolbox::waveuntrans(KisMathToolbox::KisWavelet* wav, KisMathToolbox::KisWavelet* buff, uint halfsize) +{ + uint l = (2*halfsize)*wav->depth*sizeof(float); + for(uint i = 0; i < halfsize; i++) + { + float * itLL = wav->coeffs + i*buff->size*buff->depth; + float * itHL = wav->coeffs + (i*buff->size + halfsize)*buff->depth; + float * itLH = wav->coeffs + (halfsize+i)*buff->size*buff->depth; + float * itHH = wav->coeffs + ( (halfsize+i)*buff->size + halfsize)*buff->depth; + float * itS11 = buff->coeffs + 2*i*wav->size*wav->depth; + float * itS12 = buff->coeffs + (2*i*wav->size+1)*wav->depth; + float * itS21 = buff->coeffs + (2*i+1)*wav->size*wav->depth; + float * itS22 = buff->coeffs + ((2*i+1)*wav->size+1)*wav->depth; + for(uint j = 0; j < halfsize; j++) + { + for( uint k = 0; k < wav->depth; k++) + { + *(itS11++) = (*itLL + *itHL + *itLH + *itHH)*0.25*M_SQRT2; + *(itS12++) = (*itLL - *itHL + *itLH - *itHH)*0.25*M_SQRT2; + *(itS21++) = (*itLL + *itHL - *itLH - *itHH)*0.25*M_SQRT2; + *(itS22++) = (*(itLL++) - *(itHL++) - *(itLH++) + *(itHH++))*0.25*M_SQRT2; + } + itS11 += wav->depth; itS12 += wav->depth; + itS21 += wav->depth; itS22 += wav->depth; + } + emit nextStep(); + } + for(uint i = 0; i < halfsize; i++) + { + uint p = i*wav->size*wav->depth; + memcpy(wav->coeffs + p, buff->coeffs + p, l); + p = (i + halfsize )*wav->size*wav->depth; + memcpy(wav->coeffs + p, buff->coeffs + p, l); + } + + if(halfsize != wav->size/2) + { + waveuntrans(wav, buff, halfsize*2); + } +} + +KisMathToolbox::KisWavelet* KisBasicMathToolbox::fastWaveletTransformation(KisPaintDeviceSP src, const QRect& rect, KisWavelet* buff) +{ + if(buff == 0) + { + buff = initWavelet( src, rect ); + } + KisWavelet* wav = initWavelet( src, rect ); + transformToFR(src, wav, rect); + wavetrans(wav, buff, wav->size / 2); + + return wav; +} + +void KisBasicMathToolbox::fastWaveletUntransformation(KisPaintDeviceSP dst, const QRect& rect, KisWavelet* wav, KisWavelet* buff) +{ + if(buff == 0) + { + buff = initWavelet( dst, rect ); + } + + waveuntrans(wav, buff, 1 ); + transformFromFR(dst, wav, rect); +} diff --git a/krita/core/kis_basic_math_toolbox.h b/krita/core/kis_basic_math_toolbox.h new file mode 100644 index 00000000..ce80e55c --- /dev/null +++ b/krita/core/kis_basic_math_toolbox.h @@ -0,0 +1,44 @@ +/* + * This file is part of the KDE project + * + * Copyright (c) 2005 Cyrille Berger <cberger@cberger.net> + * + * 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_BASIC_MATH_TOOLBOX_H +#define KIS_BASIC_MATH_TOOLBOX_H + +#include "kis_math_toolbox.h" + +/** + * This class implement KisMathToolbox for most colorspaces, only colorspaces with "angular" + * channels need to reimplement the functions + */ +class KisBasicMathToolbox : public KisMathToolbox +{ + public: + KisBasicMathToolbox(); + ~KisBasicMathToolbox(); + public: + virtual KisWavelet* fastWaveletTransformation(KisPaintDeviceSP src, const QRect&, KisWavelet* buff = 0); + virtual void fastWaveletUntransformation(KisPaintDeviceSP dst, const QRect&, KisWavelet* wav, KisWavelet* buff = 0); + private: + void wavetrans(KisWavelet* wav, KisWavelet* buff, uint halfsize); + void waveuntrans(KisWavelet* wav, KisWavelet* buff, uint halfsize); + +}; + +#endif diff --git a/krita/core/kis_boundary.cc b/krita/core/kis_boundary.cc new file mode 100644 index 00000000..24524aeb --- /dev/null +++ b/krita/core/kis_boundary.cc @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2005 Bart Coppens <kde@bartcoppens.be> + * + * 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 <qpixmap.h> +#include <qpainter.h> + +#include "kis_colorspace.h" +#include "kis_iterators_pixel.h" +#include "kis_paint_device.h" +#include "kis_boundary.h" + +KisBoundary::KisBoundary(KisPaintDevice* dev) { + m_device = dev; + m_fuzzyness = 255 / 2; +} + +bool KisBoundary::isDark(Q_UINT8 val) { + return val < m_fuzzyness; +} + +void KisBoundary::generateBoundary(int w, int h) { + if (!m_device) + return; + + KisColorSpace* cs = m_device->colorSpace(); + + // Horizontal + for (int currentY = - 1; currentY < h; currentY++) { + KisHLineIteratorPixel topIt = m_device->createHLineIterator(0, currentY, w, false); + KisHLineIteratorPixel botIt = m_device->createHLineIterator(0, currentY + 1, w, false); + bool darkTop; + bool darkBot; + + m_horSegments.append(QValueList<PointPair>()); + + while (!topIt.isDone()) { + darkTop = cs->getAlpha(topIt.rawData()); + darkBot = cs->getAlpha(botIt.rawData()); + if (darkTop != darkBot) { + // detected a change + m_horSegments.back().append(qMakePair(KisPoint(botIt.x(), botIt.y()), 1)); + } + ++topIt; + ++botIt; + } + } + + // Vertical + for (int currentX = - 1; currentX < w; currentX++) { + KisVLineIteratorPixel leftIt = m_device->createVLineIterator(currentX, 0, h, false); + KisVLineIteratorPixel rightIt = m_device->createVLineIterator(currentX + 1, 0, h, false); + bool darkLeft; + bool darkRight; + + m_vertSegments.append(QValueList<PointPair>()); + + while (!leftIt.isDone()) { + darkLeft = cs->getAlpha(leftIt.rawData()); + darkRight = cs->getAlpha(rightIt.rawData()); + if (darkLeft != darkRight) { + // detected a change + m_vertSegments.back().append(qMakePair(KisPoint(rightIt.x(), rightIt.y()), 1)); + } + ++leftIt; + ++rightIt; + } + } +} + diff --git a/krita/core/kis_boundary.h b/krita/core/kis_boundary.h new file mode 100644 index 00000000..069d0289 --- /dev/null +++ b/krita/core/kis_boundary.h @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2005 Bart Coppens <kde@bartcoppens.be> + * + * 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_BOUNDARY_H_ +#define _KIS_BOUNDARY_H_ + +#include <qvaluelist.h> +#include <qpair.h> +#include <koffice_export.h> + +#include "kis_point.h" + +class KisPaintDevice; + +/** + * Generates an 'outline' for a paint device. It should look a bit like the outline of a + * marching ants selection. You can use it to paint the outline of a KisBrush while painting. + * It's not really optimized, so it's not recommended to do big things with it and expect + * it to be fast. + * Usage: construct a KisBoundary, and then run a generateBoundary(w, h) on it. After that, + * you can use the KisBoundaryPainter::paint method to let it paint the outline, or get a pixmap. + **/ +class KRITACORE_EXPORT KisBoundary { +public: + KisBoundary(KisPaintDevice* dev); + void generateBoundary(int w, int h); + +private: + typedef QPair<KisPoint, int> PointPair; // int->length + bool isDark(Q_UINT8 val); + KisPaintDevice* m_device; + int m_fuzzyness; + + typedef QValueList<PointPair> PointPairList; + typedef QValueList< PointPairList > PointPairListList; + + PointPairListList m_horSegments; + PointPairListList m_vertSegments; + + friend class KisBoundaryPainter; +}; + +#endif // _KIS_BOUNDARY_H_ diff --git a/krita/core/kis_brush.cc b/krita/core/kis_brush.cc new file mode 100644 index 00000000..57ee2584 --- /dev/null +++ b/krita/core/kis_brush.cc @@ -0,0 +1,1333 @@ +/* + * Copyright (c) 1999 Matthias Elter <me@kde.org> + * Copyright (c) 2003 Patrick Julien <freak@codepimps.org> + * Copyright (c) 2004 Boudewijn Rempt <boud@valdyas.org> + * Copyright (c) 2004 Adrian Page <adrian@pagenet.plus.com> + * Copyright (c) 2005 Bart Coppens <kde@bartcoppens.be> + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#ifdef HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif + +#include <netinet/in.h> +#include <limits.h> +#include <stdlib.h> +#include <cfloat> + +#include <qfile.h> +#include <qimage.h> +#include <qpoint.h> +#include <qvaluevector.h> + +#include <kdebug.h> +#include <klocale.h> + +#include <kis_meta_registry.h> +#include "kis_paint_device.h" +#include "kis_global.h" +#include "kis_brush.h" +#include "kis_alpha_mask.h" +#include "kis_colorspace_factory_registry.h" +#include "kis_iterators_pixel.h" +#include "kis_image.h" + + +namespace { + struct GimpBrushV1Header { + Q_UINT32 header_size; /* header_size = sizeof (BrushHeader) + brush name */ + Q_UINT32 version; /* brush file version # */ + Q_UINT32 width; /* width of brush */ + Q_UINT32 height; /* height of brush */ + Q_UINT32 bytes; /* depth of brush in bytes */ + }; + + /// All fields are in MSB on disk! + struct GimpBrushHeader { + Q_UINT32 header_size; /* header_size = sizeof (BrushHeader) + brush name */ + Q_UINT32 version; /* brush file version # */ + Q_UINT32 width; /* width of brush */ + Q_UINT32 height; /* height of brush */ + Q_UINT32 bytes; /* depth of brush in bytes */ + + /* The following are only defined in version 2 */ + Q_UINT32 magic_number; /* GIMP brush magic number */ + Q_UINT32 spacing; /* brush spacing as % of width & height, 0 - 1000 */ + }; + + // Needed, or the GIMP won't open it! + Q_UINT32 const GimpV2BrushMagic = ('G' << 24) + ('I' << 16) + ('M' << 8) + ('P' << 0); +} + +#define DEFAULT_SPACING 0.25 +#define MAXIMUM_SCALE 2 + +KisBrush::KisBrush(const QString& filename) : super(filename) +{ + m_brushType = INVALID; + m_ownData = true; + m_useColorAsMask = false; + m_hasColor = false; + m_spacing = DEFAULT_SPACING; + m_boundary = 0; +} + +KisBrush::KisBrush(const QString& filename, + const QByteArray& data, + Q_UINT32 & dataPos) : super(filename) +{ + m_brushType = INVALID; + m_ownData = false; + m_useColorAsMask = false; + m_hasColor = false; + m_spacing = DEFAULT_SPACING; + m_boundary = 0; + + m_data.setRawData(data.data() + dataPos, data.size() - dataPos); + init(); + m_data.resetRawData(data.data() + dataPos, data.size() - dataPos); + dataPos += m_header_size + (width() * height() * m_bytes); +} + +KisBrush::KisBrush(KisPaintDevice* image, int x, int y, int w, int h) + : super(QString("")) +{ + m_brushType = INVALID; + m_ownData = true; + m_useColorAsMask = false; + m_hasColor = true; + m_spacing = DEFAULT_SPACING; + m_boundary = 0; + + initFromPaintDev(image, x, y, w, h); +} + +KisBrush::KisBrush(const QImage& image, const QString& name) + : super(QString("")) +{ + m_ownData = false; + m_useColorAsMask = false; + m_hasColor = true; + m_spacing = DEFAULT_SPACING; + m_boundary = 0; + + setImage(image); + setName(name); + setBrushType(IMAGE); +} + + +KisBrush::~KisBrush() +{ + m_scaledBrushes.clear(); + delete m_boundary; +} + +bool KisBrush::load() +{ + if (m_ownData) { + QFile file(filename()); + file.open(IO_ReadOnly); + m_data = file.readAll(); + file.close(); + } + return init(); +} + +bool KisBrush::init() +{ + GimpBrushHeader bh; + + if (sizeof(GimpBrushHeader) > m_data.size()) { + return false; + } + + memcpy(&bh, &m_data[0], sizeof(GimpBrushHeader)); + bh.header_size = ntohl(bh.header_size); + m_header_size = bh.header_size; + + bh.version = ntohl(bh.version); + m_version = bh.version; + + bh.width = ntohl(bh.width); + bh.height = ntohl(bh.height); + + bh.bytes = ntohl(bh.bytes); + m_bytes = bh.bytes; + + bh.magic_number = ntohl(bh.magic_number); + m_magic_number = bh.magic_number; + + if (bh.version == 1) { + // No spacing in version 1 files so use Gimp default + bh.spacing = static_cast<int>(DEFAULT_SPACING * 100); + } + else { + bh.spacing = ntohl(bh.spacing); + + if (bh.spacing > 1000) { + return false; + } + } + + setSpacing(bh.spacing / 100.0); + + if (bh.header_size > m_data.size() || bh.header_size == 0) { + return false; + } + + QString name; + + if (bh.version == 1) { + // Version 1 has no magic number or spacing, so the name + // is at a different offset. Character encoding is undefined. + const char *text = &m_data[sizeof(GimpBrushV1Header)]; + name = QString::fromAscii(text, bh.header_size - sizeof(GimpBrushV1Header)); + } else { + // ### Version = 3->cinepaint; may be float16 data! + // Version >=2: UTF-8 encoding is used + name = QString::fromUtf8(&m_data[sizeof(GimpBrushHeader)], + bh.header_size - sizeof(GimpBrushHeader)); + } + + setName(i18n(name.ascii())); // Ascii? And what with real UTF-8 chars? + + if (bh.width == 0 || bh.height == 0 || !m_img.create(bh.width, bh.height, 32)) { + return false; + } + + Q_INT32 k = bh.header_size; + + if (bh.bytes == 1) { + // Grayscale + + if (static_cast<Q_UINT32>(k + bh.width * bh.height) > m_data.size()) { + return false; + } + + m_brushType = MASK; + m_hasColor = false; + + for (Q_UINT32 y = 0; y < bh.height; y++) { + for (Q_UINT32 x = 0; x < bh.width; x++, k++) { + Q_INT32 val = 255 - static_cast<uchar>(m_data[k]); + m_img.setPixel(x, y, qRgb(val, val, val)); + } + } + } else if (bh.bytes == 4) { + // RGBA + + if (static_cast<Q_UINT32>(k + (bh.width * bh.height * 4)) > m_data.size()) { + return false; + } + + m_brushType = IMAGE; + m_img.setAlphaBuffer(true); + m_hasColor = true; + + for (Q_UINT32 y = 0; y < bh.height; y++) { + for (Q_UINT32 x = 0; x < bh.width; x++, k += 4) { + m_img.setPixel(x, y, qRgba(m_data[k], + m_data[k+1], + m_data[k+2], + m_data[k+3])); + } + } + } else { + return false; + } + + setWidth(m_img.width()); + setHeight(m_img.height()); + //createScaledBrushes(); + if (m_ownData) { + m_data.resize(0); // Save some memory, we're using enough of it as it is. + } + + + if (m_img.width() == 0 || m_img.height() == 0) + setValid(false); + else + setValid(true); + + return true; +} + +bool KisBrush::initFromPaintDev(KisPaintDevice* image, int x, int y, int w, int h) { + // Forcefully convert to RGBA8 + // XXX profile and exposure? + setImage(image->convertToQImage(0, x, y, w, h)); + setName(image->name()); + + m_brushType = IMAGE; + m_hasColor = true; + + return true; +} + +bool KisBrush::save() +{ + QFile file(filename()); + file.open(IO_WriteOnly | IO_Truncate); + bool ok = saveToDevice(&file); + file.close(); + return ok; +} + +bool KisBrush::saveToDevice(QIODevice* dev) const +{ + GimpBrushHeader bh; + QCString utf8Name = name().utf8(); // Names in v2 brushes are in UTF-8 + char const* name = utf8Name.data(); + int nameLength = qstrlen(name); + int wrote; + + bh.header_size = htonl(sizeof(GimpBrushHeader) + nameLength); + bh.version = htonl(2); // Only RGBA8 data needed atm, no cinepaint stuff + bh.width = htonl(width()); + bh.height = htonl(height()); + // Hardcoded, 4 bytes RGBA or 1 byte GREY + if (!hasColor()) + bh.bytes = htonl(1); + else + bh.bytes = htonl(4); + bh.magic_number = htonl(GimpV2BrushMagic); + bh.spacing = htonl(static_cast<Q_UINT32>(spacing() * 100.0)); + + // Write header: first bh, then the name + QByteArray bytes; + bytes.setRawData(reinterpret_cast<char*>(&bh), sizeof(GimpBrushHeader)); + wrote = dev->writeBlock(bytes); + bytes.resetRawData(reinterpret_cast<char*>(&bh), sizeof(GimpBrushHeader)); + + if (wrote == -1) + return false; + + wrote = dev->writeBlock(name, nameLength); // No +1 for the trailing NULL it seems... + if (wrote == -1) + return false; + + int k = 0; + + if (!hasColor()) { + bytes.resize(width() * height()); + for (Q_INT32 y = 0; y < height(); y++) { + for (Q_INT32 x = 0; x < width(); x++) { + QRgb c = m_img.pixel(x, y); + bytes[k++] = static_cast<char>(255 - qRed(c)); // red == blue == green + } + } + } else { + bytes.resize(width() * height() * 4); + for (Q_INT32 y = 0; y < height(); y++) { + for (Q_INT32 x = 0; x < width(); x++) { + // order for gimp brushes, v2 is: RGBA + QRgb pixel = m_img.pixel(x,y); + bytes[k++] = static_cast<char>(qRed(pixel)); + bytes[k++] = static_cast<char>(qGreen(pixel)); + bytes[k++] = static_cast<char>(qBlue(pixel)); + bytes[k++] = static_cast<char>(qAlpha(pixel)); + } + } + } + + wrote = dev->writeBlock(bytes); + if (wrote == -1) + return false; + + return true; +} + +QImage KisBrush::img() +{ + QImage image = m_img; + + if (hasColor() && useColorAsMask()) { + image.detach(); + + for (int x = 0; x < image.width(); x++) { + for (int y = 0; y < image.height(); y++) { + QRgb c = image.pixel(x, y); + int a = (qGray(c) * qAlpha(c)) / 255; + image.setPixel(x, y, qRgba(a, 0, a, a)); + } + } + } + + return image; +} + +KisAlphaMaskSP KisBrush::mask(const KisPaintInformation& info, double subPixelX, double subPixelY) const +{ + if (m_scaledBrushes.isEmpty()) { + createScaledBrushes(); + } + + double scale = scaleForPressure(info.pressure); + + const ScaledBrush *aboveBrush = 0; + const ScaledBrush *belowBrush = 0; + + findScaledBrushes(scale, &aboveBrush, &belowBrush); + Q_ASSERT(aboveBrush != 0); + + KisAlphaMaskSP outputMask = 0; + + if (belowBrush != 0) { + // We're in between two masks. Interpolate between them. + + KisAlphaMaskSP scaledAboveMask = scaleMask(aboveBrush, scale, subPixelX, subPixelY); + KisAlphaMaskSP scaledBelowMask = scaleMask(belowBrush, scale, subPixelX, subPixelY); + + double t = (scale - belowBrush->scale()) / (aboveBrush->scale() - belowBrush->scale()); + + outputMask = KisAlphaMask::interpolate(scaledBelowMask, scaledAboveMask, t); + } else { + if (fabs(scale - aboveBrush->scale()) < DBL_EPSILON) { + // Exact match. + outputMask = scaleMask(aboveBrush, scale, subPixelX, subPixelY); + } else { + // We are smaller than the smallest mask, which is always 1x1. + double s = scale / aboveBrush->scale(); + outputMask = scaleSinglePixelMask(s, aboveBrush->mask()->alphaAt(0, 0), subPixelX, subPixelY); + } + } + + return outputMask; +} + +KisPaintDeviceSP KisBrush::image(KisColorSpace * /*colorSpace*/, const KisPaintInformation& info, double subPixelX, double subPixelY) const +{ + if (m_scaledBrushes.isEmpty()) { + createScaledBrushes(); + } + + double scale = scaleForPressure(info.pressure); + + const ScaledBrush *aboveBrush = 0; + const ScaledBrush *belowBrush = 0; + + findScaledBrushes(scale, &aboveBrush, &belowBrush); + Q_ASSERT(aboveBrush != 0); + + QImage outputImage; + + if (belowBrush != 0) { + // We're in between two brushes. Interpolate between them. + + QImage scaledAboveImage = scaleImage(aboveBrush, scale, subPixelX, subPixelY); + QImage scaledBelowImage = scaleImage(belowBrush, scale, subPixelX, subPixelY); + + double t = (scale - belowBrush->scale()) / (aboveBrush->scale() - belowBrush->scale()); + + outputImage = interpolate(scaledBelowImage, scaledAboveImage, t); + } else { + if (fabs(scale - aboveBrush->scale()) < DBL_EPSILON) { + // Exact match. + outputImage = scaleImage(aboveBrush, scale, subPixelX, subPixelY); + } else { + // We are smaller than the smallest brush, which is always 1x1. + double s = scale / aboveBrush->scale(); + outputImage = scaleSinglePixelImage(s, aboveBrush->image().pixel(0, 0), subPixelX, subPixelY); + } + } + + int outputWidth = outputImage.width(); + int outputHeight = outputImage.height(); + + KisPaintDevice *layer = new KisPaintDevice(KisMetaRegistry::instance()->csRegistry()->getRGB8(), "brush"); + + Q_CHECK_PTR(layer); + + for (int y = 0; y < outputHeight; y++) { + KisHLineIterator iter = layer->createHLineIterator( 0, y, outputWidth, true); + for (int x = 0; x < outputWidth; x++) { + Q_UINT8 * p = iter.rawData(); + + QRgb pixel = outputImage.pixel(x, y); + int red = qRed(pixel); + int green = qGreen(pixel); + int blue = qBlue(pixel); + int alpha = qAlpha(pixel); + + // Scaled images are in pre-multiplied alpha form so + // divide by alpha. + // channel order is BGRA + if (alpha != 0) { + p[2] = (red * 255) / alpha; + p[1] = (green * 255) / alpha; + p[0] = (blue * 255) / alpha; + p[3] = alpha; + } + + ++iter; + } + } + + return layer; +} + +void KisBrush::setHotSpot(KisPoint pt) +{ + double x = pt.x(); + double y = pt.y(); + + if (x < 0) + x = 0; + else if (x >= width()) + x = width() - 1; + + if (y < 0) + y = 0; + else if (y >= height()) + y = height() - 1; + + m_hotSpot = KisPoint(x, y); +} + +KisPoint KisBrush::hotSpot(const KisPaintInformation& info) const +{ + double scale = scaleForPressure(info.pressure); + double w = width() * scale; + double h = height() * scale; + + // The smallest brush we can produce is a single pixel. + if (w < 1) { + w = 1; + } + + if (h < 1) { + h = 1; + } + + // XXX: This should take m_hotSpot into account, though it + // isn't specified by gimp brushes so it would default to the centre + // anyway. + KisPoint p(w / 2, h / 2); + return p; +} + +enumBrushType KisBrush::brushType() const +{ + if (m_brushType == IMAGE && useColorAsMask()) { + return MASK; + } + else { + return m_brushType; + } +} + +bool KisBrush::hasColor() const +{ + return m_hasColor; +} + +void KisBrush::createScaledBrushes() const +{ + if (!m_scaledBrushes.isEmpty()) + m_scaledBrushes.clear(); + + // Construct a series of brushes where each one's dimensions are + // half the size of the previous one. + int width = m_img.width() * MAXIMUM_SCALE; + int height = m_img.height() * MAXIMUM_SCALE; + + QImage scaledImage; + + while (true) { + + if (width >= m_img.width() && height >= m_img.height()) { + scaledImage = scaleImage(m_img, width, height); + } + else { + // Scale down the previous image once we're below 1:1. + scaledImage = scaleImage(scaledImage, width, height); + } + + KisAlphaMaskSP scaledMask = new KisAlphaMask(scaledImage, hasColor()); + Q_CHECK_PTR(scaledMask); + + double xScale = static_cast<double>(width) / m_img.width(); + double yScale = static_cast<double>(height) / m_img.height(); + double scale = xScale; + + m_scaledBrushes.append(ScaledBrush(scaledMask, hasColor() ? scaledImage : QImage(), scale, xScale, yScale)); + + if (width == 1 && height == 1) { + break; + } + + // Round up so that we never have to scale an image by less than 1/2. + width = (width + 1) / 2; + height = (height + 1) / 2; + + } + +} + +double KisBrush::xSpacing(double pressure) const +{ + return width() * scaleForPressure(pressure) * m_spacing; +} + +double KisBrush::ySpacing(double pressure) const +{ + return height() * scaleForPressure(pressure) * m_spacing; +} + +double KisBrush::scaleForPressure(double pressure) +{ + double scale = pressure / PRESSURE_DEFAULT; + + if (scale < 0) { + scale = 0; + } + + if (scale > MAXIMUM_SCALE) { + scale = MAXIMUM_SCALE; + } + + return scale; +} + +Q_INT32 KisBrush::maskWidth(const KisPaintInformation& info) const +{ + // Add one for sub-pixel shift + return static_cast<Q_INT32>(ceil(width() * scaleForPressure(info.pressure)) + 1); +} + +Q_INT32 KisBrush::maskHeight(const KisPaintInformation& info) const +{ + // Add one for sub-pixel shift + return static_cast<Q_INT32>(ceil(height() * scaleForPressure(info.pressure)) + 1); +} + +KisAlphaMaskSP KisBrush::scaleMask(const ScaledBrush *srcBrush, double scale, double subPixelX, double subPixelY) const +{ + // Add one pixel for sub-pixel shifting + int dstWidth = static_cast<int>(ceil(scale * width())) + 1; + int dstHeight = static_cast<int>(ceil(scale * height())) + 1; + + KisAlphaMaskSP dstMask = new KisAlphaMask(dstWidth, dstHeight); + Q_CHECK_PTR(dstMask); + + KisAlphaMaskSP srcMask = srcBrush->mask(); + + // Compute scales to map the scaled brush onto the required scale. + double xScale = srcBrush->xScale() / scale; + double yScale = srcBrush->yScale() / scale; + + int srcWidth = srcMask->width(); + int srcHeight = srcMask->height(); + + for (int dstY = 0; dstY < dstHeight; dstY++) { + for (int dstX = 0; dstX < dstWidth; dstX++) { + + double srcX = (dstX - subPixelX + 0.5) * xScale; + double srcY = (dstY - subPixelY + 0.5) * yScale; + + srcX -= 0.5; + srcY -= 0.5; + + int leftX = static_cast<int>(srcX); + + if (srcX < 0) { + leftX--; + } + + double xInterp = srcX - leftX; + + int topY = static_cast<int>(srcY); + + if (srcY < 0) { + topY--; + } + + double yInterp = srcY - topY; + + Q_UINT8 topLeft = (leftX >= 0 && leftX < srcWidth && topY >= 0 && topY < srcHeight) ? srcMask->alphaAt(leftX, topY) : OPACITY_TRANSPARENT; + Q_UINT8 bottomLeft = (leftX >= 0 && leftX < srcWidth && topY + 1 >= 0 && topY + 1 < srcHeight) ? srcMask->alphaAt(leftX, topY + 1) : OPACITY_TRANSPARENT; + Q_UINT8 topRight = (leftX + 1 >= 0 && leftX + 1 < srcWidth && topY >= 0 && topY < srcHeight) ? srcMask->alphaAt(leftX + 1, topY) : OPACITY_TRANSPARENT; + Q_UINT8 bottomRight = (leftX + 1 >= 0 && leftX + 1 < srcWidth && topY + 1 >= 0 && topY + 1 < srcHeight) ? srcMask->alphaAt(leftX + 1, topY + 1) : OPACITY_TRANSPARENT; + + double a = 1 - xInterp; + double b = 1 - yInterp; + + // Bi-linear interpolation + int d = static_cast<int>(a * b * topLeft + + a * (1 - b) * bottomLeft + + (1 - a) * b * topRight + + (1 - a) * (1 - b) * bottomRight + 0.5); + + if (d < OPACITY_TRANSPARENT) { + d = OPACITY_TRANSPARENT; + } + else + if (d > OPACITY_OPAQUE) { + d = OPACITY_OPAQUE; + } + + dstMask->setAlphaAt(dstX, dstY, static_cast<Q_UINT8>(d)); + } + } + + return dstMask; +} + +QImage KisBrush::scaleImage(const ScaledBrush *srcBrush, double scale, double subPixelX, double subPixelY) const +{ + // Add one pixel for sub-pixel shifting + int dstWidth = static_cast<int>(ceil(scale * width())) + 1; + int dstHeight = static_cast<int>(ceil(scale * height())) + 1; + + QImage dstImage(dstWidth, dstHeight, 32); + dstImage.setAlphaBuffer(true); + + const QImage srcImage = srcBrush->image(); + + // Compute scales to map the scaled brush onto the required scale. + double xScale = srcBrush->xScale() / scale; + double yScale = srcBrush->yScale() / scale; + + int srcWidth = srcImage.width(); + int srcHeight = srcImage.height(); + + for (int dstY = 0; dstY < dstHeight; dstY++) { + for (int dstX = 0; dstX < dstWidth; dstX++) { + + double srcX = (dstX - subPixelX + 0.5) * xScale; + double srcY = (dstY - subPixelY + 0.5) * yScale; + + srcX -= 0.5; + srcY -= 0.5; + + int leftX = static_cast<int>(srcX); + + if (srcX < 0) { + leftX--; + } + + double xInterp = srcX - leftX; + + int topY = static_cast<int>(srcY); + + if (srcY < 0) { + topY--; + } + + double yInterp = srcY - topY; + + QRgb topLeft = (leftX >= 0 && leftX < srcWidth && topY >= 0 && topY < srcHeight) ? srcImage.pixel(leftX, topY) : qRgba(0, 0, 0, 0); + QRgb bottomLeft = (leftX >= 0 && leftX < srcWidth && topY + 1 >= 0 && topY + 1 < srcHeight) ? srcImage.pixel(leftX, topY + 1) : qRgba(0, 0, 0, 0); + QRgb topRight = (leftX + 1 >= 0 && leftX + 1 < srcWidth && topY >= 0 && topY < srcHeight) ? srcImage.pixel(leftX + 1, topY) : qRgba(0, 0, 0, 0); + QRgb bottomRight = (leftX + 1 >= 0 && leftX + 1 < srcWidth && topY + 1 >= 0 && topY + 1 < srcHeight) ? srcImage.pixel(leftX + 1, topY + 1) : qRgba(0, 0, 0, 0); + + double a = 1 - xInterp; + double b = 1 - yInterp; + + // Bi-linear interpolation. Image is pre-multiplied by alpha. + int red = static_cast<int>(a * b * qRed(topLeft) + + a * (1 - b) * qRed(bottomLeft) + + (1 - a) * b * qRed(topRight) + + (1 - a) * (1 - b) * qRed(bottomRight) + 0.5); + int green = static_cast<int>(a * b * qGreen(topLeft) + + a * (1 - b) * qGreen(bottomLeft) + + (1 - a) * b * qGreen(topRight) + + (1 - a) * (1 - b) * qGreen(bottomRight) + 0.5); + int blue = static_cast<int>(a * b * qBlue(topLeft) + + a * (1 - b) * qBlue(bottomLeft) + + (1 - a) * b * qBlue(topRight) + + (1 - a) * (1 - b) * qBlue(bottomRight) + 0.5); + int alpha = static_cast<int>(a * b * qAlpha(topLeft) + + a * (1 - b) * qAlpha(bottomLeft) + + (1 - a) * b * qAlpha(topRight) + + (1 - a) * (1 - b) * qAlpha(bottomRight) + 0.5); + + if (red < 0) { + red = 0; + } + else + if (red > 255) { + red = 255; + } + + if (green < 0) { + green = 0; + } + else + if (green > 255) { + green = 255; + } + + if (blue < 0) { + blue = 0; + } + else + if (blue > 255) { + blue = 255; + } + + if (alpha < 0) { + alpha = 0; + } + else + if (alpha > 255) { + alpha = 255; + } + + dstImage.setPixel(dstX, dstY, qRgba(red, green, blue, alpha)); + } + } + + return dstImage; +} + +QImage KisBrush::scaleImage(const QImage& srcImage, int width, int height) +{ + QImage scaledImage; + //QString filename; + + int srcWidth = srcImage.width(); + int srcHeight = srcImage.height(); + + double xScale = static_cast<double>(srcWidth) / width; + double yScale = static_cast<double>(srcHeight) / height; + + if (xScale > 2 + DBL_EPSILON || yScale > 2 + DBL_EPSILON || xScale < 1 - DBL_EPSILON || yScale < 1 - DBL_EPSILON) { + // smoothScale gives better results when scaling an image up + // or scaling it to less than half size. + scaledImage = srcImage.smoothScale(width, height); + + //filename = QString("smoothScale_%1x%2.png").arg(width).arg(height); + } + else { + scaledImage.create(width, height, 32); + scaledImage.setAlphaBuffer(srcImage.hasAlphaBuffer()); + + for (int dstY = 0; dstY < height; dstY++) { + for (int dstX = 0; dstX < width; dstX++) { + + double srcX = (dstX + 0.5) * xScale; + double srcY = (dstY + 0.5) * yScale; + + srcX -= 0.5; + srcY -= 0.5; + + int leftX = static_cast<int>(srcX); + + if (srcX < 0) { + leftX--; + } + + double xInterp = srcX - leftX; + + int topY = static_cast<int>(srcY); + + if (srcY < 0) { + topY--; + } + + double yInterp = srcY - topY; + + QRgb topLeft = (leftX >= 0 && leftX < srcWidth && topY >= 0 && topY < srcHeight) ? srcImage.pixel(leftX, topY) : qRgba(0, 0, 0, 0); + QRgb bottomLeft = (leftX >= 0 && leftX < srcWidth && topY + 1 >= 0 && topY + 1 < srcHeight) ? srcImage.pixel(leftX, topY + 1) : qRgba(0, 0, 0, 0); + QRgb topRight = (leftX + 1 >= 0 && leftX + 1 < srcWidth && topY >= 0 && topY < srcHeight) ? srcImage.pixel(leftX + 1, topY) : qRgba(0, 0, 0, 0); + QRgb bottomRight = (leftX + 1 >= 0 && leftX + 1 < srcWidth && topY + 1 >= 0 && topY + 1 < srcHeight) ? srcImage.pixel(leftX + 1, topY + 1) : qRgba(0, 0, 0, 0); + + double a = 1 - xInterp; + double b = 1 - yInterp; + + int red; + int green; + int blue; + int alpha; + + if (srcImage.hasAlphaBuffer()) { + red = static_cast<int>(a * b * qRed(topLeft) * qAlpha(topLeft) + + a * (1 - b) * qRed(bottomLeft) * qAlpha(bottomLeft) + + (1 - a) * b * qRed(topRight) * qAlpha(topRight) + + (1 - a) * (1 - b) * qRed(bottomRight) * qAlpha(bottomRight) + 0.5); + green = static_cast<int>(a * b * qGreen(topLeft) * qAlpha(topLeft) + + a * (1 - b) * qGreen(bottomLeft) * qAlpha(bottomLeft) + + (1 - a) * b * qGreen(topRight) * qAlpha(topRight) + + (1 - a) * (1 - b) * qGreen(bottomRight) * qAlpha(bottomRight) + 0.5); + blue = static_cast<int>(a * b * qBlue(topLeft) * qAlpha(topLeft) + + a * (1 - b) * qBlue(bottomLeft) * qAlpha(bottomLeft) + + (1 - a) * b * qBlue(topRight) * qAlpha(topRight) + + (1 - a) * (1 - b) * qBlue(bottomRight) * qAlpha(bottomRight) + 0.5); + alpha = static_cast<int>(a * b * qAlpha(topLeft) + + a * (1 - b) * qAlpha(bottomLeft) + + (1 - a) * b * qAlpha(topRight) + + (1 - a) * (1 - b) * qAlpha(bottomRight) + 0.5); + + if (alpha != 0) { + red /= alpha; + green /= alpha; + blue /= alpha; + } + } + else { + red = static_cast<int>(a * b * qRed(topLeft) + + a * (1 - b) * qRed(bottomLeft) + + (1 - a) * b * qRed(topRight) + + (1 - a) * (1 - b) * qRed(bottomRight) + 0.5); + green = static_cast<int>(a * b * qGreen(topLeft) + + a * (1 - b) * qGreen(bottomLeft) + + (1 - a) * b * qGreen(topRight) + + (1 - a) * (1 - b) * qGreen(bottomRight) + 0.5); + blue = static_cast<int>(a * b * qBlue(topLeft) + + a * (1 - b) * qBlue(bottomLeft) + + (1 - a) * b * qBlue(topRight) + + (1 - a) * (1 - b) * qBlue(bottomRight) + 0.5); + alpha = 255; + } + + if (red < 0) { + red = 0; + } + else + if (red > 255) { + red = 255; + } + + if (green < 0) { + green = 0; + } + else + if (green > 255) { + green = 255; + } + + if (blue < 0) { + blue = 0; + } + else + if (blue > 255) { + blue = 255; + } + + if (alpha < 0) { + alpha = 0; + } + else + if (alpha > 255) { + alpha = 255; + } + + scaledImage.setPixel(dstX, dstY, qRgba(red, green, blue, alpha)); + } + } + + //filename = QString("bilinear_%1x%2.png").arg(width).arg(height); + } + + //scaledImage.save(filename, "PNG"); + + return scaledImage; +} + +void KisBrush::findScaledBrushes(double scale, const ScaledBrush **aboveBrush, const ScaledBrush **belowBrush) const +{ + uint current = 0; + + while (true) { + *aboveBrush = &(m_scaledBrushes[current]); + + if (fabs((*aboveBrush)->scale() - scale) < DBL_EPSILON) { + // Scale matches exactly + break; + } + + if (current == m_scaledBrushes.count() - 1) { + // This is the last one + break; + } + + if (scale > m_scaledBrushes[current + 1].scale() + DBL_EPSILON) { + // We fit in between the two. + *belowBrush = &(m_scaledBrushes[current + 1]); + break; + } + + current++; + } +} + +KisAlphaMaskSP KisBrush::scaleSinglePixelMask(double scale, Q_UINT8 maskValue, double subPixelX, double subPixelY) +{ + int srcWidth = 1; + int srcHeight = 1; + int dstWidth = 2; + int dstHeight = 2; + KisAlphaMaskSP outputMask = new KisAlphaMask(dstWidth, dstHeight); + Q_CHECK_PTR(outputMask); + + double a = subPixelX; + double b = subPixelY; + + for (int y = 0; y < dstHeight; y++) { + for (int x = 0; x < dstWidth; x++) { + + Q_UINT8 topLeft = (x > 0 && y > 0) ? maskValue : OPACITY_TRANSPARENT; + Q_UINT8 bottomLeft = (x > 0 && y < srcHeight) ? maskValue : OPACITY_TRANSPARENT; + Q_UINT8 topRight = (x < srcWidth && y > 0) ? maskValue : OPACITY_TRANSPARENT; + Q_UINT8 bottomRight = (x < srcWidth && y < srcHeight) ? maskValue : OPACITY_TRANSPARENT; + + // Bi-linear interpolation + int d = static_cast<int>(a * b * topLeft + + a * (1 - b) * bottomLeft + + (1 - a) * b * topRight + + (1 - a) * (1 - b) * bottomRight + 0.5); + + // Multiply by the square of the scale because a 0.5x0.5 pixel + // has 0.25 the value of the 1x1. + d = static_cast<int>(d * scale * scale + 0.5); + + if (d < OPACITY_TRANSPARENT) { + d = OPACITY_TRANSPARENT; + } + else + if (d > OPACITY_OPAQUE) { + d = OPACITY_OPAQUE; + } + + outputMask->setAlphaAt(x, y, static_cast<Q_UINT8>(d)); + } + } + + return outputMask; +} + +QImage KisBrush::scaleSinglePixelImage(double scale, QRgb pixel, double subPixelX, double subPixelY) +{ + int srcWidth = 1; + int srcHeight = 1; + int dstWidth = 2; + int dstHeight = 2; + + QImage outputImage(dstWidth, dstHeight, 32); + outputImage.setAlphaBuffer(true); + + double a = subPixelX; + double b = subPixelY; + + for (int y = 0; y < dstHeight; y++) { + for (int x = 0; x < dstWidth; x++) { + + QRgb topLeft = (x > 0 && y > 0) ? pixel : qRgba(0, 0, 0, 0); + QRgb bottomLeft = (x > 0 && y < srcHeight) ? pixel : qRgba(0, 0, 0, 0); + QRgb topRight = (x < srcWidth && y > 0) ? pixel : qRgba(0, 0, 0, 0); + QRgb bottomRight = (x < srcWidth && y < srcHeight) ? pixel : qRgba(0, 0, 0, 0); + + // Bi-linear interpolation. Images are in pre-multiplied form. + int red = static_cast<int>(a * b * qRed(topLeft) + + a * (1 - b) * qRed(bottomLeft) + + (1 - a) * b * qRed(topRight) + + (1 - a) * (1 - b) * qRed(bottomRight) + 0.5); + int green = static_cast<int>(a * b * qGreen(topLeft) + + a * (1 - b) * qGreen(bottomLeft) + + (1 - a) * b * qGreen(topRight) + + (1 - a) * (1 - b) * qGreen(bottomRight) + 0.5); + int blue = static_cast<int>(a * b * qBlue(topLeft) + + a * (1 - b) * qBlue(bottomLeft) + + (1 - a) * b * qBlue(topRight) + + (1 - a) * (1 - b) * qBlue(bottomRight) + 0.5); + int alpha = static_cast<int>(a * b * qAlpha(topLeft) + + a * (1 - b) * qAlpha(bottomLeft) + + (1 - a) * b * qAlpha(topRight) + + (1 - a) * (1 - b) * qAlpha(bottomRight) + 0.5); + + // Multiply by the square of the scale because a 0.5x0.5 pixel + // has 0.25 the value of the 1x1. + alpha = static_cast<int>(alpha * scale * scale + 0.5); + + // Apply to the colour channels too since we are + // storing pre-multiplied by alpha. + red = static_cast<int>(red * scale * scale + 0.5); + green = static_cast<int>(green * scale * scale + 0.5); + blue = static_cast<int>(blue * scale * scale + 0.5); + + if (red < 0) { + red = 0; + } + else + if (red > 255) { + red = 255; + } + + if (green < 0) { + green = 0; + } + else + if (green > 255) { + green = 255; + } + + if (blue < 0) { + blue = 0; + } + else + if (blue > 255) { + blue = 255; + } + + if (alpha < 0) { + alpha = 0; + } + else + if (alpha > 255) { + alpha = 255; + } + + outputImage.setPixel(x, y, qRgba(red, green, blue, alpha)); + } + } + + return outputImage; +} + +QImage KisBrush::interpolate(const QImage& image1, const QImage& image2, double t) +{ + Q_ASSERT((image1.width() == image2.width()) && (image1.height() == image2.height())); + Q_ASSERT(t > -DBL_EPSILON && t < 1 + DBL_EPSILON); + + int width = image1.width(); + int height = image1.height(); + + QImage outputImage(width, height, 32); + outputImage.setAlphaBuffer(true); + + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + QRgb image1pixel = image1.pixel(x, y); + QRgb image2pixel = image2.pixel(x, y); + + // Images are in pre-multiplied alpha format. + int red = static_cast<int>((1 - t) * qRed(image1pixel) + t * qRed(image2pixel) + 0.5); + int green = static_cast<int>((1 - t) * qGreen(image1pixel) + t * qGreen(image2pixel) + 0.5); + int blue = static_cast<int>((1 - t) * qBlue(image1pixel) + t * qBlue(image2pixel) + 0.5); + int alpha = static_cast<int>((1 - t) * qAlpha(image1pixel) + t * qAlpha(image2pixel) + 0.5); + + if (red < 0) { + red = 0; + } + else + if (red > 255) { + red = 255; + } + + if (green < 0) { + green = 0; + } + else + if (green > 255) { + green = 255; + } + + if (blue < 0) { + blue = 0; + } + else + if (blue > 255) { + blue = 255; + } + + if (alpha < 0) { + alpha = 0; + } + else + if (alpha > 255) { + alpha = 255; + } + + outputImage.setPixel(x, y, qRgba(red, green, blue, alpha)); + } + } + + return outputImage; +} + +KisBrush::ScaledBrush::ScaledBrush() +{ + m_mask = 0; + m_image = QImage(); + m_scale = 1; + m_xScale = 1; + m_yScale = 1; +} + +KisBrush::ScaledBrush::ScaledBrush(KisAlphaMaskSP scaledMask, const QImage& scaledImage, double scale, double xScale, double yScale) +{ + m_mask = scaledMask; + m_image = scaledImage; + m_scale = scale; + m_xScale = xScale; + m_yScale = yScale; + + if (!m_image.isNull()) { + // Convert image to pre-multiplied by alpha. + + m_image.detach(); + + for (int y = 0; y < m_image.height(); y++) { + for (int x = 0; x < m_image.width(); x++) { + + QRgb pixel = m_image.pixel(x, y); + + int red = qRed(pixel); + int green = qGreen(pixel); + int blue = qBlue(pixel); + int alpha = qAlpha(pixel); + + red = (red * alpha) / 255; + green = (green * alpha) / 255; + blue = (blue * alpha) / 255; + + m_image.setPixel(x, y, qRgba(red, green, blue, alpha)); + } + } + } +} + +void KisBrush::setImage(const QImage& img) +{ + m_img = img; + m_img.detach(); + + setWidth(img.width()); + setHeight(img.height()); + + m_scaledBrushes.clear(); + + setValid(true); +} + +Q_INT32 KisBrush::width() const +{ + return m_width; +} + +void KisBrush::setWidth(Q_INT32 w) +{ + m_width = w; +} + +Q_INT32 KisBrush::height() const +{ + return m_height; +} + +void KisBrush::setHeight(Q_INT32 h) +{ + m_height = h; +} + +/*QImage KisBrush::outline(double pressure) { + KisLayerSP layer = image(KisMetaRegistry::instance()->csRegistry()->getColorSpace(KisID("RGBA",""),""), + KisPaintInformation(pressure)); + KisBoundary bounds(layer.data()); + int w = maskWidth(pressure); + int h = maskHeight(pressure); + + bounds.generateBoundary(w, h); + QPixmap pix(bounds.pixmap(w, h)); + QImage result; + result = pix; + return result; +}*/ + +void KisBrush::generateBoundary() { + KisPaintDeviceSP dev; + int w = maskWidth(KisPaintInformation()); + int h = maskHeight(KisPaintInformation()); + + if (brushType() == IMAGE || brushType() == PIPE_IMAGE) { + dev = image(KisMetaRegistry::instance()->csRegistry() ->getColorSpace(KisID("RGBA",""),""), KisPaintInformation()); + } else { + KisAlphaMaskSP amask = mask(KisPaintInformation()); + KisColorSpace* cs = KisMetaRegistry::instance()->csRegistry()->getColorSpace(KisID("RGBA",""),""); + dev = new KisPaintDevice(cs, "tmp for generateBoundary"); + for (int y = 0; y < h; y++) { + KisHLineIteratorPixel it = dev->createHLineIterator(0, y, w, true); + int x = 0; + + while(!it.isDone()) { + cs->setAlpha(it.rawData(), amask->alphaAt(x++, y), 1); + ++it; + } + } + } + + m_boundary = new KisBoundary(dev); + m_boundary->generateBoundary(w, h); +} + +KisBoundary KisBrush::boundary() { + if (!m_boundary) + generateBoundary(); + return *m_boundary; +} + +void KisBrush::makeMaskImage() { + if (!hasColor()) + return; + + QImage img; + img.create(width(), height(), 32); + + if (m_img.width() == img.width() && m_img.height() == img.height()) { + for (int x = 0; x < width(); x++) { + for (int y = 0; y < height(); y++) { + QRgb c = m_img.pixel(x, y); + int a = (qGray(c) * qAlpha(c)) / 255; // qGray(black) = 0 + img.setPixel(x, y, qRgba(a, a, a, 255)); + } + } + + m_img = img; + } + + m_brushType = MASK; + m_hasColor = false; + m_useColorAsMask = false; + delete m_boundary; + m_boundary = 0; + m_scaledBrushes.clear(); +} + +KisBrush* KisBrush::clone() const { + KisBrush* c = new KisBrush(""); + c->m_spacing = m_spacing; + c->m_useColorAsMask = m_useColorAsMask; + c->m_hasColor = m_useColorAsMask; + c->m_img = m_img; + c->m_width = m_width; + c->m_height = m_height; + c->m_ownData = false; + c->m_hotSpot = m_hotSpot; + c->m_brushType = m_brushType; + c->setValid(true); + + return c; +} + +#include "kis_brush.moc" + diff --git a/krita/core/kis_brush.h b/krita/core/kis_brush.h new file mode 100644 index 00000000..7de2c8b7 --- /dev/null +++ b/krita/core/kis_brush.h @@ -0,0 +1,191 @@ +/* + * Copyright (c) 1999 Matthias Elter <me@kde.org> + * Copyright (c) 2002 Patrick Julien <freak@codepimps.org> + * Copyright (c) 2004 Boudewijn Rempt <boud@valdyas.org> + * + * 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_BRUSH_ +#define KIS_BRUSH_ + +#include <qcstring.h> +#include <qstring.h> +#include <qsize.h> +#include <qimage.h> +#include <qvaluevector.h> + +#include <kio/job.h> + +#include "kis_resource.h" +#include "kis_types.h" +#include "kis_point.h" +#include "kis_alpha_mask.h" +#include "koffice_export.h" +#include "kis_boundary.h" +#include "kis_paintop.h" + +class QPoint; +class QPixmap; +class KisBoundary; +class KisColorSpace; +class QIODevice; + +enum enumBrushType { + INVALID, + MASK, + IMAGE, + PIPE_MASK, + PIPE_IMAGE, + AIRBRUSH +}; + +class KRITACORE_EXPORT KisBrush : public KisResource { + typedef KisResource super; + Q_OBJECT + +public: + /// Construct brush to load filename later as brush + KisBrush(const QString& filename); + /// Load brush from the specified data, at position dataPos, and set the filename + KisBrush(const QString& filename, + const QByteArray & data, + Q_UINT32 & dataPos); + /// Load brush from the specified paint device, in the specified region + KisBrush(KisPaintDevice* image, int x, int y, int w, int h); + /// Load brush as a copy from the specified QImage (handy when you need to copy a brush!) + KisBrush(const QImage& image, const QString& name = QString("")); + + virtual ~KisBrush(); + + virtual bool load(); + /// synchronous, doesn't emit any signal (none defined!) + virtual bool save(); + virtual QImage img(); + virtual bool saveToDevice(QIODevice* dev) const; + + /** + @return a mask computed from the grey-level values of the + pixels in the brush. + */ + virtual KisAlphaMaskSP mask(const KisPaintInformation& info, + double subPixelX = 0, double subPixelY = 0) const; + // XXX: return non-tiled simple buffer + virtual KisPaintDeviceSP image(KisColorSpace * colorSpace, const KisPaintInformation& info, + double subPixelX = 0, double subPixelY = 0) const; + + void setHotSpot(KisPoint); + KisPoint hotSpot(const KisPaintInformation& info = KisPaintInformation()) const; + + void setSpacing(double s) { m_spacing = s; } + double spacing() const { return m_spacing; } + double xSpacing(double pressure = PRESSURE_DEFAULT) const; + double ySpacing(double pressure = PRESSURE_DEFAULT) const; + + // Dimensions in pixels of the mask/image at a given pressure. + Q_INT32 maskWidth(const KisPaintInformation& info) const; + Q_INT32 maskHeight(const KisPaintInformation& info) const; + + virtual void setUseColorAsMask(bool useColorAsMask) { m_useColorAsMask = useColorAsMask; } + virtual bool useColorAsMask() const { return m_useColorAsMask; } + virtual bool hasColor() const; + + virtual void makeMaskImage(); + Q_INT32 width() const; + Q_INT32 height() const; + + virtual enumBrushType brushType() const; + + //QImage outline(double pressure = PRESSURE_DEFAULT); + virtual KisBoundary boundary(); + + /** + * Returns true if this brush can return something useful for the info. This is used + * by Pipe Brushes that can't paint sometimes + **/ + virtual bool canPaintFor(const KisPaintInformation& /*info*/) { return true; } + + virtual KisBrush* clone() const; + +protected: + void setWidth(Q_INT32 w); + void setHeight(Q_INT32 h); + void setImage(const QImage& img); + void setBrushType(enumBrushType type) { m_brushType = type; }; + static double scaleForPressure(double pressure); + +private: + class ScaledBrush { + public: + ScaledBrush(); + ScaledBrush(KisAlphaMaskSP scaledMask, const QImage& scaledImage, double scale, double xScale, double yScale); + + double scale() const { return m_scale; } + double xScale() const { return m_xScale; } + double yScale() const { return m_yScale; } + KisAlphaMaskSP mask() const { return m_mask; } + QImage image() const { return m_image; } + + private: + KisAlphaMaskSP m_mask; + QImage m_image; + double m_scale; + double m_xScale; + double m_yScale; + }; + + + bool init(); + bool initFromPaintDev(KisPaintDevice* image, int x, int y, int w, int h); + void createScaledBrushes() const; + + KisAlphaMaskSP scaleMask(const ScaledBrush *srcBrush, double scale, double subPixelX, double subPixelY) const; + QImage scaleImage(const ScaledBrush *srcBrush, double scale, double subPixelX, double subPixelY) const; + + static QImage scaleImage(const QImage& srcImage, int width, int height); + static QImage interpolate(const QImage& image1, const QImage& image2, double t); + + static KisAlphaMaskSP scaleSinglePixelMask(double scale, Q_UINT8 maskValue, double subPixelX, double subPixelY); + static QImage scaleSinglePixelImage(double scale, QRgb pixel, double subPixelX, double subPixelY); + + // Find the scaled brush(es) nearest to the given scale. + void findScaledBrushes(double scale, const ScaledBrush **aboveBrush, const ScaledBrush **belowBrush) const; + + // Initialize our boundary + void generateBoundary(); + + QByteArray m_data; + bool m_ownData; + KisPoint m_hotSpot; + double m_spacing; + bool m_useColorAsMask; + bool m_hasColor; + QImage m_img; + mutable QValueVector<ScaledBrush> m_scaledBrushes; + + Q_INT32 m_width; + Q_INT32 m_height; + + Q_UINT32 m_header_size; /* header_size = sizeof (BrushHeader) + brush name */ + Q_UINT32 m_version; /* brush file version # */ + Q_UINT32 m_bytes; /* depth of brush in bytes */ + Q_UINT32 m_magic_number; /* GIMP brush magic number */ + + enumBrushType m_brushType; + + KisBoundary* m_boundary; + +}; +#endif // KIS_BRUSH_ + diff --git a/krita/core/kis_change_profile_visitor.h b/krita/core/kis_change_profile_visitor.h new file mode 100644 index 00000000..dea0eb4b --- /dev/null +++ b/krita/core/kis_change_profile_visitor.h @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2006 Boudewijn Rempt <boud@valdyas.org + * + * 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_CHANGE_PROFILE_VISITOR_H_ +#define KIS_CHANGE_PROFILE_VISITOR_H_ + +#include "kis_global.h" +#include "kis_types.h" +#include "kis_layer_visitor.h" +#include "kis_paint_layer.h" +#include "kis_paint_device.h" +#include "kis_adjustment_layer.h" +#include "kis_group_layer.h" + +/** + * The Change Profile visitor walks over all layers and if the current + * layer has the specified colorspace AND the specified old profile, sets + * the colorspace to the same colorspace with the NEW profile, without doing + * conversions. This is essential if you have loaded an image that didn't + * have an embedded profile to which you want to attach the right profile. + */ +class KisChangeProfileVisitor :public KisLayerVisitor { +public: + KisChangeProfileVisitor(KisColorSpace *oldColorSpace, KisColorSpace *dstColorSpace); + virtual ~KisChangeProfileVisitor(); + +public: + virtual bool visit(KisPaintLayer *layer); + virtual bool visit(KisGroupLayer *layer); + virtual bool visit(KisPartLayer *layer); + virtual bool visit(KisAdjustmentLayer* layer); + +private: + KisColorSpace *m_oldColorSpace; + KisColorSpace *m_dstColorSpace; +}; + +KisChangeProfileVisitor::KisChangeProfileVisitor(KisColorSpace * oldColorSpace, + KisColorSpace *dstColorSpace) : + KisLayerVisitor(), + m_oldColorSpace(oldColorSpace), + m_dstColorSpace(dstColorSpace) +{ +} + +KisChangeProfileVisitor::~KisChangeProfileVisitor() +{ +} + +bool KisChangeProfileVisitor::visit(KisGroupLayer * layer) +{ + // Clear the projection, we will have to re-render everything. + layer->resetProjection(); + + KisLayerSP child = layer->firstChild(); + while (child) { + child->accept(*this); + child = child->nextSibling(); + } + layer->setDirty(); + return true; +} + +bool KisChangeProfileVisitor::visit(KisPaintLayer *layer) +{ + if (!layer) return false; + if (!layer->paintDevice()) return false; + if (!layer->paintDevice()->colorSpace()) return false; + + KisColorSpace * cs = layer->paintDevice()->colorSpace(); + + if (cs == m_oldColorSpace) { + + layer->paintDevice()->setProfile(m_dstColorSpace->getProfile()); + + layer->setDirty(); + } + return true; +} + +bool KisChangeProfileVisitor::visit(KisPartLayer *) +{ + return true; +} + + +bool KisChangeProfileVisitor::visit(KisAdjustmentLayer * layer) +{ + layer->resetCache(); + layer->setDirty(); + return true; +} + +#endif // KIS_CHANGE_PROFILE_VISITOR_H_ + diff --git a/krita/core/kis_colorspace_convert_visitor.h b/krita/core/kis_colorspace_convert_visitor.h new file mode 100644 index 00000000..ce4ee787 --- /dev/null +++ b/krita/core/kis_colorspace_convert_visitor.h @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2005 Casper Boemann <cbr@boemann.dk> + * + * 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_COLORSPACE_CONVERT_VISITOR_H_ +#define KIS_COLORSPACE_CONVERT_VISITOR_H_ + +#include "kis_global.h" +#include "kis_types.h" +#include "kis_layer_visitor.h" +#include "kis_paint_layer.h" +#include "kis_paint_device.h" +#include "kis_adjustment_layer.h" +#include "kis_group_layer.h" + +class KisColorSpaceConvertVisitor :public KisLayerVisitor { +public: + KisColorSpaceConvertVisitor(KisColorSpace *dstColorSpace, Q_INT32 renderingIntent); + virtual ~KisColorSpaceConvertVisitor(); + +public: + virtual bool visit(KisPaintLayer *layer); + virtual bool visit(KisGroupLayer *layer); + virtual bool visit(KisPartLayer *layer); + virtual bool visit(KisAdjustmentLayer* layer); + +private: + KisColorSpace *m_dstColorSpace; + Q_INT32 m_renderingIntent; +}; + +KisColorSpaceConvertVisitor::KisColorSpaceConvertVisitor(KisColorSpace *dstColorSpace, Q_INT32 renderingIntent) : + KisLayerVisitor(), + m_dstColorSpace(dstColorSpace), + m_renderingIntent(renderingIntent) +{ +} + +KisColorSpaceConvertVisitor::~KisColorSpaceConvertVisitor() +{ +} + +bool KisColorSpaceConvertVisitor::visit(KisGroupLayer * layer) +{ + // Clear the projection, we will have to re-render everything. + // The image is already set to the new colorspace, so this'll work. + layer->resetProjection(); + + KisLayerSP child = layer->firstChild(); + while (child) { + child->accept(*this); + child = child->nextSibling(); + } + layer->setDirty(); + return true; +} + +bool KisColorSpaceConvertVisitor::visit(KisPaintLayer *layer) +{ + layer->paintDevice()->convertTo(m_dstColorSpace, m_renderingIntent); + + layer->setDirty(); + return true; +} + +bool KisColorSpaceConvertVisitor::visit(KisPartLayer *) +{ + return true; +} + + +bool KisColorSpaceConvertVisitor::visit(KisAdjustmentLayer * layer) +{ + if (layer->filter()->name() == "perchannel") { + // Per-channel filters need to be reset because of different number + // of channels. This makes undo very tricky, but so be it. + // XXX: Make this more generic for after 1.6, when we'll have many + // channel-specific filters. + KisFilter * f = KisFilterRegistry::instance()->get("perchannel"); + layer->setFilter(f->configuration()); + } + layer->resetCache(); + layer->setDirty(); + return true; +} + +#endif // KIS_COLORSPACE_CONVERT_VISITOR_H_ + diff --git a/krita/core/kis_command.cc b/krita/core/kis_command.cc new file mode 100644 index 00000000..bcc997ea --- /dev/null +++ b/krita/core/kis_command.cc @@ -0,0 +1,43 @@ +/* + * Copyright (c) 1999 Michael Koch <koch@kde.org> + * Copyright (c) 2002 Patrick Julien <freak@codepimps.org> + * + * 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 <qstring.h> +#include "kis_command.h" + +KisCommand::KisCommand(KisUndoAdapter *adapter) +{ + m_name = ""; + m_undoAdapter = adapter; +} + +KisCommand::KisCommand(const QString& name, KisUndoAdapter *adapter) +{ + m_name = name; + m_undoAdapter = adapter; +} + +KisCommand::~KisCommand() +{ +} + +QString KisCommand::name() const +{ + return m_name; +} + diff --git a/krita/core/kis_command.h b/krita/core/kis_command.h new file mode 100644 index 00000000..f391c087 --- /dev/null +++ b/krita/core/kis_command.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) 1999 Michael Koch <koch@kde.org> + * Copyright (c) 2002 Patrick Julien <freak@codepimps.org> + * + * 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_COMMAND_H_ +#define KIS_COMMAND_H_ + +#include <qstring.h> +#include <kcommand.h> + +class KisUndoAdapter; + +class KisCommand : public KCommand { + typedef KCommand super; + +public: + KisCommand(KisUndoAdapter *undoAdapter); + KisCommand(const QString& name, KisUndoAdapter *undoAdapter); + virtual ~KisCommand(); + + virtual void execute() = 0; + virtual void unexecute() = 0; + virtual QString name() const; + +protected: + KisUndoAdapter *adapter() const; + +private: + KisUndoAdapter *m_undoAdapter; + QString m_name; +}; + +inline +KisUndoAdapter *KisCommand::adapter() const +{ + return m_undoAdapter; +} + +#endif // KIS_COMMAND_H_ + diff --git a/krita/core/kis_convolution_painter.cc b/krita/core/kis_convolution_painter.cc new file mode 100644 index 00000000..cb51480c --- /dev/null +++ b/krita/core/kis_convolution_painter.cc @@ -0,0 +1,426 @@ +/* + * Copyright (c) 2005 Cyrille Berger <cberger@cberger.net> + * + * 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 <stdlib.h> +#include <string.h> +#include <cfloat> + +#include "qbrush.h" +#include "qcolor.h" +#include "qfontinfo.h" +#include "qfontmetrics.h" +#include "qpen.h" +#include "qregion.h" +#include "qwmatrix.h" +#include <qimage.h> +#include <qmap.h> +#include <qpainter.h> +#include <qpixmap.h> +#include <qpointarray.h> +#include <qrect.h> +#include <qstring.h> + +#include <kdebug.h> +#include <klocale.h> + +#include <qcolor.h> + +#include "kis_brush.h" +#include "kis_global.h" +#include "kis_image.h" +#include "kis_iterators_pixel.h" +#include "kis_layer.h" +#include "kis_paint_device.h" +#include "kis_painter.h" +#include "kis_pattern.h" +#include "kis_rect.h" +#include "kis_colorspace.h" +#include "kis_types.h" +#include "kis_vec.h" +#include "kis_selection.h" +#include "kis_convolution_painter.h" + + +KisKernelSP KisKernel::fromQImage(const QImage& img) +{ + KisKernelSP k = new KisKernel; + k->width = img.width(); + k->height = img.height(); + k->offset = 0; + uint count = k->width * k->height; + k->data = new Q_INT32[count]; + Q_INT32* itData = k->data; + Q_UINT8* itImg = img.bits(); + k->factor = 0; + for(uint i = 0; i < count; ++i , ++itData, itImg+=4) + { + *itData = 255 - ( *itImg + *(itImg+1) + *(itImg+2) ) / 3; + k->factor += *itData; + } + return k; +} + + +KisConvolutionPainter::KisConvolutionPainter() + : super() +{ +} + +KisConvolutionPainter::KisConvolutionPainter(KisPaintDeviceSP device) : super(device) +{ +} + +void KisConvolutionPainter::applyMatrix(KisKernelSP kernel, Q_INT32 x, Q_INT32 y, Q_INT32 w, Q_INT32 h, + KisConvolutionBorderOp borderOp, + KisChannelInfo::enumChannelFlags channelFlags ) +{ + // Make the area we cover as small as possible + if (m_device->hasSelection()) { + + QRect r = m_device->selection()->selectedRect().intersect(QRect(x, y, w, h)); + x = r.x(); + y = r.y(); + w = r.width(); + h = r.height(); + + } + + if ( w == 0 && h == 0 ) return; + + // Determine the kernel's extent from the center pixel + Q_INT32 kw, kh, khalfWidth, khalfHeight, xLastMinuskhw, yLastMinuskhh; + kw = kernel->width; + kh = kernel->height; + khalfWidth = (kw - 1) / 2; + khalfHeight = (kh - 1) / 2; + + xLastMinuskhw = x + (w - khalfWidth); + yLastMinuskhh = y + (h - khalfHeight); + + // Don't try to convolve on an area smaller than the kernel, or with a kernel that is not square or has no center pixel. + if (w < kw || h < kh || (kw&1) == 0 || (kh&1) == 0 || kernel->factor == 0 ) return; + + m_cancelRequested = false; + int lastProgressPercent = 0; + emit notifyProgress(0); + + KisColorSpace * cs = m_device->colorSpace(); + + // Determine whether we convolve border pixels, or not. + switch (borderOp) { + case BORDER_DEFAULT_FILL : + break; + case BORDER_REPEAT: + applyMatrixRepeat(kernel, x, y, w, h, channelFlags); + return; + case BORDER_WRAP: + case BORDER_AVOID: + default : + x += khalfWidth; + y += khalfHeight; + w -= kw - 1; + h -= kh - 1; + } + + // Iterate over all pixels in our rect, create a cache of pixels around the current pixel and convolve them in the colorstrategy. + + int cacheSize = kw * kh; + int cdepth = cs -> pixelSize(); + Q_UINT8** pixelPtrCache = new Q_UINT8*[cacheSize]; + for (int i = 0; i < cacheSize; i++) + pixelPtrCache[i] = new Q_UINT8[cdepth]; +// pixelPtrCache.fill(0); + + // row == the y position of the pixel we want to change in the paint device + int row = y; + + for (; row < y + h; ++row) { + + // col = the x position of the pixel we want to change + int col = x; + + KisHLineIteratorPixel hit = m_device->createHLineIterator(x, row, w, true); + bool needFull = true; + while (!hit.isDone()) { + + // Iterate over all contributing pixels that are covered by the kernel + // krow = the y position in the kernel matrix + if(needFull) + { + Q_INT32 i = 0; + for (Q_INT32 krow = 0; krow < kh; ++krow) { + + // col - khalfWidth = the left starting point of the kernel as centered on our pixel + // krow - khalfHeight = the offset for the top of the kernel as centered on our pixel + // kw = the width of the kernel + + // Fill the cache with pointers to the pixels under the kernel + KisHLineIteratorPixel kit = m_device->createHLineIterator(col - khalfWidth, (row - khalfHeight) + krow, kw, false); + while (!kit.isDone()) { + memcpy(pixelPtrCache[i], kit.oldRawData(), cdepth); + ++kit; + ++i; + } + } + needFull = false; + Q_ASSERT (i==kw*kh); + } else { + for (Q_INT32 krow = 0; krow < kh; ++krow) { // shift the cache to the left + Q_UINT8** d = pixelPtrCache + krow * kw; + //memmove( d, d + 1, (kw-1)*sizeof(Q_UINT8*)); + for (int i = 0; i < (kw-1); i++) { + memcpy(d[i], d[i+1], cdepth); + } + } + Q_INT32 i = kw - 1; + KisVLineIteratorPixel kit = m_device->createVLineIterator(col + khalfWidth, row - khalfHeight, kh, false); + while (!kit.isDone()) { + memcpy(pixelPtrCache[i], kit.oldRawData(), cdepth); + ++kit; + i += kw; + } + } + if (hit.isSelected()) { + cs->convolveColors(pixelPtrCache, kernel->data, channelFlags, hit.rawData(), kernel->factor, kernel->offset, kw * kh); +// pixelPtrCache.fill(0); + } + ++col; + ++hit; + } + + int progressPercent = 100 - ((((y + h) - row) * 100) / h); + + if (progressPercent > lastProgressPercent) { + emit notifyProgress(progressPercent); + lastProgressPercent = progressPercent; + + if (m_cancelRequested) { + for (int i = 0; i < cacheSize; i++) + delete[] pixelPtrCache[i]; + delete[] pixelPtrCache; + + return; + } + } + + } + + addDirtyRect(QRect(x, y, w, h)); + + emit notifyProgressDone(); + + for (int i = 0; i < cacheSize; i++) + delete[] pixelPtrCache[i]; + delete[] pixelPtrCache; +} + +void KisConvolutionPainter::applyMatrixRepeat(KisKernelSP kernel, Q_INT32 x, Q_INT32 y, Q_INT32 w, Q_INT32 h, + KisChannelInfo::enumChannelFlags channelFlags) +{ + int lastProgressPercent = 0; + // Determine the kernel's extent from the center pixel + Q_INT32 kw, kh, khalfWidth, khalfHeight, xLastMinuskhw, yLastMinuskhh; + kw = kernel->width; + kh = kernel->height; + khalfWidth = (kw - 1) / 2; + khalfHeight = (kh - 1) / 2; + + xLastMinuskhw = x + (w - khalfWidth); + yLastMinuskhh = y + (h - khalfHeight); + + KisColorSpace * cs = m_device->colorSpace(); + + // Iterate over all pixels in our rect, create a cache of pixels around the current pixel and convolve them in the colorstrategy. + + int cacheSize = kw * kh; + int cdepth = cs -> pixelSize(); + Q_UINT8** pixelPtrCache = new Q_UINT8*[cacheSize]; + for (int i = 0; i < cacheSize; i++) + pixelPtrCache[i] = new Q_UINT8[cdepth]; + + // row == the y position of the pixel we want to change in the paint device + int row = y; + + for (; row < y + h; ++row) { + + // col = the x position of the pixel we want to change + int col = x; + + KisHLineIteratorPixel hit = m_device->createHLineIterator(x, row, w, true); + bool needFull = true; + + Q_INT32 itStart = row - khalfHeight; + Q_INT32 itH = kh; + if(itStart < 0) + { + itH += itStart; + itStart = 0; + } else if(itStart + kh > yLastMinuskhh) + { + itH -= itStart + kh - yLastMinuskhh; + } + KisVLineIteratorPixel kit = m_device->createVLineIterator(col + khalfWidth, itStart, itH, false); + while (!hit.isDone()) { + + // Iterate over all contributing pixels that are covered by the kernel + // krow = the y position in the kernel matrix + if(needFull) // The cache has not been fill, so we need to fill it + { + Q_INT32 i = 0; + Q_INT32 krow = 0; + if( row < khalfHeight ) + { + // We are just outside the layer, all the row in the cache will be identical + // so we need to create them only once, and then to copy them + if( x < khalfWidth) + { // the left pixels are outside of the layer, in the corner + Q_INT32 kcol = 0; + KisHLineIteratorPixel kit = m_device->createHLineIterator(0, 0, kw, false); + for(; kcol < (khalfWidth - x) + 1; ++kcol) + { // First copy the address of the topleft pixel + memcpy(pixelPtrCache[kcol], kit.oldRawData(), cdepth); + } + for(; kcol < kw; ++kcol) + { // Then copy the address of the rest of the line + ++kit; + memcpy(pixelPtrCache[kcol], kit.oldRawData(), cdepth); + } + } else { + uint kcol = 0; + KisHLineIteratorPixel kit = m_device->createHLineIterator(col - khalfWidth, 0, kw, false); + while (!kit.isDone()) { + memcpy(pixelPtrCache[kcol], kit.oldRawData(), cdepth); + ++kit; + ++kcol; + } + } + krow = 1; // we have allready done the first krow + for(;krow < (khalfHeight - row); ++krow) + { + // Copy the first line in the current line + for (int i = 0; i < kw; i++) + memcpy(pixelPtrCache[krow * kw + i], pixelPtrCache[i], cdepth); + } + i = krow * kw; + } + Q_INT32 itH = kh; + if(row + khalfHeight > yLastMinuskhh) + { + itH += yLastMinuskhh - row - khalfHeight; + } + for (; krow < itH; ++krow) { + + // col - khalfWidth = the left starting point of the kernel as centered on our pixel + // krow - khalfHeight = the offset for the top of the kernel as centered on our pixel + // kw = the width of the kernel + + // Fill the cache with pointers to the pixels under the kernel + Q_INT32 itHStart = col - khalfWidth; + Q_INT32 itW = kw; + if(itHStart < 0) + { + itW += itHStart; + itHStart = 0; + } + KisHLineIteratorPixel kit = m_device->createHLineIterator(itHStart, (row - khalfHeight) + krow, itW, false); + if( col < khalfWidth ) + { + for(; i < krow * kw + ( kw - itW ); i+= 1) + { + memcpy(pixelPtrCache[i], kit.oldRawData(), cdepth); + } + } + while (!kit.isDone()) { + memcpy(pixelPtrCache[i], kit.oldRawData(), cdepth); + ++kit; + ++i; + } + } + Q_INT32 lastvalid = i - kw; + for(; krow < kh; ++krow) { + // Copy the last valid line in the current line + for (int i = 0; i < kw; i++) + memcpy(pixelPtrCache[krow * kw + i], pixelPtrCache[lastvalid + i], + cdepth); + } + needFull = false; + } else { +/* for (Q_INT32 krow = 0; krow < kh; ++krow) { // shift the cache to the left + Q_UINT8** d = pixelPtrCache + krow * kw; +// memmove( d, d + 1, (kw-1)*sizeof(Q_UINT8*)); + for (int i = 0; i < (kw-1); i++) { + memcpy(d[i], d[i+1], cdepth); + } + }*/ + Q_UINT8* firstincache = pixelPtrCache[0]; + memmove(pixelPtrCache, pixelPtrCache + 1, (cacheSize - 1) * sizeof(Q_UINT8*) ); + pixelPtrCache[cacheSize - 1] = firstincache; + if(col < xLastMinuskhw) + { + Q_INT32 i = kw - 1; +// KisVLineIteratorPixel kit = m_device->createVLineIterator(col + khalfWidth, itStart, itH, false); + kit.nextCol(); + if( row < khalfHeight ) + { + for(; i < (khalfHeight- row ) * kw; i+=kw) + { + memcpy(pixelPtrCache[i], kit.oldRawData(), cdepth); + } + } + while (!kit.isDone()) { + memcpy(pixelPtrCache[i], kit.oldRawData(), cdepth); + ++kit; + i += kw; + } + Q_INT32 lastvalid = i - kw; + for(;i < kw*kh; i+=kw) + { + memcpy(pixelPtrCache[i], pixelPtrCache[lastvalid], cdepth); + } + } + } + if (hit.isSelected()) { + cs->convolveColors(pixelPtrCache, kernel->data, channelFlags, hit.rawData(), kernel->factor, kernel->offset, kw * kh); + } + ++col; + ++hit; + } + + int progressPercent = 100 - ((((y + h) - row) * 100) / h); + + if (progressPercent > lastProgressPercent) { + emit notifyProgress(progressPercent); + lastProgressPercent = progressPercent; + + if (m_cancelRequested) { + for (int i = 0; i < cacheSize; i++) + delete[] pixelPtrCache[i]; + delete[] pixelPtrCache; + return; + } + } + + } + + addDirtyRect(QRect(x, y, w, h)); + + emit notifyProgressDone(); + for (int i = 0; i < cacheSize; i++) + delete[] pixelPtrCache[i]; + delete[] pixelPtrCache; +} diff --git a/krita/core/kis_convolution_painter.h b/krita/core/kis_convolution_painter.h new file mode 100644 index 00000000..d4aa8efb --- /dev/null +++ b/krita/core/kis_convolution_painter.h @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2005 Cyrille Berger <cberger@cberger.net> + * + * 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_CONVOLUTION_PAINTER_H_ +#define KIS_CONVOLUTION_PAINTER_H_ + +#include <qimage.h> + +#include "ksharedptr.h" +#include "kis_types.h" +#include "kis_painter.h" + +#include "koffice_export.h" + +enum KisConvolutionBorderOp { + BORDER_DEFAULT_FILL = 0, // Use the default pixel to make up for the missing pixels on the border or the pixel that lies beyond + // the rect we are convolving. + BORDER_WRAP = 1, // Use the pixel on the opposite side to make up for the missing pixels on the border. XXX: Not implemented yet + BORDER_REPEAT = 2, // Use the border for the missing pixels, too. + BORDER_AVOID = 3 // Skip convolving the border pixels at all. +}; + +class KisKernel; +typedef KSharedPtr<KisKernel> KisKernelSP; + +class KisKernel : public KShared +{ + +public: + + Q_UINT32 width; + Q_UINT32 height; + Q_INT32 offset; + Q_INT32 factor; + Q_INT32 * data; + + KisKernel() : width(0), height(0), offset(0), factor(0), data(0) {}; + + virtual ~KisKernel() { delete [] data; }; + + static KisKernelSP fromQImage(const QImage& img); + +}; + + +class KRITACORE_EXPORT KisConvolutionPainter : public KisPainter +{ + + typedef KisPainter super; + +public: + + KisConvolutionPainter(); + KisConvolutionPainter(KisPaintDeviceSP device); + + /** + * Convolve all channels in src using the specified kernel; there is only one kernel for all + * channels possible. By default the the border pixels are not convolved, that is, convolving + * starts with at (x + kernel.width/2, y + kernel.height/2) and stops at w - (kernel.width/2) + * and h - (kernel.height/2) + * + * The border op decides what to do with pixels too close to the edge of the rect as defined above. + * + * The channels flag determines which set out of color channels, alpha channels, substance or substrate + * channels we convolve. + * + * Note that we do not (currently) support different kernels for different channels _or_ channel types. + */ + void applyMatrix(KisKernelSP kernel, Q_INT32 x, Q_INT32 y, Q_INT32 w, Q_INT32 h, + KisConvolutionBorderOp borderOp = BORDER_AVOID, + KisChannelInfo::enumChannelFlags channelFlags = KisChannelInfo::FLAG_COLOR); +private: + /** + * This function is called by applyMatrix when borderOp == BORDER_REPEAT + */ + void applyMatrixRepeat(KisKernelSP kernel, Q_INT32 x, Q_INT32 y, Q_INT32 w, Q_INT32 h, + KisChannelInfo::enumChannelFlags channelFlags); + + +}; +#endif //KIS_CONVOLUTION_PAINTER_H_ diff --git a/krita/core/kis_crop_visitor.h b/krita/core/kis_crop_visitor.h new file mode 100644 index 00000000..6e5c826c --- /dev/null +++ b/krita/core/kis_crop_visitor.h @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2005 Boudewijn Rempt <boud@valdyas.org> + * + * 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_CROP_VISITOR_H_ +#define KIS_CROP_VISITOR_H_ + +#include "qrect.h" + +#include "klocale.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_selected_transaction.h> + +class KisProgressDisplayInterface; +class KisFilterStrategy; + +class KisCropVisitor : public KisLayerVisitor { + +public: + + KisCropVisitor( const QRect & rc, bool movelayers = true) + : KisLayerVisitor() + , m_rect(rc), m_movelayers(movelayers) + { + } + + virtual ~KisCropVisitor() + { + } + + /** + * Crops the specified layer and adds the undo information to the undo adapter of the + * layer's image. + */ + bool visit(KisPaintLayer *layer) + { + KisPaintDeviceSP dev = layer->paintDevice(); + KisSelectedTransaction * t = 0; + if (layer->undoAdapter() && layer->undoAdapter()->undo()) + t = new KisSelectedTransaction(i18n("Crop"), dev.data()); + + dev->crop(m_rect); + + if (layer->undoAdapter() && layer->undoAdapter()->undo()) { + layer->undoAdapter()->addCommand(t); + } + + if(m_movelayers) { + if(layer->undoAdapter() && layer->undoAdapter()->undo()) { + KNamedCommand * cmd = dev->moveCommand(layer->x() - m_rect.x(), layer->y() - m_rect.y()); + layer->undoAdapter()->addCommand(cmd); + } + } + layer->setDirty(dev->image()->bounds()); + return true; + }; + + bool visit(KisGroupLayer *layer) + { + layer->resetProjection(); + + KisLayerSP child = layer->firstChild(); + while (child) { + child->accept(*this); + child = child->nextSibling(); + } + layer->setDirty(); + return true; + }; + + bool visit(KisPartLayer */*layer*/) + { + return true; + }; + + virtual bool visit(KisAdjustmentLayer* layer) + { + layer->resetCache(); + return true; + } + + +private: + QRect m_rect; + bool m_movelayers; +}; + + +#endif diff --git a/krita/core/kis_datamanager.h b/krita/core/kis_datamanager.h new file mode 100644 index 00000000..2c450004 --- /dev/null +++ b/krita/core/kis_datamanager.h @@ -0,0 +1,217 @@ +/* + * Copyright (c) 2004 Boudewijn Rempt <boud@valdyas.org> + * + * 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_DATAMANAGER_H_ +#define KIS_DATAMANAGER_H_ + +#include <qglobal.h> +#include <qvaluevector.h> +#include <qrect.h> + +class KoStore; + + +// Change the following two lines to switch (at compiletime) to another datamanager +#include "tiles/kis_tileddatamanager.h" +#define ACTUAL_DATAMGR KisTiledDataManager + +/** + * KisDataManager defines the interface that modules responsible for + * storing and retrieving data must inmplement. Data modules, like + * the tile manager, are responsible for: + * + * * Storing undo/redo data + * * Offering ordererd and unordered iterators over rects of pixels + * * (eventually) efficiently loading and saving data in a format + * that may allow deferred loading. + * + * A datamanager knows nothing about the type of pixel data except + * how many Q_UINT8's a single pixel takes. + */ +class KisDataManager : public ACTUAL_DATAMGR { + +public: + KisDataManager(Q_UINT32 pixelSize, const Q_UINT8 *defPixel) : ACTUAL_DATAMGR(pixelSize, defPixel) {} + KisDataManager(const KisDataManager& dm) : ACTUAL_DATAMGR(dm) { } + +public: + /** + * Sets the default pixel. Note that this might change every occurrance, and it might not, but new data + * well be initialised with this pixel + */ + inline void setDefaultPixel(const Q_UINT8 *defPixel) { return ACTUAL_DATAMGR::setDefaultPixel(defPixel); } + + /** + * Gets the default pixel. + */ + inline const Q_UINT8 *defaultPixel() const { return ACTUAL_DATAMGR::defaultPixel(); } + + /** + * Reguests a memento from the data manager. There is only one memento active + * at any given moment for a given paint device and all and any + * write actions on the datamanger builds undo data into this memento + * necessary to rollback the transaction. + */ + inline KisMementoSP getMemento() { return ACTUAL_DATAMGR::getMemento(); } + + /** + * Restores the image data to the state at the time of the getMemento() call. + * + * Note that rollback should be performed with mementos in the reverse order of + * their creation, as mementos only store incremental changes + */ + inline void rollback(KisMementoSP memento) { ACTUAL_DATAMGR::rollback(memento); } + + /** + * Restores the image data to the state at the time of the rollback call of the memento. + * + * Note that rollforward must only be called when an rollback have previously been performed, and + * no intermittent actions have been performed (though it's ok to rollback other mementos and + * roll them forward again) + */ + inline void rollforward(KisMementoSP memento) { ACTUAL_DATAMGR::rollforward(memento); } + +public: + /** + * Reads and writes the tiles from/onto a KoStore (wich is simply a file within a zip file) + * + */ + inline bool write(KoStore *store) { return ACTUAL_DATAMGR::write(store); } + inline bool read(KoStore *store) { return ACTUAL_DATAMGR::read(store); } + +public: + + /** + * Returns the number of bytes a pixel takes + */ + inline Q_UINT32 pixelSize() { return ACTUAL_DATAMGR::pixelSize(); } + + /** + * Return the extent of the data in x,y,w,h. + */ + inline void extent(Q_INT32 &x, Q_INT32 &y, Q_INT32 &w, Q_INT32 &h) const + { return ACTUAL_DATAMGR::extent(x, y, w, h); } + + QRect extent() const { return ACTUAL_DATAMGR::extent(); } + + +public: + + /** + * Crop or extend the data to x, y, w, h. + */ + inline void setExtent(Q_INT32 x, Q_INT32 y, Q_INT32 w, Q_INT32 h) + { return ACTUAL_DATAMGR::setExtent(x, y, w, h); } + + inline void setExtent(const QRect & rect) { setExtent(rect.x(), rect.y(), rect.width(), rect.height()); } + +public: + + /** + * Clear the specified rect to the specified value. + */ + inline void clear(Q_INT32 x, Q_INT32 y, + Q_INT32 w, Q_INT32 h, + Q_UINT8 def) { ACTUAL_DATAMGR::clear(x, y, w, h, def); } + + /** + * Clear the specified rect to the specified pixel value. + */ + inline void clear(Q_INT32 x, Q_INT32 y, + Q_INT32 w, Q_INT32 h, + const Q_UINT8 * def) { ACTUAL_DATAMGR::clear(x, y, w, h, def); } + + /** + * Clear all back to default values. + */ + inline void clear() { ACTUAL_DATAMGR::clear(); } + + +public: + + /** + * Copy the specified rect from the specified data into this + * data. + */ + inline void paste(KisDataManagerSP data, Q_INT32 sx, Q_INT32 sy, Q_INT32 dx, Q_INT32 dy, + Q_INT32 w, Q_INT32 h) { ACTUAL_DATAMGR::paste(data, sx, sy, dx, dy, w, h); } + +public: + /** + * Get a read-only pointer to the specified pixel. + */ + inline KDE_DEPRECATED const Q_UINT8* pixel(Q_INT32 x, Q_INT32 y) + { return ACTUAL_DATAMGR::pixel(x, y); } + + /** + * Get a read-write pointer to the specified pixel. + */ + inline KDE_DEPRECATED Q_UINT8* writablePixel(Q_INT32 x, Q_INT32 y) + { return ACTUAL_DATAMGR::writablePixel(x, y); } + + /** + * Write the specified data to x, y. There is no checking on pixelSize! + */ + inline void setPixel(Q_INT32 x, Q_INT32 y, const Q_UINT8 * data) + { ACTUAL_DATAMGR::setPixel(x, y, data);} + + + /** + * Copy the bytes in the specified rect to a chunk of memory. + * The pixelSize in bytes is w * h * pixelSize. XXX: Better + * use QValueVector? + */ + inline void readBytes(Q_UINT8 * data, + Q_INT32 x, Q_INT32 y, + Q_INT32 w, Q_INT32 h) + { ACTUAL_DATAMGR::readBytes(data, x, y, w, h);} + + /** + * Copy the bytes to the specified rect. w * h * pixelSize bytes will be read, whether + * the caller prepared them or not. XXX: Better use QValueVector? + */ + inline void writeBytes(const Q_UINT8 * data, + Q_INT32 x, Q_INT32 y, + Q_INT32 w, Q_INT32 h) + {ACTUAL_DATAMGR::writeBytes( data, x, y, w, h); } + + // Get the number of contiguous columns starting at x, valid for all values + // of y between minY and maxY. + inline Q_INT32 numContiguousColumns(Q_INT32 x, Q_INT32 minY, Q_INT32 maxY) + { return ACTUAL_DATAMGR::numContiguousColumns(x, minY, maxY); } + + + // Get the number of contiguous rows starting at y, valid for all values + // of x between minX and maxX. + inline Q_INT32 numContiguousRows(Q_INT32 y, Q_INT32 minX, Q_INT32 maxX) + { return ACTUAL_DATAMGR::numContiguousRows(y, minX, maxX); } + + + // Get the row stride at pixel (x, y). This is the number of bytes to add to a + // pointer to pixel (x, y) to access (x, y + 1). + inline Q_INT32 rowStride(Q_INT32 x, Q_INT32 y) + { return ACTUAL_DATAMGR::rowStride(x, y); } + +protected: + friend class KisRectIterator; + friend class KisHLineIterator; + friend class KisVLineIterator; +}; + + +#endif // KIS_DATAMANAGER_H_ + diff --git a/krita/core/kis_exif_info.cc b/krita/core/kis_exif_info.cc new file mode 100644 index 00000000..f81a1566 --- /dev/null +++ b/krita/core/kis_exif_info.cc @@ -0,0 +1,66 @@ +/* + * This file is part of Krita + * + * Copyright (c) 2006 Cyrille Berger <cberger@cberger.net> + * + * 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_exif_info.h" + +#include <stdlib.h> + +#include <kdebug.h> + +KisExifInfo::KisExifInfo() +{} + + +KisExifInfo::~KisExifInfo() +{} + + +bool KisExifInfo::load(const QDomElement& elmt) +{ + if(elmt.tagName() != "ExifInfo") + return false; + for( QDomNode node = elmt.firstChild(); !node.isNull(); node = node.nextSibling() ) + { + QDomElement e = node.toElement(); + if ( !e.isNull() ) + { + if(e.tagName() == "ExifValue") + { + QString key = e.attribute("name"); + ExifValue eV; + eV.load(e); + setValue(key, eV); + } + } + } + return true; +} + +QDomElement KisExifInfo::save(QDomDocument& doc) +{ + QDomElement elmt = doc.createElement("ExifInfo"); + for( KisExifInfo::evMap::const_iterator it = begin(); it != end(); ++it) + { + ExifValue ev = it.data(); + QDomElement evD = ev.save( doc); + evD.setAttribute("name", it.key()); + elmt.appendChild(evD); + } + return elmt; +} diff --git a/krita/core/kis_exif_info.h b/krita/core/kis_exif_info.h new file mode 100644 index 00000000..aaebf3a9 --- /dev/null +++ b/krita/core/kis_exif_info.h @@ -0,0 +1,58 @@ +/* + * This file is part of Krita + * + * Copyright (c) 2006 Cyrille Berger <cberger@cberger.net> + * + * 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_DOCUMENT_INFO_EXIF_H +#define KIS_DOCUMENT_INFO_EXIF_H + +#include "kis_exif_value.h" + +#include <qdom.h> +#include <qmap.h> + +class KisExifInfo +{ + public: + KisExifInfo(); + virtual ~KisExifInfo(); + + virtual bool load(const QDomElement& elmt); + virtual QDomElement save(QDomDocument& doc); + + bool getValue(QString name, ExifValue& value) + { + if ( m_values.find( name ) == m_values.end() ) { + return false; + } + else { + value = m_values[name]; + return true; + } + } + void setValue(QString name, ExifValue value) + { + m_values[name] = value; + } + typedef QMap<QString, ExifValue> evMap; + evMap::const_iterator begin() const { return m_values.begin(); } + evMap::const_iterator end() const { return m_values.end(); } + private: + evMap m_values; +}; + +#endif diff --git a/krita/core/kis_exif_value.cc b/krita/core/kis_exif_value.cc new file mode 100644 index 00000000..25e78208 --- /dev/null +++ b/krita/core/kis_exif_value.cc @@ -0,0 +1,678 @@ +/* + * This file is part of Krita + * + * Copyright (c) 2006 Cyrille Berger <cberger@cberger.net> + * + * 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_exif_value.h" + +#include <kdebug.h> +#include <kmdcodec.h> + +namespace { +void set16Bit (unsigned char *data, ExifValue::ByteOrder order, const Q_UINT16* value) +{ + switch (order) { + case ExifValue::BYTE_ORDER_MOTOROLA: + data[0] = (unsigned char) (*value >> 8); + data[1] = (unsigned char) *value; + break; + case ExifValue::BYTE_ORDER_INTEL: + data[0] = (unsigned char) *value; + data[1] = (unsigned char) (*value >> 8); + break; + } +} + +void get16Bit (const unsigned char *data, ExifValue::ByteOrder order, Q_UINT16* value) +{ + switch (order) { + case ExifValue::BYTE_ORDER_MOTOROLA: + *value = ((data[0] << 8) | data[1]); + break; + case ExifValue::BYTE_ORDER_INTEL: + *value = ((data[1] << 8) | data[0]); + break; + } +} + +void get32Bit (const unsigned char *data, ExifValue::ByteOrder order, Q_UINT32* value) +{ + switch (order) { + case ExifValue::BYTE_ORDER_MOTOROLA: + *value = ((data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3]); + break; + case ExifValue::BYTE_ORDER_INTEL: + *value = ((data[3] << 24) | (data[2] << 16) | (data[1] << 8) | data[0]); + } +} + +void set32Bit(unsigned char *data, ExifValue::ByteOrder order, const Q_UINT32* value) +{ + switch (order) { + case ExifValue::BYTE_ORDER_MOTOROLA: + data[0] = (unsigned char) (*value >> 24); + data[1] = (unsigned char) (*value >> 16); + data[2] = (unsigned char) (*value >> 8); + data[3] = (unsigned char) *value; + break; + case ExifValue::BYTE_ORDER_INTEL: + data[3] = (unsigned char) (*value >> 24); + data[2] = (unsigned char) (*value >> 16); + data[1] = (unsigned char) (*value >> 8); + data[0] = (unsigned char) *value; + break; + } +} + +void get64Bit (const unsigned char *data, ExifValue::ByteOrder order, Q_UINT64* value) +{ + switch (order) { + case ExifValue::BYTE_ORDER_MOTOROLA: + *value = (((Q_UINT64)data[0] << 56) | ((Q_UINT64)data[1] << 48) | ((Q_UINT64)data[2] << 40) | ((Q_UINT64)data[3] << 32) | ((Q_UINT64)data[4] << 24) | ((Q_UINT64)data[5] << 16) | ((Q_UINT64)data[6] << 8) | (Q_UINT64)data[7]); + break; + case ExifValue::BYTE_ORDER_INTEL: + *value = (((Q_UINT64)data[7] << 56) | ((Q_UINT64)data[6] << 48) | ((Q_UINT64)data[5] << 40) | ((Q_UINT64)data[4] << 32) | ((Q_UINT64)data[3] << 24) | ((Q_UINT64)data[2] << 16) | ((Q_UINT64)data[1] << 8) | (Q_UINT64)data[0]); + } +} + +void set64Bit(unsigned char *data, ExifValue::ByteOrder order, const Q_UINT64* value) +{ + switch (order) { + case ExifValue::BYTE_ORDER_MOTOROLA: + data[0] = (unsigned char) (*value >> 56); + data[1] = (unsigned char) (*value >> 48); + data[2] = (unsigned char) (*value >> 40); + data[3] = (unsigned char) (*value >> 32); + data[4] = (unsigned char) (*value >> 24); + data[5] = (unsigned char) (*value >> 16); + data[6] = (unsigned char) (*value >> 8); + data[7] = (unsigned char) *value; + break; + case ExifValue::BYTE_ORDER_INTEL: + data[7] = (unsigned char) (*value >> 56); + data[6] = (unsigned char) (*value >> 48); + data[5] = (unsigned char) (*value >> 40); + data[4] = (unsigned char) (*value >> 32); + data[3] = (unsigned char) (*value >> 24); + data[2] = (unsigned char) (*value >> 16); + data[1] = (unsigned char) (*value >> 8); + data[0] = (unsigned char) *value; + break; + } +} + + +} + +ExifValue::ExifValue(ExifType ntype, unsigned char *data, unsigned int size, int ifd, uint ncomponents, ExifValue::ByteOrder order ) : m_ifd(ifd), m_type(ntype), m_components(ncomponents), m_value(0) +{ + allocData(); + setValue(data, size, order); +} + +void ExifValue::allocData() +{ + if( type() != EXIF_TYPE_ASCII && type() != EXIF_TYPE_UNDEFINED) + { + m_value = new ExifNumber[components()]; + } else if ( type() == EXIF_TYPE_ASCII ) + { + m_value = new QString(); + } else if ( type() == EXIF_TYPE_UNDEFINED) + { + m_value = new UByteArray(); + } +} + +bool ExifValue::load(const QDomElement& elmt) +{ + QString attr; + if( (attr = elmt.attribute("ifd")).isNull() ) + return false; + m_ifd = attr.toInt(); + if( (attr = elmt.attribute("components")).isNull() ) + return false; + m_components = attr.toInt(); + if( (attr = elmt.attribute("type")).isNull() ) + return false; + m_type = (ExifValue::ExifType)attr.toInt(); + allocData(); + switch(type()) + { + case EXIF_TYPE_BYTE: + for(uint i = 0; i < components(); i++) + { + if( (attr = elmt.attribute(QString("value%1").arg(i) ) ).isNull() ) + { + setValue(i, (Q_UINT8)0); + } else { + setValue(i, (Q_UINT8) attr.toUInt()); + } + } + break; + case EXIF_TYPE_ASCII: + setAsAscii( elmt.attribute("value" ) ); + break; + case EXIF_TYPE_SHORT: + for(uint i = 0; i < components(); i++) + { + if( (attr = elmt.attribute(QString("value%1").arg(i) ) ).isNull() ) + { + setValue(i, (Q_UINT16)0); + } else { + setValue(i, (Q_UINT16) attr.toUInt()); + } + } + break; + case EXIF_TYPE_LONG: + for(uint i = 0; i < components(); i++) + { + if( (attr = elmt.attribute(QString("value%1").arg(i) ) ).isNull() ) + { + setValue(i, (Q_UINT32)0); + } else { + setValue(i, (Q_UINT32) attr.toUInt()); + } + } + break; + case EXIF_TYPE_RATIONAL: + for(uint i = 0; i < components(); i++) + { + KisExifRational r; + if( (attr = elmt.attribute(QString("numerator%1").arg(i) ) ).isNull() ) + { + r.numerator = (Q_UINT32)0; + } else { + r.numerator = (Q_UINT32) attr.toUInt(); + } + if( (attr = elmt.attribute(QString("denominator%1").arg(i) ) ).isNull() ) + { + r.denominator = (Q_UINT32)0; + } else { + r.denominator = (Q_UINT32) attr.toUInt(); + } + setValue(i, r); + } + break; + case EXIF_TYPE_SBYTE: + for(uint i = 0; i < components(); i++) + { + if( (attr = elmt.attribute(QString("value%1").arg(i) ) ).isNull() ) + { + setValue(i, (Q_INT8)0); + } else { + setValue(i, (Q_INT8) attr.toInt()); + } + } + break; + case EXIF_TYPE_UNDEFINED: + { + QString instr = elmt.attribute("value"); + QByteArray out; + QByteArray in = instr.utf8(); + KCodecs::base64Decode( in, out); + out.resize(out.size() - 2 ); + setAsUndefined((uchar*)out.data(), out.size() ); + } + break; + case EXIF_TYPE_SSHORT: + for(uint i = 0; i < components(); i++) + { + if( (attr = elmt.attribute(QString("value%1").arg(i) ) ).isNull() ) + { + setValue(i, (Q_INT16)0); + } else { + setValue(i, (Q_INT16) attr.toInt()); + } + } + break; + case EXIF_TYPE_SLONG: + for(uint i = 0; i < components(); i++) + { + if( (attr = elmt.attribute(QString("value%1").arg(i) ) ).isNull() ) + { + setValue(i, (Q_INT32)0); + } else { + setValue(i, (Q_INT32) attr.toInt()); + } + } + break; + case EXIF_TYPE_SRATIONAL: + for(uint i = 0; i < components(); i++) + { + KisExifSRational r; + if( (attr = elmt.attribute(QString("numerator%1").arg(i) ) ).isNull() ) + { + r.numerator = (Q_INT32)0; + } else { + r.numerator = (Q_INT32) attr.toInt(); + } + if( (attr = elmt.attribute(QString("denominator%1").arg(i) ) ).isNull() ) + { + r.denominator = (Q_UINT32)0; + } else { + r.denominator = (Q_UINT32) attr.toInt(); + } + setValue(i, r); + } + break; + case EXIF_TYPE_FLOAT: + for(uint i = 0; i < components(); i++) + { + if( (attr = elmt.attribute(QString("value%1").arg(i) ) ).isNull() ) + { + setValue(i, (float)0); + } else { + setValue(i, (float) attr.toFloat()); + } + } + break; + case EXIF_TYPE_DOUBLE: + for(uint i = 0; i < components(); i++) + { + if( (attr = elmt.attribute(QString("value%1").arg(i) ) ).isNull() ) + { + setValue(i, (double)0); + } else { + setValue(i, (double) attr.toDouble()); + } + } + break; + case EXIF_TYPE_UNKNOW: + break; + + } + return true; +} + +QDomElement ExifValue::save(QDomDocument& doc) +{ + QDomElement elmt = doc.createElement("ExifValue"); + elmt.setAttribute("ifd", ifd()); + elmt.setAttribute("components", components() ); + elmt.setAttribute("type", type() ); + switch(type()) + { + case EXIF_TYPE_BYTE: + for(uint i = 0; i < components(); i++) + elmt.setAttribute(QString("value%1").arg(i), asByte( i ) ); + break; + case EXIF_TYPE_ASCII: + elmt.setAttribute("value", asAscii() ); + break; + case EXIF_TYPE_SHORT: + for(uint i = 0; i < components(); i++) + elmt.setAttribute(QString("value%1").arg(i), asShort( i ) ); + break; + case EXIF_TYPE_LONG: + for(uint i = 0; i < components(); i++) + elmt.setAttribute(QString("value%1").arg(i), asLong( i ) ); + break; + case EXIF_TYPE_RATIONAL: + for(uint i = 0; i < components(); i++) + { + KisExifRational r = asRational(i); + elmt.setAttribute(QString("numerator%1").arg(i), r.numerator ); + elmt.setAttribute(QString("denominator%1").arg(i), r.denominator ); + } + break; + case EXIF_TYPE_SBYTE: + for(uint i = 0; i < components(); i++) + elmt.setAttribute(QString("value%1").arg(i), asSByte( i ) ); + break; + case EXIF_TYPE_UNDEFINED: + { + UByteArray value = asUndefined(); + QByteArray data; + data.setRawData((char*)value.data(), value.size()); + QByteArray encodedData; + KCodecs::base64Encode( data, encodedData ); + data.resetRawData( (char*)value.data(), value.size()); + elmt.setAttribute("value", encodedData); + } + break; + case EXIF_TYPE_SSHORT: + for(uint i = 0; i < components(); i++) + elmt.setAttribute(QString("value%1").arg(i), asSShort( i ) ); + break; + case EXIF_TYPE_SLONG: + for(uint i = 0; i < components(); i++) + elmt.setAttribute(QString("value%1").arg(i), asSLong( i ) ); + break; + case EXIF_TYPE_SRATIONAL: + for(uint i = 0; i < components(); i++) + { + KisExifSRational r = asSRational(i); + elmt.setAttribute(QString("numerator%1").arg(i), r.numerator ); + elmt.setAttribute(QString("denominator%1").arg(i), r.denominator ); + } + break; + case EXIF_TYPE_FLOAT: + for(uint i = 0; i < components(); i++) + elmt.setAttribute(QString("value%1").arg(i), asFloat( i ) ); + break; + case EXIF_TYPE_DOUBLE: + for(uint i = 0; i < components(); i++) + elmt.setAttribute(QString("value%1").arg(i), asDouble( i ) ); + break; + case EXIF_TYPE_UNKNOW: + break; + } + return elmt; +} + + +void ExifValue::setValue(const unsigned char *data, unsigned int size, ExifValue::ByteOrder order) +{ + switch(type()) + { + case EXIF_TYPE_BYTE: + if( size == components() ) + { + ExifNumber n; + for(uint i = 0; i < components(); i++) + { + n.m_byte = data[i]; + setAsExifNumber( i, n); + } + } + break; + case EXIF_TYPE_ASCII: + setAsAscii((char*) data); + break; + case EXIF_TYPE_SHORT: + if( size == 2*components() ) + { + ExifNumber n; + for(uint i = 0; i < components(); i++) + { + get16Bit( data + 2 * i, order, &n.m_short); + setAsExifNumber( i, n); + } + } + break; + case EXIF_TYPE_LONG: + if( size == 4*components() ) + { + ExifNumber n; + for(uint i = 0; i < components(); i++) + { + get32Bit( data + 4 * i, order, &n.m_long); + setAsExifNumber( i, n); + } + } + break; + case EXIF_TYPE_RATIONAL: + if( size == 8*components() ) + { + ExifNumber n; + for(uint i = 0; i < components(); i++) + { + get32Bit( data + 8 * i, order, &n.m_rational.numerator); + get32Bit( data + 8 * i + 4, order, &n.m_rational.denominator); + setAsExifNumber( i, n); + } + } + break; + case EXIF_TYPE_SBYTE: + if( size == components() ) + { + ExifNumber n; + for(uint i = 0; i < components(); i++) + { + n.m_sbyte = ((Q_INT8*)data)[i]; + setAsExifNumber( i, n); + } + } + break; + case EXIF_TYPE_UNDEFINED: + setAsUndefined(data, size); + break; + case EXIF_TYPE_SSHORT: + if( size == 2*components() ) + { + ExifNumber n; + for(uint i = 0; i < components(); i++) + { + get16Bit( data + 2 * i, order, (Q_UINT16*)&n.m_sshort); + setAsExifNumber( i, n); + } + } + break; + case EXIF_TYPE_SLONG: + if( size == 4*components() ) + { + ExifNumber n; + for(uint i = 0; i < components(); i++) + { + get32Bit( data + 4 * i, order, (Q_UINT32*)&n.m_slong); + setAsExifNumber( i, n); + } + } + break; + case EXIF_TYPE_SRATIONAL: + if( size == 8*components() ) + { + ExifNumber n; + for(uint i = 0; i < components(); i++) + { + get32Bit( data + 8 * i, order, (Q_UINT32*)&n.m_srational.numerator); + get32Bit( data + 8 * i + 4, order, (Q_UINT32*)&n.m_srational.denominator); + setAsExifNumber( i, n); + } + } + break; + case EXIF_TYPE_FLOAT: + if( size == 4*components() ) + { + ExifNumber n; + for(uint i = 0; i < components(); i++) + { + get32Bit( data + 4 * i, order, (Q_UINT32*)&n.m_float); + setAsExifNumber( i, n); + } + } + break; + case EXIF_TYPE_DOUBLE: + if( size == 8*components() ) + { + ExifNumber n; + for(uint i = 0; i < components(); i++) + { + get64Bit( data + 8 * i, order, (Q_UINT64*)&n.m_double); + setAsExifNumber( i, n); + } + } + break; + case EXIF_TYPE_UNKNOW: + break; + } +} + +void ExifValue::convertToData(unsigned char ** data, unsigned int* size, ExifValue::ByteOrder order) +{ + switch(type()) + { + case EXIF_TYPE_BYTE: + *size = components(); + *data = new uchar[*size]; + for(uint i = 0; i < components(); i++) + { + (*data)[i] = asExifNumber(i).m_byte; + } + return; + case EXIF_TYPE_ASCII: + { + QString str = asAscii(); + *size = str.length(); + *data = new uchar[ *size ]; + uchar* ptr = *data; + memcpy(ptr, str.ascii(), (*size)*sizeof(uchar)); + } + return; + break; + case EXIF_TYPE_SHORT: + { + *size = 2*components(); + *data = new uchar[*size]; + for(uint i = 0; i < components(); i++) + { + set16Bit( (*data) + 2 * i, order, &asExifNumber(i).m_short); + } + return; + } + case EXIF_TYPE_LONG: + { + *size = 4*components(); + *data = new uchar[*size]; + for(uint i = 0; i < components(); i++) + { + set32Bit( (*data) + 4 * i, order, &asExifNumber(i).m_long); + } + return; + } + case EXIF_TYPE_RATIONAL: + *size = 8*components(); + *data = new uchar[*size]; + for(uint i = 0; i < components(); i++) + { + ExifNumber n = asExifNumber(i); + set32Bit( (*data) + 8 * i, order, &n.m_rational.numerator); + set32Bit( (*data) + 8 * i + 4, order, &n.m_rational.denominator); + } + return; + case EXIF_TYPE_SBYTE: + *size = components(); + *data = new uchar[*size]; + for(uint i = 0; i < components(); i++) + { + *(((Q_INT8*)*data) + i) = asExifNumber(i).m_sbyte; + } + return; + case EXIF_TYPE_UNDEFINED: + { + UByteArray array = asUndefined(); + *size = array.size(); + *data = new uchar[*size]; + memcpy( *data, array.data(), (*size)*sizeof(unsigned char)); + } + return; + case EXIF_TYPE_SSHORT: + *size = 2*components(); + *data = new uchar[*size]; + for(uint i = 0; i < components(); i++) + { + set16Bit( (*data) + 2 * i, order, (Q_UINT16*)&asExifNumber(i).m_sshort); + } + return; + case EXIF_TYPE_SLONG: + *size = 4*components(); + *data = new uchar[*size]; + for(uint i = 0; i < components(); i++) + { + set32Bit( (*data) + 4 * i, order, (Q_UINT32*)&asExifNumber(i).m_slong); + } + return; + case EXIF_TYPE_SRATIONAL: + *size = 8*components(); + *data = new uchar[*size]; + for(uint i = 0; i < components(); i++) + { + ExifNumber n = asExifNumber(i); + set32Bit( (*data) + 4 * i, order, (Q_UINT32*)&asExifNumber(i).m_srational.numerator); + set32Bit( (*data) + 4 * i + 4, order, (Q_UINT32*)&asExifNumber(i).m_srational.denominator); + } + return; + case EXIF_TYPE_FLOAT: + *size = 4*components(); + *data = new uchar[*size]; + for(uint i = 0; i < components(); i++) + { + set32Bit( (*data) + 4 * i, order, (Q_UINT32*)&asExifNumber(i).m_float); + } + return; + case EXIF_TYPE_DOUBLE: + *size = 8*components(); + *data = new uchar[*size]; + for(uint i = 0; i < components(); i++) + { + set64Bit( (*data) + 4 * i, order, (Q_UINT64*)&asExifNumber(i).m_double); + } + return; + case EXIF_TYPE_UNKNOW: + break; + } +} + +QString ExifValue::toString() +{ + switch(type()) + { + case EXIF_TYPE_ASCII: + return asAscii(); + case EXIF_TYPE_UNDEFINED: + { + QString undefined = "undefined"; + UByteArray array = asUndefined(); + for(uint i = 0; i < components(); i++) + { + undefined += "\\" + QString().setNum( array[i] ); + } + return undefined; + } + default: + { + QString str = ""; + for(uint i = 0; i < components(); i++) + { + str += toString(i); + } + return str; + } + } +} + +QString ExifValue::toString(uint i) +{ + switch(type()) + { + case EXIF_TYPE_BYTE: + return QString("%1 ").arg( asExifNumber( i ).m_byte ); + case EXIF_TYPE_SHORT: + return QString("%1 ").arg( asExifNumber( i ).m_short ); + case EXIF_TYPE_LONG: + return QString("%1 ").arg( asExifNumber( i ).m_long ); + case EXIF_TYPE_RATIONAL: + return QString("%1 / %2 ").arg( asExifNumber( i ).m_rational.numerator ).arg( asExifNumber( i ).m_rational.denominator ); + case EXIF_TYPE_SBYTE: + return QString("%1 ").arg( asExifNumber( i ).m_sbyte ); + case EXIF_TYPE_SSHORT: + return QString("%1 ").arg( asExifNumber( i ).m_sshort ); + case EXIF_TYPE_SLONG: + return QString("%1 ").arg( asExifNumber( i ).m_slong ); + case EXIF_TYPE_SRATIONAL: + return QString("%1 / %2 ").arg( asExifNumber( i ).m_srational.numerator ).arg( asExifNumber( i ).m_srational.denominator ); + case EXIF_TYPE_FLOAT: + return QString("%1 ").arg( asExifNumber( i ).m_float ); + case EXIF_TYPE_DOUBLE: + return QString("%1 ").arg( asExifNumber( i ).m_double ); + default: + return "unknow "; + } +} + diff --git a/krita/core/kis_exif_value.h b/krita/core/kis_exif_value.h new file mode 100644 index 00000000..6e37e0a3 --- /dev/null +++ b/krita/core/kis_exif_value.h @@ -0,0 +1,270 @@ +/* + * This file is part of Krita + * + * Copyright (c) 2006 Cyrille Berger <cberger@cberger.net> + * + * 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_EXIF_VALUE_H +#define KIS_EXIF_VALUE_H + +#include <qdom.h> + +#include <qcstring.h> +#include <qstring.h> + +typedef QMemArray<Q_UINT8> UByteArray; + +struct KisExifRational { + Q_UINT32 numerator; + Q_UINT32 denominator; +}; + +struct KisExifSRational { + Q_INT32 numerator; + Q_INT32 denominator; +}; + +class ExifValue { + typedef union { + Q_UINT8 m_byte; + Q_UINT16 m_short; + Q_UINT32 m_long; + KisExifRational m_rational; + Q_INT8 m_sbyte; + Q_INT16 m_sshort; + Q_INT32 m_slong; + KisExifSRational m_srational; + float m_float; + double m_double; + } ExifNumber; + public: + enum ExifType { + EXIF_TYPE_BYTE = 1, + EXIF_TYPE_ASCII = 2, + EXIF_TYPE_SHORT = 3, + EXIF_TYPE_LONG = 4, + EXIF_TYPE_RATIONAL = 5, + EXIF_TYPE_SBYTE = 6, + EXIF_TYPE_UNDEFINED = 7, + EXIF_TYPE_SSHORT = 8, + EXIF_TYPE_SLONG = 9, + EXIF_TYPE_SRATIONAL = 10, + EXIF_TYPE_FLOAT = 11, + EXIF_TYPE_DOUBLE = 12, + EXIF_TYPE_UNKNOW = 13 + }; + enum ByteOrder { + BYTE_ORDER_MOTOROLA, + BYTE_ORDER_INTEL + }; + ExifValue() : m_ifd(-1), m_type(EXIF_TYPE_UNKNOW), m_components(0), m_value(0) { } + ExifValue(ExifType type, unsigned char *data, unsigned int size, int ifd, uint components, ExifValue::ByteOrder order); + + virtual bool load(const QDomElement& elmt); + virtual QDomElement save(QDomDocument& doc); + + /** + * Return the type of the array + */ + inline ExifType type() { return m_type; } + inline const UByteArray asUndefined() { + if(m_type == EXIF_TYPE_UNDEFINED) + return *(UByteArray*) m_value; + return UByteArray(); + } + inline void setAsUndefined(const unsigned char *data, unsigned int size) + { + if(m_type == EXIF_TYPE_UNDEFINED) + { + ((UByteArray*)m_value)->duplicate(data, size); + m_components = size; + } + } + inline const QString asAscii() { + if(m_type == EXIF_TYPE_ASCII) + return QString(*(QString*) m_value); + return QString(); + } + inline void setAsAscii(char* data) + { + if(m_type == EXIF_TYPE_ASCII) + { + QString str = QString((char*) data); + *(QString*)m_value = str; + m_components = str.length(); + } + } + inline void setAsAscii(QString str) + { + *(QString*)m_value = str; + m_components = str.length(); + } + void convertToData(unsigned char ** data, unsigned int* size, ExifValue::ByteOrder order); + /** + * Return the ifd number to which this ExifValue belongs. + */ + inline int ifd() { return m_ifd; } + /** + * Return the number of components of this ExifValue + */ + inline uint components() { return m_components; } + + /** + * This function return the value of a the ExifValue as a string. + */ + QString toString(); + + inline Q_UINT8 asByte(uint i) + { + if(m_type == EXIF_TYPE_BYTE) + return asExifNumber(i).m_byte; + return 0; + } + inline void setValue(uint i, Q_UINT8 v) + { + ((ExifNumber*)m_value)[i].m_byte = v; + } + inline Q_UINT8 asShort(uint i) + { + if(m_type == EXIF_TYPE_SHORT) + return asExifNumber(i).m_short; + return 0; + } + inline void setValue(uint i, Q_UINT16 v) + { + ((ExifNumber*)m_value)[i].m_short = v; + } + inline Q_UINT8 asLong(uint i) + { + if(m_type == EXIF_TYPE_LONG) + return asExifNumber(i).m_long; + return 0; + } + inline void setValue(uint i, Q_UINT32 v) + { + ((ExifNumber*)m_value)[i].m_long = v; + } + inline KisExifRational asRational(uint i) + { + if(m_type == EXIF_TYPE_RATIONAL) + return asExifNumber(i).m_rational; + return KisExifRational(); + } + inline void setValue(uint i, Q_UINT32 n, Q_UINT32 d) + { + ((ExifNumber*)m_value)[i].m_rational.numerator = n; + ((ExifNumber*)m_value)[i].m_rational.denominator = d; + } + inline void setValue(uint i, KisExifRational r) + { + ((ExifNumber*)m_value)[i].m_rational = r; + } + inline Q_INT8 asSByte(uint i) + { + if(m_type == EXIF_TYPE_SBYTE) + return asExifNumber(i).m_sbyte; + return 0; + } + inline void setValue(uint i, Q_INT8 v) + { + ((ExifNumber*)m_value)[i].m_sbyte = v; + } + inline Q_INT16 asSShort(uint i) + { + if(m_type == EXIF_TYPE_SSHORT) + return asExifNumber(i).m_sshort; + return 0; + } + inline void setValue(uint i, Q_INT16 v) + { + ((ExifNumber*)m_value)[i].m_sshort = v; + } + inline Q_INT32 asSLong(uint i) + { + if(m_type == EXIF_TYPE_SLONG) + return asExifNumber(i).m_slong; + return 0; + } + inline void setValue(uint i, Q_INT32 v) + { + ((ExifNumber*)m_value)[i].m_slong = v; + } + inline KisExifSRational asSRational(uint i) + { + if(m_type == EXIF_TYPE_SRATIONAL) + return asExifNumber(i).m_srational; + return KisExifSRational(); + } + inline void setValue(uint i, KisExifSRational r) + { + ((ExifNumber*)m_value)[i].m_srational = r; + } + inline void setValue(uint i, Q_INT32 n, Q_INT32 d) + { + ((ExifNumber*)m_value)[i].m_srational.numerator = n; + ((ExifNumber*)m_value)[i].m_srational.denominator = d; + } + inline float asFloat(uint i) + { + if(m_type == EXIF_TYPE_FLOAT) + return asExifNumber(i).m_float; + return 0.; + } + inline void setValue(uint i, float v) + { + ((ExifNumber*)m_value)[i].m_float = v; + } + inline double asDouble(uint i) + { + if(m_type == EXIF_TYPE_DOUBLE) + return asExifNumber(i).m_double; + return 0.; + } + inline void setValue(uint i, double v) + { + ((ExifNumber*)m_value)[i].m_double = v; + } + private: + /** + * Return the ith component as a string. + */ + QString toString(uint i); + void setValue(const unsigned char *data, unsigned int size, ExifValue::ByteOrder order); + /** + * Return the ExifValue as a number. + */ + inline const ExifNumber asExifNumber(uint index) + { + Q_ASSERT(index < m_components); + return ((ExifNumber*)m_value)[index]; + } + inline void setAsExifNumber(uint index, ExifNumber n) + { + Q_ASSERT(index < m_components); + ((ExifNumber*)m_value)[index] = n; + } + /** + * This function will allocate the memory used for storing the current data. + */ + void allocData(); + private: + int m_ifd; + ExifType m_type; + uint m_components; + void *m_value; +}; + +#endif diff --git a/krita/core/kis_fill_painter.cc b/krita/core/kis_fill_painter.cc new file mode 100644 index 00000000..0ccb9e8a --- /dev/null +++ b/krita/core/kis_fill_painter.cc @@ -0,0 +1,407 @@ +/* + * Copyright (c) 2004 Adrian Page <adrian@pagenet.plus.com> + * Copyright (c) 2004 Bart Coppens <kde@bartcoppens.be> + * + * 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 <stdlib.h> +#include <string.h> +#include <cfloat> +#include <stack> + +#include "qbrush.h" +#include "qfontinfo.h" +#include "qfontmetrics.h" +#include "qpen.h" +#include "qregion.h" +#include "qwmatrix.h" +#include <qimage.h> +#include <qmap.h> +#include <qpainter.h> +#include <qpixmap.h> +#include <qpointarray.h> +#include <qrect.h> +#include <qstring.h> + +#include <kdebug.h> +#include <kcommand.h> +#include <klocale.h> + +#include "kis_brush.h" +#include "kis_debug_areas.h" +#include "kis_image.h" +#include "kis_layer.h" +#include "kis_paint_device.h" +#include "kis_painter.h" +#include "kis_pattern.h" +#include "kis_rect.h" +#include "kis_colorspace.h" +#include "kis_transaction.h" +#include "kis_types.h" +#include "kis_vec.h" +#include "kis_selection.h" +#include "kis_fill_painter.h" +#include "kis_iterators_pixel.h" +#include "kis_iterator.h" +#include "kis_color.h" +#include "kis_selection.h" + +namespace { +} + +KisFillPainter::KisFillPainter() + : super() +{ + m_width = m_height = -1; + m_sampleMerged = false; + m_careForSelection = false; + m_fuzzy = false; +} + +KisFillPainter::KisFillPainter(KisPaintDeviceSP device) : super(device) +{ + m_width = m_height = -1; + m_sampleMerged = false; + m_careForSelection = false; + m_fuzzy = false; +} + +// 'regular' filling +// XXX: This also needs renaming, since filling ought to keep the opacity and the composite op in mind, +// this is more eraseToColor. +void KisFillPainter::fillRect(Q_INT32 x1, Q_INT32 y1, Q_INT32 w, Q_INT32 h, const KisColor& kc, Q_UINT8 opacity) +{ + if (w > 0 && h > 0) { + // Make sure we're in the right colorspace + + KisColor kc2(kc); // get rid of const + kc2.convertTo(m_device->colorSpace()); + Q_UINT8 * data = kc2.data(); + m_device->colorSpace()->setAlpha(data, opacity, 1); + + m_device->fill(x1, y1, w, h, data); + + addDirtyRect(QRect(x1, y1, w, h)); + } +} + +void KisFillPainter::fillRect(Q_INT32 x1, Q_INT32 y1, Q_INT32 w, Q_INT32 h, KisPattern * pattern) { + if (!pattern) return; + if (!pattern->valid()) return; + if (!m_device) return; + + + KisPaintDeviceSP patternLayer = pattern->image(m_device->colorSpace()); + + int sx, sy, sw, sh; + + int y = y1; + + if (y >= 0) { + sy = y % pattern->height(); + } else { + sy = pattern->height() - (((-y - 1) % pattern->height()) + 1); + } + + while (y < y1 + h) { + sh = QMIN((y1 + h) - y, pattern->height() - sy); + + int x = x1; + + if (x >= 0) { + sx = x % pattern->width(); + } else { + sx = pattern->width() - (((-x - 1) % pattern->width()) + 1); + } + + while (x < x1 + w) { + sw = QMIN((x1 + w) - x, pattern->width() - sx); + + bitBlt(x, y, m_compositeOp, patternLayer.data(), m_opacity, sx, sy, sw, sh); + x += sw; sx = 0; + } + + y+=sh; sy = 0; + } + + addDirtyRect(QRect(x1, y1, w, h)); +} + +// flood filling + +void KisFillPainter::fillColor(int startX, int startY) { + genericFillStart(startX, startY); + + // Now create a layer and fill it + KisPaintDeviceSP filled = new KisPaintDevice(m_device->colorSpace(), "filled"); + Q_CHECK_PTR(filled); + KisFillPainter painter(filled.data()); + painter.fillRect(0, 0, m_width, m_height, m_paintColor); + painter.end(); + + genericFillEnd(filled); +} + +void KisFillPainter::fillPattern(int startX, int startY) { + genericFillStart(startX, startY); + + // Now create a layer and fill it + KisPaintDeviceSP filled = new KisPaintDevice(m_device->colorSpace(), "filled"); + Q_CHECK_PTR(filled); + KisFillPainter painter(filled.data()); + painter.fillRect(0, 0, m_width, m_height, m_pattern); + painter.end(); + + genericFillEnd(filled); +} + +void KisFillPainter::genericFillStart(int startX, int startY) { + m_cancelRequested = false; + + if (m_width < 0 || m_height < 0) { + if (m_device->image()) { + m_width = m_device->image()->width(); + m_height = m_device->image()->height(); + } else { + m_width = m_height = 500; + } + } + + m_size = m_width * m_height; + + // Create a selection from the surrounding area + m_selection = createFloodSelection(startX, startY); +} + +void KisFillPainter::genericFillEnd(KisPaintDeviceSP filled) { + if (m_cancelRequested) { + m_width = m_height = -1; + return; + } + + QRect rc = m_selection->selectedRect(); + + bltSelection(rc.x(), rc.y(), m_compositeOp, filled, m_selection, m_opacity, + rc.x(), rc.y(), rc.width(), rc.height()); + + emit notifyProgressDone(); + + m_width = m_height = -1; +} + +struct FillSegment { + FillSegment(int x, int y/*, FillSegment* parent*/) : x(x), y(y)/*, parent(parent)*/ {} + int x; + int y; +// FillSegment* parent; +}; + +typedef enum { None = 0, Added = 1, Checked = 2 } Status; + +KisSelectionSP KisFillPainter::createFloodSelection(int startX, int startY) { + if (m_width < 0 || m_height < 0) { + if (m_device->hasSelection() && m_careForSelection) { + + QRect rc = m_device->selection()->selectedRect(); + m_width = rc.width() - (startX - rc.x()); + m_height = rc.height() - (startY - rc.y()); + + } else if (m_device->image()) { + + m_width = m_device->image()->width(); + m_height = m_device->image()->height(); + + } else { + m_width = m_height = 500; + } + } + + // Don't try to fill if we start outside the borders, just return an empty 'fill' + if (startX < 0 || startY < 0 || startX >= m_width || startY >= m_height) + return new KisSelection(m_device); + + KisPaintDeviceSP sourceDevice = 0; + + // sample merged? + if (m_sampleMerged) { + if (!m_device->image()) { + return new KisSelection(m_device); + } + sourceDevice = m_device->image()->mergedImage(); + } else { + sourceDevice = m_device; + } + + m_size = m_width * m_height; + + KisSelectionSP selection = new KisSelection(m_device); + KisColorSpace * colorSpace = selection->colorSpace(); + KisColorSpace * devColorSpace = sourceDevice->colorSpace(); + + Q_UINT8* source = new Q_UINT8[sourceDevice->pixelSize()]; + KisHLineIteratorPixel pixelIt = sourceDevice->createHLineIterator(startX, startY, startX+1, false); + + memcpy(source, pixelIt.rawData(), sourceDevice->pixelSize()); + + std::stack<FillSegment*> stack; + + stack.push(new FillSegment(startX, startY/*, 0*/)); + + Status* map = new Status[m_size]; + + memset(map, None, m_size * sizeof(Status)); + + int progressPercent = 0; int pixelsDone = 0; int currentPercent = 0; + emit notifyProgressStage(i18n("Making fill outline..."), 0); + + bool hasSelection = m_careForSelection && sourceDevice->hasSelection(); + KisSelectionSP srcSel = 0; + if (hasSelection) + srcSel = sourceDevice->selection(); + + while(!stack.empty()) { + FillSegment* segment = stack.top(); + stack.pop(); + if (map[m_width * segment->y + segment->x] == Checked) { + delete segment; + continue; + } + map[m_width * segment->y + segment->x] = Checked; + + int x = segment->x; + int y = segment->y; + + /* We need an iterator that is valid in the range (0,y) - (width,y). Therefore, + it is needed to start the iterator at the first position, and then skip to (x,y). */ + pixelIt = sourceDevice->createHLineIterator(0, y, m_width, false); + pixelIt += x; + Q_UINT8 diff = devColorSpace->difference(source, pixelIt.rawData()); + + if (diff >= m_threshold + || (hasSelection && srcSel->selected(pixelIt.x(), pixelIt.y()) == MIN_SELECTED)) { + delete segment; + continue; + } + + // Here as well: start the iterator at (0,y) + KisHLineIteratorPixel selIt = selection->createHLineIterator(0, y, m_width, true); + selIt += x; + if (m_fuzzy) + colorSpace->fromQColor(Qt::white, MAX_SELECTED - diff, selIt.rawData()); + else + colorSpace->fromQColor(Qt::white, MAX_SELECTED, selIt.rawData()); + + if (y > 0 && (map[m_width * (y - 1) + x] == None)) { + map[m_width * (y - 1) + x] = Added; + stack.push(new FillSegment(x, y-1)); + } + if (y < (m_height - 1) && (map[m_width * (y + 1) + x] == None)) { + map[m_width * (y + 1) + x] = Added; + stack.push(new FillSegment(x, y+1)); + } + + ++pixelsDone; + + bool stop = false; + + --pixelIt; + --selIt; + --x; + + // go to the left + while(!stop && x >= 0 && (map[m_width * y + x] != Checked) ) { // FIXME optimizeable? + map[m_width * y + x] = Checked; + diff = devColorSpace->difference(source, pixelIt.rawData()); + if (diff >= m_threshold + || (hasSelection && srcSel->selected(pixelIt.x(), pixelIt.y()) == MIN_SELECTED)) { + stop = true; + continue; + } + + if (m_fuzzy) + colorSpace->fromQColor(Qt::white, MAX_SELECTED - diff, selIt.rawData()); + else + colorSpace->fromQColor(Qt::white, MAX_SELECTED, selIt.rawData()); + + if (y > 0 && (map[m_width * (y - 1) + x] == None)) { + map[m_width * (y - 1) + x] = Added; + stack.push(new FillSegment(x, y-1)); + } + if (y < (m_height - 1) && (map[m_width * (y + 1) + x] == None)) { + map[m_width * (y + 1) + x] = Added; + stack.push(new FillSegment(x, y+1)); + } + ++pixelsDone; + --pixelIt; + --selIt; + --x; + } + + x = segment->x + 1; + delete segment; + + if (map[m_width * y + x] == Checked) + continue; + + // and go to the right + pixelIt = sourceDevice->createHLineIterator(x, y, m_width, false); + selIt = selection->createHLineIterator(x, y, m_width, true); + + stop = false; + while(!stop && x < m_width && (map[m_width * y + x] != Checked) ) { + diff = devColorSpace->difference(source, pixelIt.rawData()); + map[m_width * y + x] = Checked; + + if (diff >= m_threshold + || (hasSelection && srcSel->selected(pixelIt.x(), pixelIt.y()) == MIN_SELECTED) ) { + stop = true; + continue; + } + + if (m_fuzzy) + colorSpace->fromQColor(Qt::white, MAX_SELECTED - diff, selIt.rawData()); + else + colorSpace->fromQColor(Qt::white, MAX_SELECTED, selIt.rawData()); + + if (y > 0 && (map[m_width * (y - 1) + x] == None)) { + map[m_width * (y - 1) + x] = Added; + stack.push(new FillSegment(x, y-1)); + } + if (y < (m_height - 1) && (map[m_width * (y + 1) + x] == None)) { + map[m_width * (y + 1) + x] = Added; + stack.push(new FillSegment(x, y+1)); + } + ++pixelsDone; + ++pixelIt; + ++selIt; + ++x; + } + + if (m_size > 0) { + progressPercent = (pixelsDone * 100) / m_size; + if (progressPercent > currentPercent) { + emit notifyProgress(progressPercent); + currentPercent = progressPercent; + } + } + } + + + delete[] map; + delete[] source; + + return selection; +} diff --git a/krita/core/kis_fill_painter.h b/krita/core/kis_fill_painter.h new file mode 100644 index 00000000..63591350 --- /dev/null +++ b/krita/core/kis_fill_painter.h @@ -0,0 +1,207 @@ +/* + * Copyright (c) 2004 Adrian Page <adrian@pagenet.plus.com> + * Copyright (c) 2004 Bart Coppens <kde@bartcoppens.be> + * + * 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_FILL_PAINTER_H_ +#define KIS_FILL_PAINTER_H_ + +#include <qrect.h> + +#include "kis_meta_registry.h" +#include "kis_color.h" +#include "kis_colorspace_factory_registry.h" +#include "kis_painter.h" +#include "kis_types.h" +#include <koffice_export.h> + +class KisPattern; + +// XXX: Filling should set dirty rect. +/** + * This painter can be used to fill paint devices in different ways. This can also be used + * for flood filling related operations. + */ +class KRITACORE_EXPORT KisFillPainter : public KisPainter +{ + + typedef KisPainter super; + +public: + + /** + * Construct an empty painter. Use the begin(KisPaintDeviceSP) method to attach + * to a paint device + */ + KisFillPainter(); + /** + * Start painting on the specified paint device + */ + KisFillPainter(KisPaintDeviceSP device); + + /** + * Fill a rectangle with black transparent pixels (0, 0, 0, 0 for RGBA). + */ + void eraseRect(Q_INT32 x1, Q_INT32 y1, Q_INT32 w, Q_INT32 h); + /** + * Overloaded version of the above function. + */ + void eraseRect(const QRect& rc); + + /** + * Fill a rectangle with a certain color. + */ + void fillRect(Q_INT32 x, Q_INT32 y, Q_INT32 w, Q_INT32 h, const KisColor& c); + /** + * Overloaded version of the above function. + */ + void fillRect(const QRect& rc, const KisColor& c); + + /** + * Fill a rectangle with a certain color and opacity. + */ + void fillRect(Q_INT32 x, Q_INT32 y, Q_INT32 w, Q_INT32 h, const KisColor& c, Q_UINT8 opacity); + /** + * Overloaded version of the above function. + */ + void fillRect(const QRect& rc, const KisColor& c, Q_UINT8 opacity); + + /** + * Fill a rectangle with a certain pattern. The pattern is repeated if it does not fit the + * entire rectangle. + */ + void fillRect(Q_INT32 x1, Q_INT32 y1, Q_INT32 w, Q_INT32 h, KisPattern * pattern); + /** + * Overloaded version of the above function. + */ + void fillRect(const QRect& rc, KisPattern * pattern); + + /** + * Fills the enclosed area around the point with the set color. If there is a + * selection, the whole selection is filled + **/ + void fillColor(int startX, int startY); + + /** + * Fills the enclosed area around the point with the set pattern. If there is a + * selection, the whole selection is filled + **/ + void fillPattern(int startX, int startY); + + /** + * Returns a selection mask for the floodfill starting at the specified position. + **/ + KisSelectionSP createFloodSelection(int startX, int startY); + + /** + * Set the threshold for floodfill. The range is 0-255: 0 means the fill will only + * fill parts that are the exact same color, 255 means anything will be filled + */ + void setFillThreshold(int threshold); + /** Returns the fill threshold, see setFillThreshold for details */ + int fillThreshold() const { return m_threshold; } + + /** Sets the width of the layer */ + void setWidth(int w) { m_width = w; } + + /** Sets the height of the layer */ + void setHeight(int h) { m_height = h; } + + /** If sample merged is set to true, the paint device will get the bounds of the + * floodfill from the complete image instead of the layer */ + bool sampleMerged() const { return m_sampleMerged; } + /** Set sample merged. See sampleMerged() for details */ + void setSampleMerged(bool set) { m_sampleMerged = set; } + + /** If true, floodfill doesn't fill outside the selected area of a layer */ + bool careForSelection() const { return m_careForSelection; } + /** Set caring for selection. See careForSelection for details */ + void setCareForSelection(bool set) { m_careForSelection = set; } + + /** + * If true, the floodfill will be fuzzy. This means that the 'value' of selectedness + * will depend on the difference between the sampled color and the color at the current + * position. + */ + bool fuzzyFill() const { return m_fuzzy; } + /** Sets the fuzzyfill parameter. See fuzzyFill for details */ + void setFuzzyFill(bool set) { m_fuzzy = set; } + +private: + // for floodfill + void genericFillStart(int startX, int startY); + void genericFillEnd(KisPaintDeviceSP filled); + + KisSelectionSP m_selection; + + int m_threshold; + int m_size; + int m_width, m_height; + QRect m_rect; + bool m_sampleMerged; + bool m_careForSelection; + bool m_fuzzy; +}; + + +inline +void KisFillPainter::fillRect(Q_INT32 x, Q_INT32 y, Q_INT32 w, Q_INT32 h, const KisColor& c) +{ + fillRect(x, y, w, h, c, OPACITY_OPAQUE); +} + +inline +void KisFillPainter::fillRect(const QRect& rc, const KisColor& c) +{ + fillRect(rc.x(), rc.y(), rc.width(), rc.height(), c, OPACITY_OPAQUE); +} + +inline +void KisFillPainter::eraseRect(Q_INT32 x1, Q_INT32 y1, Q_INT32 w, Q_INT32 h) +{ + KisColorSpace * cs = KisMetaRegistry::instance()->csRegistry()->getRGB8(); + KisColor c(Qt::black, cs); + fillRect(x1, y1, w, h, c, OPACITY_TRANSPARENT); +} + +inline +void KisFillPainter::eraseRect(const QRect& rc) +{ + KisColorSpace * cs = KisMetaRegistry::instance()->csRegistry()->getRGB8(); + KisColor c(Qt::black, cs); + fillRect(rc.x(), rc.y(), rc.width(), rc.height(), c, OPACITY_TRANSPARENT); +} + +inline +void KisFillPainter::fillRect(const QRect& rc, const KisColor& c, Q_UINT8 opacity) +{ + fillRect(rc.x(), rc.y(), rc.width(), rc.height(), c, opacity); +} + +inline +void KisFillPainter::fillRect(const QRect& rc, KisPattern *pattern) +{ + fillRect(rc.x(), rc.y(), rc.width(), rc.height(), pattern); +} + +inline +void KisFillPainter::setFillThreshold(int threshold) +{ + m_threshold = threshold; +} + + +#endif //KIS_FILL_PAINTER_H_ diff --git a/krita/core/kis_filter.cc b/krita/core/kis_filter.cc new file mode 100644 index 00000000..05e1a92d --- /dev/null +++ b/krita/core/kis_filter.cc @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2004 Cyrille Berger <cberger@cberger.net> + * + * 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_filter.h" + +#include <qstring.h> + +#include "kis_types.h" +#include "kis_filter_configuration.h" + +KisFilter::KisFilter(const KisID& id, const QString & category, const QString & entry) + : KisProgressSubject(0, id.id().latin1()) + , m_id(id) + , m_progressDisplay(0) + , m_category(category) + , m_entry(entry) +{ +} + +KisFilterConfiguration * KisFilter::configuration(QWidget*) +{ + return new KisFilterConfiguration(m_id.id(), 0); +} + +KisFilterConfiguration * KisFilter::configuration() +{ + return new KisFilterConfiguration(m_id.id(), 0); +} + +KisFilterConfigWidget * KisFilter::createConfigurationWidget(QWidget *, KisPaintDeviceSP) +{ + return 0; +} + +void KisFilter::setProgressDisplay(KisProgressDisplayInterface * progressDisplay) +{ + m_progressDisplay = progressDisplay; +} + + +void KisFilter::enableProgress() { + m_progressEnabled = true; + m_cancelRequested = false; +} + +void KisFilter::disableProgress() { + m_progressEnabled = false; + m_cancelRequested = false; + m_progressDisplay = 0; +} + +void KisFilter::setProgressTotalSteps(Q_INT32 totalSteps) +{ + if (m_progressEnabled) { + + m_progressTotalSteps = totalSteps; + m_lastProgressPerCent = 0; + m_progressSteps = 0; + emit notifyProgress(0); + } +} + +void KisFilter::setProgress(Q_INT32 progress) +{ + if (m_progressEnabled) { + Q_INT32 progressPerCent = (progress * 100) / m_progressTotalSteps; + m_progressSteps = progress; + + if (progressPerCent != m_lastProgressPerCent) { + + m_lastProgressPerCent = progressPerCent; + emit notifyProgress(progressPerCent); + } + } +} + +void KisFilter::incProgress() +{ + setProgress(++m_progressSteps); + +} + +void KisFilter::setProgressStage(const QString& stage, Q_INT32 progress) +{ + if (m_progressEnabled) { + + Q_INT32 progressPerCent = (progress * 100) / m_progressTotalSteps; + + m_lastProgressPerCent = progressPerCent; + emit notifyProgressStage(stage, progressPerCent); + } +} + +void KisFilter::setProgressDone() +{ + if (m_progressEnabled) { + emit notifyProgressDone(); + } +} + + +bool KisFilter::autoUpdate() { + return m_autoUpdate; +} + +void KisFilter::setAutoUpdate(bool set) { + m_autoUpdate = set; +} + +QRect KisFilter::enlargeRect(QRect rect, KisFilterConfiguration* c) const { + int margin = overlapMarginNeeded(c); + rect.rLeft() -= margin; + rect.rTop() -= margin; + rect.rRight() += margin; + rect.rBottom() += margin; + return rect; +} + +#include "kis_filter.moc" diff --git a/krita/core/kis_filter.h b/krita/core/kis_filter.h new file mode 100644 index 00000000..dd0f5186 --- /dev/null +++ b/krita/core/kis_filter.h @@ -0,0 +1,200 @@ +/* + * Copyright (c) 2004 Cyrille Berger <cberger@cberger.net> + * + * 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_FILTER_H_ +#define _KIS_FILTER_H_ + +#include <list> + +#include <qstring.h> + +#include <ksharedptr.h> +#include <klocale.h> + +#include "kis_types.h" +#include "kis_filter_registry.h" +#include "kis_id.h" +#include "kis_paint_device.h" +#include "kis_progress_subject.h" +#include "kis_filter_configuration.h" +#include "kis_colorspace.h" +#include "koffice_export.h" + +class KisColorSpace; +class KisPreviewDialog; +class KisProgressDisplayInterface; +class KisFilterConfigWidget; +class QWidget; + +/** + * Basic interface of a Krita filter. + */ +class KRITACORE_EXPORT KisFilter : public KisProgressSubject, public KShared { + Q_OBJECT +public: + + /** + * Construct a Krita filter + */ + KisFilter(const KisID& id, const QString & category, const QString & entry); + virtual ~KisFilter() {} + +public: + + virtual void setProgressDisplay(KisProgressDisplayInterface * progressDisplay); + + virtual void process(KisPaintDeviceSP src, KisPaintDeviceSP dst, KisFilterConfiguration*, const QRect&) = 0; + +public: + /** + * @return a new configuration derived from the widget. If the widget is NULL or not the correct type, + * a default configuration object will be returned + */ + virtual KisFilterConfiguration * configuration(QWidget*); + + /** + * @return a default configuration object + * Normally this doesn't need to be overriden + */ + virtual KisFilterConfiguration * configuration(); + + /** + * If true, this filter can be used in painting tools as a paint operation + */ + virtual bool supportsPainting() { return false; }; + + /// This filter can be displayed in a preview dialog + virtual bool supportsPreview() { return false; }; + + /// This filter can be used in adjustment layers + // XXX: This uses supportsPreview() for backwards compatibility + virtual bool supportsAdjustmentLayers() { return supportsPreview(); }; + + /** + * Return a list of default configuration to demonstrates the use of the filter + * @return a list with a null element if the filter do not use a configuration + */ + virtual std::list<KisFilterConfiguration*> listOfExamplesConfiguration(KisPaintDeviceSP ) + { std::list<KisFilterConfiguration*> list; list.insert(list.begin(), 0); return list; } + + /** + * Can this filter work incrementally when painting, or do we need to work + * on the state as it was before painting started. The former is faster. + */ + virtual bool supportsIncrementalPainting() { return true; }; + + /** + * This filter supports cutting up the work area and filtering + * each chunk in a separate thread. Filters that need access to the + * whole area for correct computations should return false. + */ + virtual bool supportsThreading() { return true; }; + + /** + * Used when threading is used -- the overlap margin is passed to the + * filter to use to compute pixels, but the margin is not pasted into the + * resulting image. + */ + virtual int overlapMarginNeeded(KisFilterConfiguration* = 0) const { return 0; }; + + /** + * Similar to overlapMarginNeeded: some filters will alter a lot of pixels that are + * near to each other at the same time. So when you changed a single rectangle + * in a device, the actual rectangle that will feel the influence of this change + * might be bigger. Use this function to detirmine that rect. + * The default implementation makes a guess using overlapMarginNeeded. + */ + virtual QRect enlargeRect(QRect rect, KisFilterConfiguration* = 0) const; + + /** + * Determine the colorspace independence of this filter. + * @see ColorSpaceIndependence + * + * @return the degree of independence + */ + virtual ColorSpaceIndependence colorSpaceIndependence() { return TO_RGBA8; }; + + /** + * Determine if this filter can work with this colorSpace. For instance, some + * colorspaces don't depend on lcms, and cannot do certain tasks. The colorsfilters + * are problems here. + * BSAR: I'm still not convinced that this is the right approach. I think that every + * colorspace should implement the api fully; and that the filter should simply call + * that api. After all, you don't need lcms to desaturate. + * + * @param colorsSpace + */ + virtual bool workWith(KisColorSpace*) { return true; } + + virtual void enableProgress(); + virtual void disableProgress(); + + bool autoUpdate(); + + // Unique identification for this filter + inline const KisID id() const { return m_id; }; + // Which submenu in the filters menu does filter want to go? + + inline QString menuCategory() const { return m_category; }; + // The i18n'ed string this filter wants to show itself in the menu + + inline QString menuEntry() const { return m_entry; }; + + /** + * Create the configuration widget for this filter. + * + * @param parent the Qt owner widget of this widget + * @param dev the paintdevice this filter will act on + * @return NULL if the filter does not use user-settable configuration settings. + * else return a pointer to the new configuration widget + */ + virtual KisFilterConfigWidget * createConfigurationWidget(QWidget * parent, KisPaintDeviceSP dev); + + virtual void cancel() { m_cancelRequested = true; } + + virtual void setAutoUpdate(bool set); + bool progressEnabled() const { return m_progressEnabled; } + inline bool cancelRequested() const { return m_progressEnabled && m_cancelRequested; } + +protected slots: + + // Convenience functions for progress display. + void setProgressTotalSteps(Q_INT32 totalSteps); + void setProgress(Q_INT32 progress); + void incProgress(); + void setProgressStage(const QString& stage, Q_INT32 progress); + void setProgressDone(); + inline Q_INT32 progress() { return m_progressSteps; } +private: + bool m_cancelRequested; + bool m_progressEnabled; + bool m_autoUpdate; + +protected: + Q_INT32 m_progressTotalSteps; + Q_INT32 m_lastProgressPerCent; + Q_INT32 m_progressSteps; + + KisID m_id; + KisProgressDisplayInterface * m_progressDisplay; + QString m_category; // The category in the filter menu this filter fits + QString m_entry; // the i18n'ed accelerated menu text + +}; + + +#endif diff --git a/krita/core/kis_filter_config_widget.cc b/krita/core/kis_filter_config_widget.cc new file mode 100644 index 00000000..e694189d --- /dev/null +++ b/krita/core/kis_filter_config_widget.cc @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2004 Boudewijn Rempt (boud@valdyas.org) + * + * 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_filter_config_widget.h" + + +KisFilterConfigWidget::KisFilterConfigWidget(QWidget * parent, const char * name, WFlags f) + : QWidget(parent, name, f) +{ +} + +KisFilterConfigWidget::~KisFilterConfigWidget() +{ +} + +#include "kis_filter_config_widget.moc" diff --git a/krita/core/kis_filter_config_widget.h b/krita/core/kis_filter_config_widget.h new file mode 100644 index 00000000..4d93f60b --- /dev/null +++ b/krita/core/kis_filter_config_widget.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2004 Cyrille Berger <cberger@cberger.net> + * + * 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_FILTER_CONFIG_WIDGET_H_ +#define _KIS_FILTER_CONFIG_WIDGET_H_ + +#include <qwidget.h> +#include "kis_filter_configuration.h" + +/** + * Empty base class. Filters can build their own configuration widgets that + * inherit this class. The configuration widget can emit sigPleaseUpdatePreview + * when it wants the preview in the filter dialog to be updated. + */ +class KisFilterConfigWidget : public QWidget { + + Q_OBJECT + +public: + + KisFilterConfigWidget(QWidget * parent, const char * name = 0, WFlags f = 0 ); + virtual ~KisFilterConfigWidget(); + + virtual void setConfiguration(KisFilterConfiguration * config) = 0; + +signals: + + /** + * Subclasses should emit this signal whenever the preview should be + * be recalculated. + */ + void sigPleaseUpdatePreview(); +}; + +#endif diff --git a/krita/core/kis_filter_configuration.cc b/krita/core/kis_filter_configuration.cc new file mode 100644 index 00000000..999edc5c --- /dev/null +++ b/krita/core/kis_filter_configuration.cc @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2006 Boudewijn Rempt <boud@valdyas.org> + * + * 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_filter.h" + +#include <kdebug.h> +#include <qdom.h> +#include <qstring.h> + +#include "kis_filter_registry.h" +#include "kis_transaction.h" +#include "kis_undo_adapter.h" +#include "kis_painter.h" +#include "kis_selection.h" +#include "kis_id.h" +#include "kis_canvas_subject.h" +#include "kis_progress_display_interface.h" +#include "kis_types.h" +#include "kis_filter_config_widget.h" + + +KisFilterConfiguration::KisFilterConfiguration(const KisFilterConfiguration & rhs) +{ + m_name = rhs.m_name; + m_version = rhs.m_version; + m_properties = rhs.m_properties; +} + +void KisFilterConfiguration::fromXML(const QString & s ) +{ + m_properties.clear(); + + QDomDocument doc; + doc.setContent( s ); + QDomElement e = doc.documentElement(); + QDomNode n = e.firstChild(); + + m_name = e.attribute("name"); + m_version = e.attribute("version").toInt(); + + while (!n.isNull()) { + // We don't nest elements in filter configuration. For now... + QDomElement e = n.toElement(); + QString name; + QString type; + QString value; + + if (!e.isNull()) { + if (e.tagName() == "property") { + name = e.attribute("name"); + type = e.attribute("type"); + value = e.text(); + // XXX Convert the variant pro-actively to the right type? + m_properties[name] = QVariant(value); + } + } + n = n.nextSibling(); + } + //dump(); +} + +QString KisFilterConfiguration::toString() +{ + QDomDocument doc = QDomDocument("filterconfig"); + QDomElement root = doc.createElement( "filterconfig" ); + root.setAttribute( "name", m_name ); + root.setAttribute( "version", m_version ); + + doc.appendChild( root ); + + QMap<QString, QVariant>::Iterator it; + for ( it = m_properties.begin(); it != m_properties.end(); ++it ) { + QDomElement e = doc.createElement( "property" ); + e.setAttribute( "name", it.key().latin1() ); + QVariant v = it.data(); + e.setAttribute( "type", v.typeName() ); + QString s = v.asString(); + QDomText text = doc.createCDATASection(v.asString() ); // XXX: Unittest this! + e.appendChild(text); + root.appendChild(e); + } + + return doc.toString(); +} + +const QString & KisFilterConfiguration::name() const +{ + return m_name; +} + +Q_INT32 KisFilterConfiguration::version() const +{ + return m_version; +} + +void KisFilterConfiguration::setProperty(const QString & name, const QVariant & value) +{ + if ( m_properties.find( name ) == m_properties.end() ) { + m_properties.insert( name, value ); + } + else { + m_properties[name] = value; + } +} + +bool KisFilterConfiguration::getProperty(const QString & name, QVariant & value) +{ + if ( m_properties.find( name ) == m_properties.end() ) { + return false; + } + else { + value = m_properties[name]; + return true; + } +} + +QVariant KisFilterConfiguration::getProperty(const QString & name) +{ + if ( m_properties.find( name ) == m_properties.end() ) { + return QVariant(); + } + else { + return m_properties[name]; + } +} + + +int KisFilterConfiguration::getInt(const QString & name, int def) +{ + QVariant v = getProperty(name); + if (v.isValid()) + return v.asInt(); + else + return def; + +} + +double KisFilterConfiguration::getDouble(const QString & name, double def) +{ + QVariant v = getProperty(name); + if (v.isValid()) + return v.asDouble(); + else + return def; +} + +bool KisFilterConfiguration::getBool(const QString & name, bool def) +{ + QVariant v = getProperty(name); + if (v.isValid()) + return v.asBool(); + else + return def; +} + +QString KisFilterConfiguration::getString(const QString & name, QString def) +{ + QVariant v = getProperty(name); + if (v.isValid()) + return v.asString(); + else + return def; +} + +void KisFilterConfiguration::dump() +{ + QMap<QString, QVariant>::Iterator it; + for ( it = m_properties.begin(); it != m_properties.end(); ++it ) { + } + +} diff --git a/krita/core/kis_filter_configuration.h b/krita/core/kis_filter_configuration.h new file mode 100644 index 00000000..10f9a6f4 --- /dev/null +++ b/krita/core/kis_filter_configuration.h @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2006 Boudewijn Rempt <boud@valdyas.org> + * + * 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_FILTER_CONFIGURATION_H_ +#define _KIS_FILTER_CONFIGURATION_H_ + +#include <qstring.h> +#include <qmap.h> +#include <qvariant.h> +#include <kdebug.h> +#include "koffice_export.h" + +class KisPreviewDialog; +class KisProgressDisplayInterface; +class KisFilterConfigWidget; +class QWidget; + +/** + * A KisFilterConfiguration is the serializable representation of + * the filter parameters. Filters can subclass this class to implement + * direct accessors to properties, but properties not in the map will + * not be serialized. + */ +class KRITACORE_EXPORT KisFilterConfiguration { + +public: + + /** + * Create a new filter config. + */ + KisFilterConfiguration(const QString & name, Q_INT32 version) + : m_name(name) + , m_version(version) {} + + /** + * Deep copy the filter configFile + */ + KisFilterConfiguration(const KisFilterConfiguration & rhs); + +public: + + /** + * Fill the filter configuration object from the XML encoded representation in s. + */ + virtual void fromXML(const QString &); + + /** + * Create a serialized version of this filter config + */ + virtual QString toString(); + + /** + * Get the unique, language independent name of the filter. + */ + const QString & name() const; + + /** + * Get the version of the filter that has created this config + */ + Q_INT32 version() const; + + /** + * Set the property with name to value. + */ + virtual void setProperty(const QString & name, const QVariant & value); + + /** + * Set value to the value associated with property name + * @return false if the specified property did not exist. + */ + virtual bool getProperty(const QString & name, QVariant & value); + + virtual QVariant getProperty(const QString & name); + + int getInt(const QString & name, int def = 0); + double getDouble(const QString & name, double def = 0.0); + bool getBool(const QString & name, bool def = false); + QString getString(const QString & name, QString def = QString::null); + +private: + void dump(); + +protected: + + QString m_name; + Q_INT32 m_version; + QMap<QString, QVariant> m_properties; + +}; + +#endif // _KIS_FILTER_CONFIGURATION_H_ diff --git a/krita/core/kis_filter_registry.cc b/krita/core/kis_filter_registry.cc new file mode 100644 index 00000000..1b453878 --- /dev/null +++ b/krita/core/kis_filter_registry.cc @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2003 Patrick Julien <freak@codepimps.org> + * Copyright (c) 2004 Cyrille Berger <cberger@cberger.net> + * + * 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 <qstring.h> +#include <qptrlist.h> + +#include <kaction.h> +#include <kdebug.h> +#include <klocale.h> +#include <kparts/plugin.h> +#include <kservice.h> +#include <ktrader.h> +#include <kparts/componentfactory.h> +#include "kis_debug_areas.h" +#include <math.h> +#include "kis_types.h" +#include "kis_filter_registry.h" +#include "kis_paint_device.h" +#include "kis_filter.h" + +KisFilterRegistry *KisFilterRegistry::m_singleton = 0; + +KisFilterRegistry::KisFilterRegistry() +{ + Q_ASSERT(KisFilterRegistry::m_singleton == 0); + KisFilterRegistry::m_singleton = this; + + KTrader::OfferList offers = KTrader::self()->query(QString::fromLatin1("Krita/Filter"), + QString::fromLatin1("(Type == 'Service') and " + "([X-Krita-Version] == 2)")); + + KTrader::OfferList::ConstIterator iter; + + for(iter = offers.begin(); iter != offers.end(); ++iter) + { + KService::Ptr service = *iter; + int errCode = 0; + KParts::Plugin* plugin = + KParts::ComponentFactory::createInstanceFromService<KParts::Plugin> ( service, this, 0, QStringList(), &errCode); + if ( plugin ) + kdDebug(DBG_AREA_PLUGINS) << "found plugin " << service->property("Name").toString() << "\n"; + else { + kdDebug(41006) << "found plugin " << service->property("Name").toString() << ", " << errCode << "\n"; + if( errCode == KParts::ComponentFactory::ErrNoLibrary) + { + kdWarning(41006) << " Error loading plugin was : ErrNoLibrary " << KLibLoader::self()->lastErrorMessage() << endl; + } + } + + } + +} + +KisFilterRegistry::~KisFilterRegistry() +{ +} + +KisFilterRegistry* KisFilterRegistry::instance() +{ + if(KisFilterRegistry::m_singleton == 0) + { + KisFilterRegistry::m_singleton = new KisFilterRegistry(); + } + return KisFilterRegistry::m_singleton; +} + +#include "kis_filter_registry.moc" diff --git a/krita/core/kis_filter_registry.h b/krita/core/kis_filter_registry.h new file mode 100644 index 00000000..89cf1341 --- /dev/null +++ b/krita/core/kis_filter_registry.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2003 Patrick Julien <freak@codepimps.org> + * Copyright (c) 2004 Cyrille Berger <cberger@cberger.net> + * + * 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_FILTER_REGISTRY_H_ +#define KIS_FILTER_REGISTRY_H_ + +#include <qobject.h> + +#include "kis_types.h" +#include "kis_generic_registry.h" + +#include <koffice_export.h> + +class QString; +class QStringList; + +class KRITACORE_EXPORT KisFilterRegistry : public QObject, public KisGenericRegistry<KisFilterSP> +{ + + Q_OBJECT + +public: + virtual ~KisFilterRegistry(); + + static KisFilterRegistry* instance(); + +private: + KisFilterRegistry(); + KisFilterRegistry(const KisFilterRegistry&); + KisFilterRegistry operator=(const KisFilterRegistry&); + +private: + static KisFilterRegistry *m_singleton; +}; + +#endif // KIS_FILTERSPACE_REGISTRY_H_ diff --git a/krita/core/kis_filter_strategy.cc b/krita/core/kis_filter_strategy.cc new file mode 100644 index 00000000..78dbcd41 --- /dev/null +++ b/krita/core/kis_filter_strategy.cc @@ -0,0 +1,192 @@ +/* + * Copyright (c) 2004 Michael Thaler <michael.thaler@physik.tu-muenchen.de> + * Copyright (c) 2005 Casper Boemann <cbr@boemann.dk> + * + * 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 <kdebug.h> +#include <klocale.h> +#include "kis_debug_areas.h" +#include "kis_filter_strategy.h" +#include <math.h> + +double KisHermiteFilterStrategy::valueAt(double t) const { + /* f(t) = 2|t|^3 - 3|t|^2 + 1, -1 <= t <= 1 */ + if(t < 0.0) t = -t; + if(t < 1.0) return((2.0 * t - 3.0) * t * t + 1.0); + return(0.0); +} + +Q_UINT32 KisHermiteFilterStrategy::intValueAt(Q_INT32 t) const { + /* f(t) = 2|t|^3 - 3|t|^2 + 1, -1 <= t <= 1 */ + if(t < 0) t = -t; + if(t < 256) + { + t =(2 * t - 3*256) * t * t +(256<<16); + + //go from .24 fixed point to .8 fixedpoint (hack only works with positve numbers, which it is) + t = (t + 0x8000) >> 16; + + // go from .8 fixed point to 8bitscale. ie t = (t*255)/256; + if(t >= 128) + return t - 1; + return t; + } + return(0); +} + +double KisCubicFilterStrategy::valueAt(double x) const { + if (x < -2.0) + return(0.0); + if (x < -1.0) + return((2.0+x)*(2.0+x)*(2.0+x)/6.0); + if (x < 0.0) + return((4.0+x*x*(-6.0-3.0*x))/6.0); + if (x < 1.0) + return((4.0+x*x*(-6.0+3.0*x))/6.0); + if (x < 2.0) + return((2.0-x)*(2.0-x)*(2.0-x)/6.0); + return(0.0); +} + +Q_UINT32 KisCubicFilterStrategy::intValueAt(Q_INT32 x) const { + if (x < 2) + return 0; + if (x < -1) + return (2 + x) * (2 + x) * ( 2 + x) / 6; + if ( x < 0) + return (4 + x * x * ( -6 - 3 * x)) / 6; + if (x < 1) + return (4 + x * x * ( -6 + 3 * x)) / 6; + if (x < 2) + return (2 - x) * ( 2 - x) * (2 - x) / 6; + return 0; +} + +double KisBoxFilterStrategy::valueAt(double t) const { + if((t > -0.5) && (t <= 0.5)) return(1.0); + return(0.0); +} + +Q_UINT32 KisBoxFilterStrategy::intValueAt(Q_INT32 t) const { + /* f(t) = 1, -0.5 < t <= 0.5 */ + if((t > -128) && (t <= 128)) + return 255; + return 0; +} + +double KisTriangleFilterStrategy::valueAt(double t) const { + if(t < 0.0) t = -t; + if(t < 1.0) return(1.0 - t); + return(0.0); +} + +Q_UINT32 KisTriangleFilterStrategy::intValueAt(Q_INT32 t) const { + /* f(t) = |t|, -1 <= t <= 1 */ + if(t < 0) t = -t; + if(t < 256) + { + // calc 256-1 but also go from .8 fixed point to 8bitscale. ie t = (t*255)/256; ie: if(t>=128) return t-1; + if(t>=128) return 256 - t; + return 255 - t; + } + return(0); +} + + +double KisBellFilterStrategy::valueAt(double t) const { + if(t < 0) t = -t; + if(t < .5) return(.75 - (t * t)); + if(t < 1.5) { + t = (t - 1.5); + return(.5 * (t * t)); + } + return(0.0); +} + +double KisBSplineFilterStrategy::valueAt(double t) const { + double tt; + + if(t < 0) t = -t; + if(t < 1) { + tt = t * t; + return((.5 * tt * t) - tt + (2.0 / 3.0)); + } else if(t < 2) { + t = 2 - t; + return((1.0 / 6.0) * (t * t * t)); + } + return(0.0); +} + +double KisLanczos3FilterStrategy::valueAt(double t) const { + if(t < 0) t = -t; + if(t < 3.0) return(sinc(t) * sinc(t/3.0)); + return(0.0); +} + +double KisLanczos3FilterStrategy::sinc(double x) const { + const double pi=3.1415926535897932385; + x *= pi; + if(x != 0) return(sin(x) / x); + return(1.0); +} + +double KisMitchellFilterStrategy::valueAt(double t) const { + const double B=1.0/3.0; + const double C=1.0/3.0; + double tt; + + tt = t * t; + if(t < 0) t = -t; + if(t < 1.0) { + t = (((12.0 - 9.0 * B - 6.0 * C) * (t * tt)) + ((-18.0 + 12.0 * B + 6.0 * C) * tt) + (6.0 - 2 * B)); + return(t / 6.0); + } else if(t < 2.0) { + t = (((-1.0 * B - 6.0 * C) * (t * tt)) + ((6.0 * B + 30.0 * C) * tt) + ((-12.0 * B - 48.0 * C) * t) + (8.0 * B + 24 * C)); + return(t / 6.0); + } + return(0.0); +} + +KisFilterStrategyRegistry *KisFilterStrategyRegistry::m_singleton = 0; + +KisFilterStrategyRegistry::KisFilterStrategyRegistry() +{ + Q_ASSERT(KisFilterStrategyRegistry::m_singleton == 0); + KisFilterStrategyRegistry::m_singleton = this; +} + +KisFilterStrategyRegistry::~KisFilterStrategyRegistry() +{ +} + +KisFilterStrategyRegistry* KisFilterStrategyRegistry::instance() +{ + if(KisFilterStrategyRegistry::m_singleton == 0) + { + KisFilterStrategyRegistry::m_singleton = new KisFilterStrategyRegistry(); + Q_CHECK_PTR(KisFilterStrategyRegistry::m_singleton); + m_singleton->add(new KisHermiteFilterStrategy); + m_singleton->add(new KisBoxFilterStrategy); + m_singleton->add(new KisTriangleFilterStrategy); + m_singleton->add(new KisBellFilterStrategy); + m_singleton->add(new KisBSplineFilterStrategy); +// m_singleton->add(new KisLanczos3FilterStrategy); + m_singleton->add(new KisMitchellFilterStrategy); +// m_singleton->add(new KisCubicFilterStrategy); + } + return KisFilterStrategyRegistry::m_singleton; +} + diff --git a/krita/core/kis_filter_strategy.h b/krita/core/kis_filter_strategy.h new file mode 100644 index 00000000..91385239 --- /dev/null +++ b/krita/core/kis_filter_strategy.h @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2004 Michael Thaler <michael.thaler@physik.tu-muenchen.de> + * Copyright (c) 2005 Casper Boemann <cbr@boemann.dk> + * + * 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_FILTER_STRATEGY_H_ +#define KIS_FILTER_STRATEGY_H_ + +#include <klocale.h> + +#include "kis_types.h" +#include "kis_generic_registry.h" +#include "kis_id.h" + +class KisFilterStrategy +{ + public: + KisFilterStrategy(KisID id) : m_id(id) {} + virtual ~KisFilterStrategy() {} + + KisID id() {return m_id;}; + virtual double valueAt(double /*t*/) const {return 0;}; + virtual Q_UINT32 intValueAt(Q_INT32 t) const {return Q_UINT32(255*valueAt(t/256.0));}; + double support() { return supportVal;}; + Q_UINT32 intSupport() { return intSupportVal;}; + virtual bool boxSpecial() { return false;}; + protected: + double supportVal; + Q_UINT32 intSupportVal; + KisID m_id; +}; + +class KisHermiteFilterStrategy : public KisFilterStrategy +{ + public: + KisHermiteFilterStrategy() : KisFilterStrategy(KisID("Hermite", i18n("Hermite"))) + {supportVal = 1.0; intSupportVal = 256;} + virtual ~KisHermiteFilterStrategy() {} + + virtual Q_UINT32 intValueAt(Q_INT32 t) const; + virtual double valueAt(double t) const; +}; + +class KisCubicFilterStrategy : public KisFilterStrategy +{ + public: + KisCubicFilterStrategy() : KisFilterStrategy(KisID("Bicubic", i18n("Bicubic"))) + {supportVal = 1.0; intSupportVal = 256;} + virtual ~KisCubicFilterStrategy() {} + + virtual Q_UINT32 intValueAt(Q_INT32 t) const; + virtual double valueAt(double t) const; +}; + +class KisBoxFilterStrategy : public KisFilterStrategy +{ + public: + KisBoxFilterStrategy() : KisFilterStrategy(KisID("Box", i18n("Box"))) + {supportVal = 0.5; intSupportVal = 128;} + virtual ~KisBoxFilterStrategy() {} + + virtual Q_UINT32 intValueAt(Q_INT32 t) const; + virtual double valueAt(double t) const; + virtual bool boxSpecial() { return true;}; +}; + +class KisTriangleFilterStrategy : public KisFilterStrategy +{ + public: + KisTriangleFilterStrategy() : KisFilterStrategy(KisID("Triangle", i18n("Triangle aka (bi)linear"))) + {supportVal = 1.0; intSupportVal = 256;} + virtual ~KisTriangleFilterStrategy() {} + + virtual Q_UINT32 intValueAt(Q_INT32 t) const; + virtual double valueAt(double t) const; +}; + +class KisBellFilterStrategy : public KisFilterStrategy +{ + public: + KisBellFilterStrategy() : KisFilterStrategy(KisID("Bell", i18n("Bell"))) + {supportVal = 1.5; intSupportVal = 128+256;} + virtual ~KisBellFilterStrategy() {} + + virtual double valueAt(double t) const; +}; + +class KisBSplineFilterStrategy : public KisFilterStrategy +{ + public: + KisBSplineFilterStrategy() : KisFilterStrategy(KisID("BSpline", i18n("BSpline"))) + {supportVal = 2.0; intSupportVal = 512;} + virtual ~KisBSplineFilterStrategy() {} + + virtual double valueAt(double t) const; +}; + +class KisLanczos3FilterStrategy : public KisFilterStrategy +{ + public: + KisLanczos3FilterStrategy() : KisFilterStrategy(KisID("Lanczos3", i18n("Lanczos3"))) + {supportVal = 3.0; intSupportVal = 768;} + virtual ~KisLanczos3FilterStrategy() {} + + virtual double valueAt(double t) const; + private: + double sinc(double x) const; +}; + +class KisMitchellFilterStrategy : public KisFilterStrategy +{ + public: + KisMitchellFilterStrategy() : KisFilterStrategy(KisID("Mitchell", i18n("Mitchell"))) + {supportVal = 2.0; intSupportVal = 256;} + virtual ~KisMitchellFilterStrategy() {} + + virtual double valueAt(double t) const; +}; + +class KisFilterStrategyRegistry : public KisGenericRegistry<KisFilterStrategy *> +{ +public: + virtual ~KisFilterStrategyRegistry(); + + static KisFilterStrategyRegistry* instance(); + +private: + KisFilterStrategyRegistry(); + KisFilterStrategyRegistry(const KisFilterStrategyRegistry&); + KisFilterStrategyRegistry operator=(const KisFilterStrategyRegistry&); + +private: + static KisFilterStrategyRegistry *m_singleton; +}; + +#endif // KIS_FILTER_STRATEGY_H_ diff --git a/krita/core/kis_gradient.cc b/krita/core/kis_gradient.cc new file mode 100644 index 00000000..33714461 --- /dev/null +++ b/krita/core/kis_gradient.cc @@ -0,0 +1,639 @@ +/* + * kis_gradient.cc - part of Krayon + * + * Copyright (c) 2000 Matthias Elter <elter@kde.org> + * 2001 John Califf + * 2004 Boudewijn Rempt <boud@valdyas.org> + * 2004 Adrian Page <adrian@pagenet.plus.com> + * 2004 Sven Langkamp <longamp@reallygood.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 <cfloat> +#include <cmath> + +#include <qimage.h> +#include <qtextstream.h> +#include <qfile.h> + +#include <koColor.h> +#include <kogradientmanager.h> + +#include <kdebug.h> +#include <klocale.h> + +#include "kis_gradient.h" + +#define PREVIEW_WIDTH 64 +#define PREVIEW_HEIGHT 64 + +KisGradientSegment::RGBColorInterpolationStrategy *KisGradientSegment::RGBColorInterpolationStrategy::m_instance = 0; +KisGradientSegment::HSVCWColorInterpolationStrategy *KisGradientSegment::HSVCWColorInterpolationStrategy::m_instance = 0; +KisGradientSegment::HSVCCWColorInterpolationStrategy *KisGradientSegment::HSVCCWColorInterpolationStrategy::m_instance = 0; + +KisGradientSegment::LinearInterpolationStrategy *KisGradientSegment::LinearInterpolationStrategy::m_instance = 0; +KisGradientSegment::CurvedInterpolationStrategy *KisGradientSegment::CurvedInterpolationStrategy::m_instance = 0; +KisGradientSegment::SineInterpolationStrategy *KisGradientSegment::SineInterpolationStrategy::m_instance = 0; +KisGradientSegment::SphereIncreasingInterpolationStrategy *KisGradientSegment::SphereIncreasingInterpolationStrategy::m_instance = 0; +KisGradientSegment::SphereDecreasingInterpolationStrategy *KisGradientSegment::SphereDecreasingInterpolationStrategy::m_instance = 0; + +KisGradient::KisGradient(const QString& file) : super(file) +{ +} + +KisGradient::~KisGradient() +{ + for (uint i = 0; i < m_segments.count(); i++) { + delete m_segments[i]; + m_segments[i] = 0; + } +} + +bool KisGradient::load() +{ + return init(); +} + +bool KisGradient::save() +{ + return false; +} + +QImage KisGradient::img() +{ + return m_img; +} + +bool KisGradient::init() +{ + KoGradientManager gradLoader; + KoGradient* grad = gradLoader.loadGradient(filename()); + + if( !grad ) + return false; + + m_segments.clear(); + + if( grad->colorStops.count() > 1 ) { + KoColorStop *colstop; + for(colstop = grad->colorStops.first(); colstop; colstop = grad->colorStops.next()) { + KoColorStop *colstopNext = grad->colorStops.next(); + + if(colstopNext) { + KoColor leftRgb((int)(colstop->color1 * 255 + 0.5), (int)(colstop->color2 * 255 + 0.5), (int)(colstop->color3 * 255 + 0.5)); + KoColor rightRgb((int)(colstopNext->color1 * 255 + 0.5), (int)(colstopNext->color2 * 255 + 0.5), (int)(colstopNext->color3 * 255 + 0.5)); + + double midp = colstop->midpoint; + midp = colstop->offset + ((colstopNext->offset - colstop->offset) * midp); + + Color leftColor(leftRgb.color(), colstop->opacity); + Color rightColor(rightRgb.color(), colstopNext->opacity); + + KisGradientSegment *segment = new KisGradientSegment(colstop->interpolation, colstop->colorType, colstop->offset, midp, colstopNext->offset, leftColor, rightColor); + Q_CHECK_PTR(segment); + + if ( !segment->isValid() ) { + delete segment; + return false; + } + + m_segments.push_back(segment); + grad->colorStops.prev(); + } + else { + grad->colorStops.prev(); + break; + } + } + } + else + return false; + + if (!m_segments.isEmpty()) { + m_img = generatePreview(PREVIEW_WIDTH, PREVIEW_HEIGHT); + setValid(true); + return true; + } + else { + return false; + } +} + +void KisGradient::setImage(const QImage& img) +{ + m_img = img; + m_img.detach(); + + setValid(true); +} + +KisGradientSegment *KisGradient::segmentAt(double t) const +{ + Q_ASSERT(t >= 0 || t <= 1); + Q_ASSERT(!m_segments.empty()); + + for(QValueVector<KisGradientSegment *>::const_iterator it = m_segments.begin(); it!= m_segments.end(); ++it) + { + if (t > (*it)->startOffset() - DBL_EPSILON && t < (*it)->endOffset() + DBL_EPSILON) { + return *it; + } + } + + return 0; +} + +void KisGradient::colorAt(double t, QColor *color, Q_UINT8 *opacity) const +{ + const KisGradientSegment *segment = segmentAt(t); + Q_ASSERT(segment != 0); + + if (segment) { + Color col = segment->colorAt(t); + *color = col.color(); + *opacity = static_cast<Q_UINT8>(col.alpha() * OPACITY_OPAQUE + 0.5); + } +} + +QImage KisGradient::generatePreview(int width, int height) const +{ + QImage img(width, height, 32); + + for (int y = 0; y < img.height(); y++) { + for (int x = 0; x < img.width(); x++) { + + int backgroundRed = 128 + 63 * ((x / 4 + y / 4) % 2); + int backgroundGreen = backgroundRed; + int backgroundBlue = backgroundRed; + + QColor color; + Q_UINT8 opacity; + double t = static_cast<double>(x) / (img.width() - 1); + + colorAt(t, &color, &opacity); + + double alpha = static_cast<double>(opacity) / OPACITY_OPAQUE; + + int red = static_cast<int>((1 - alpha) * backgroundRed + alpha * color.red() + 0.5); + int green = static_cast<int>((1 - alpha) * backgroundGreen + alpha * color.green() + 0.5); + int blue = static_cast<int>((1 - alpha) * backgroundBlue + alpha * color.blue() + 0.5); + + img.setPixel(x, y, qRgb(red, green, blue)); + } + } + + return img; +} + +KisGradientSegment::KisGradientSegment(int interpolationType, int colorInterpolationType, double startOffset, double middleOffset, double endOffset, const Color& startColor, const Color& endColor) +{ + m_interpolator = 0; + + switch (interpolationType) { + case INTERP_LINEAR: + m_interpolator = LinearInterpolationStrategy::instance(); + break; + case INTERP_CURVED: + m_interpolator = CurvedInterpolationStrategy::instance(); + break; + case INTERP_SINE: + m_interpolator = SineInterpolationStrategy::instance(); + break; + case INTERP_SPHERE_INCREASING: + m_interpolator = SphereIncreasingInterpolationStrategy::instance(); + break; + case INTERP_SPHERE_DECREASING: + m_interpolator = SphereDecreasingInterpolationStrategy::instance(); + break; + } + + m_colorInterpolator = 0; + + switch (colorInterpolationType) { + case COLOR_INTERP_RGB: + m_colorInterpolator = RGBColorInterpolationStrategy::instance(); + break; + case COLOR_INTERP_HSV_CCW: + m_colorInterpolator = HSVCCWColorInterpolationStrategy::instance(); + break; + case COLOR_INTERP_HSV_CW: + m_colorInterpolator = HSVCWColorInterpolationStrategy::instance(); + break; + } + + if (startOffset < DBL_EPSILON) { + m_startOffset = 0; + } + else + if (startOffset > 1 - DBL_EPSILON) { + m_startOffset = 1; + } + else { + m_startOffset = startOffset; + } + + if (middleOffset < m_startOffset + DBL_EPSILON) { + m_middleOffset = m_startOffset; + } + else + if (middleOffset > 1 - DBL_EPSILON) { + m_middleOffset = 1; + } + else { + m_middleOffset = middleOffset; + } + + if (endOffset < m_middleOffset + DBL_EPSILON) { + m_endOffset = m_middleOffset; + } + else + if (endOffset > 1 - DBL_EPSILON) { + m_endOffset = 1; + } + else { + m_endOffset = endOffset; + } + + m_length = m_endOffset - m_startOffset; + + if (m_length < DBL_EPSILON) { + m_middleT = 0.5; + } + else { + m_middleT = (m_middleOffset - m_startOffset) / m_length; + } + + m_startColor = startColor; + m_endColor = endColor; +} + +const Color& KisGradientSegment::startColor() const +{ + return m_startColor; +} + +const Color& KisGradientSegment::endColor() const +{ + return m_endColor; +} + +double KisGradientSegment::startOffset() const +{ + return m_startOffset; +} + +double KisGradientSegment::middleOffset() const +{ + return m_middleOffset; +} + +double KisGradientSegment::endOffset() const +{ + return m_endOffset; +} + +void KisGradientSegment::setStartOffset(double t) +{ + m_startOffset = t; + m_length = m_endOffset - m_startOffset; + + if (m_length < DBL_EPSILON) { + m_middleT = 0.5; + } + else { + m_middleT = (m_middleOffset - m_startOffset) / m_length; + } +} +void KisGradientSegment::setMiddleOffset(double t) +{ + m_middleOffset = t; + + if (m_length < DBL_EPSILON) { + m_middleT = 0.5; + } + else { + m_middleT = (m_middleOffset - m_startOffset) / m_length; + } +} + +void KisGradientSegment::setEndOffset(double t) +{ + m_endOffset = t; + m_length = m_endOffset - m_startOffset; + + if (m_length < DBL_EPSILON) { + m_middleT = 0.5; + } + else { + m_middleT = (m_middleOffset - m_startOffset) / m_length; + } +} + +int KisGradientSegment::interpolation() const +{ + return m_interpolator->type(); +} + +void KisGradientSegment::setInterpolation(int interpolationType) +{ + switch (interpolationType) { + case INTERP_LINEAR: + m_interpolator = LinearInterpolationStrategy::instance(); + break; + case INTERP_CURVED: + m_interpolator = CurvedInterpolationStrategy::instance(); + break; + case INTERP_SINE: + m_interpolator = SineInterpolationStrategy::instance(); + break; + case INTERP_SPHERE_INCREASING: + m_interpolator = SphereIncreasingInterpolationStrategy::instance(); + break; + case INTERP_SPHERE_DECREASING: + m_interpolator = SphereDecreasingInterpolationStrategy::instance(); + break; + } +} + +int KisGradientSegment::colorInterpolation() const +{ + return m_colorInterpolator->type(); +} + +void KisGradientSegment::setColorInterpolation(int colorInterpolationType) +{ + switch (colorInterpolationType) { + case COLOR_INTERP_RGB: + m_colorInterpolator = RGBColorInterpolationStrategy::instance(); + break; + case COLOR_INTERP_HSV_CCW: + m_colorInterpolator = HSVCCWColorInterpolationStrategy::instance(); + break; + case COLOR_INTERP_HSV_CW: + m_colorInterpolator = HSVCWColorInterpolationStrategy::instance(); + break; + } +} + +Color KisGradientSegment::colorAt(double t) const +{ + Q_ASSERT(t > m_startOffset - DBL_EPSILON && t < m_endOffset + DBL_EPSILON); + + double segmentT; + + if (m_length < DBL_EPSILON) { + segmentT = 0.5; + } + else { + segmentT = (t - m_startOffset) / m_length; + } + + double colorT = m_interpolator->valueAt(segmentT, m_middleT); + + Color color = m_colorInterpolator->colorAt(colorT, m_startColor, m_endColor); + + return color; +} + +bool KisGradientSegment::isValid() const +{ + if (m_interpolator == 0 || m_colorInterpolator ==0) + return false; + return true; +} + +KisGradientSegment::RGBColorInterpolationStrategy *KisGradientSegment::RGBColorInterpolationStrategy::instance() +{ + if (m_instance == 0) { + m_instance = new RGBColorInterpolationStrategy(); + Q_CHECK_PTR(m_instance); + } + + return m_instance; +} + +Color KisGradientSegment::RGBColorInterpolationStrategy::colorAt(double t, Color start, Color end) const +{ + int startRed = start.color().red(); + int startGreen = start.color().green(); + int startBlue = start.color().blue(); + double startAlpha = start.alpha(); + int red = static_cast<int>(startRed + t * (end.color().red() - startRed) + 0.5); + int green = static_cast<int>(startGreen + t * (end.color().green() - startGreen) + 0.5); + int blue = static_cast<int>(startBlue + t * (end.color().blue() - startBlue) + 0.5); + double alpha = startAlpha + t * (end.alpha() - startAlpha); + + return Color(QColor(red, green, blue), alpha); +} + +KisGradientSegment::HSVCWColorInterpolationStrategy *KisGradientSegment::HSVCWColorInterpolationStrategy::instance() +{ + if (m_instance == 0) { + m_instance = new HSVCWColorInterpolationStrategy(); + Q_CHECK_PTR(m_instance); + } + + return m_instance; +} + +Color KisGradientSegment::HSVCWColorInterpolationStrategy::colorAt(double t, Color start, Color end) const +{ + KoColor sc = KoColor(start.color()); + KoColor ec = KoColor(end.color()); + + int s = static_cast<int>(sc.S() + t * (ec.S() - sc.S()) + 0.5); + int v = static_cast<int>(sc.V() + t * (ec.V() - sc.V()) + 0.5); + int h; + + if (ec.H() < sc.H()) { + h = static_cast<int>(ec.H() + (1 - t) * (sc.H() - ec.H()) + 0.5); + } + else { + h = static_cast<int>(ec.H() + (1 - t) * (360 - ec.H() + sc.H()) + 0.5); + + if (h > 359) { + h -= 360; + } + } + + double alpha = start.alpha() + t * (end.alpha() - start.alpha()); + + return Color(KoColor(h, s, v, KoColor::csHSV).color(), alpha); +} + +KisGradientSegment::HSVCCWColorInterpolationStrategy *KisGradientSegment::HSVCCWColorInterpolationStrategy::instance() +{ + if (m_instance == 0) { + m_instance = new HSVCCWColorInterpolationStrategy(); + Q_CHECK_PTR(m_instance); + } + + return m_instance; +} + +Color KisGradientSegment::HSVCCWColorInterpolationStrategy::colorAt(double t, Color start, Color end) const +{ + KoColor sc = KoColor(start.color()); + KoColor se = KoColor(end.color()); + + int s = static_cast<int>(sc.S() + t * (se.S() - sc.S()) + 0.5); + int v = static_cast<int>(sc.V() + t * (se.V() - sc.V()) + 0.5); + int h; + + if (sc.H() < se.H()) { + h = static_cast<int>(sc.H() + t * (se.H() - sc.H()) + 0.5); + } + else { + h = static_cast<int>(sc.H() + t * (360 - sc.H() + se.H()) + 0.5); + + if (h > 359) { + h -= 360; + } + } + + double alpha = start.alpha() + t * (end.alpha() - start.alpha()); + + return Color(KoColor(h, s, v, KoColor::csHSV).color(), alpha); +} + +KisGradientSegment::LinearInterpolationStrategy *KisGradientSegment::LinearInterpolationStrategy::instance() +{ + if (m_instance == 0) { + m_instance = new LinearInterpolationStrategy(); + Q_CHECK_PTR(m_instance); + } + + return m_instance; +} + +double KisGradientSegment::LinearInterpolationStrategy::calcValueAt(double t, double middle) +{ + Q_ASSERT(t > -DBL_EPSILON && t < 1 + DBL_EPSILON); + Q_ASSERT(middle > -DBL_EPSILON && middle < 1 + DBL_EPSILON); + + double value = 0; + + if (t <= middle) { + if (middle < DBL_EPSILON) { + value = 0; + } + else { + value = (t / middle) * 0.5; + } + } + else { + if (middle > 1 - DBL_EPSILON) { + value = 1; + } + else { + value = ((t - middle) / (1 - middle)) * 0.5 + 0.5; + } + } + + return value; +} + +double KisGradientSegment::LinearInterpolationStrategy::valueAt(double t, double middle) const +{ + return calcValueAt(t, middle); +} + +KisGradientSegment::CurvedInterpolationStrategy::CurvedInterpolationStrategy() +{ + m_logHalf = log(0.5); +} + +KisGradientSegment::CurvedInterpolationStrategy *KisGradientSegment::CurvedInterpolationStrategy::instance() +{ + if (m_instance == 0) { + m_instance = new CurvedInterpolationStrategy(); + Q_CHECK_PTR(m_instance); + } + + return m_instance; +} + +double KisGradientSegment::CurvedInterpolationStrategy::valueAt(double t, double middle) const +{ + Q_ASSERT(t > -DBL_EPSILON && t < 1 + DBL_EPSILON); + Q_ASSERT(middle > -DBL_EPSILON && middle < 1 + DBL_EPSILON); + + double value = 0; + + if (middle < DBL_EPSILON) { + middle = DBL_EPSILON; + } + + value = pow(t, m_logHalf / log(middle)); + + return value; +} + +KisGradientSegment::SineInterpolationStrategy *KisGradientSegment::SineInterpolationStrategy::instance() +{ + if (m_instance == 0) { + m_instance = new SineInterpolationStrategy(); + Q_CHECK_PTR(m_instance); + } + + return m_instance; +} + +double KisGradientSegment::SineInterpolationStrategy::valueAt(double t, double middle) const +{ + double lt = LinearInterpolationStrategy::calcValueAt(t, middle); + double value = (sin(-M_PI_2 + M_PI * lt) + 1.0) / 2.0; + + return value; +} + +KisGradientSegment::SphereIncreasingInterpolationStrategy *KisGradientSegment::SphereIncreasingInterpolationStrategy::instance() +{ + if (m_instance == 0) { + m_instance = new SphereIncreasingInterpolationStrategy(); + Q_CHECK_PTR(m_instance); + } + + return m_instance; +} + +double KisGradientSegment::SphereIncreasingInterpolationStrategy::valueAt(double t, double middle) const +{ + double lt = LinearInterpolationStrategy::calcValueAt(t, middle) - 1; + double value = sqrt(1 - lt * lt); + + return value; +} + +KisGradientSegment::SphereDecreasingInterpolationStrategy *KisGradientSegment::SphereDecreasingInterpolationStrategy::instance() +{ + if (m_instance == 0) { + m_instance = new SphereDecreasingInterpolationStrategy(); + Q_CHECK_PTR(m_instance); + } + + return m_instance; +} + +double KisGradientSegment::SphereDecreasingInterpolationStrategy::valueAt(double t, double middle) const +{ + double lt = LinearInterpolationStrategy::calcValueAt(t, middle); + double value = 1 - sqrt(1 - lt * lt); + + return value; +} + +#include "kis_gradient.moc" + diff --git a/krita/core/kis_gradient.h b/krita/core/kis_gradient.h new file mode 100644 index 00000000..3610e0e0 --- /dev/null +++ b/krita/core/kis_gradient.h @@ -0,0 +1,264 @@ +/* + * kis_gradient.h - part of Krayon + * + * Copyright (c) 2000 Matthias Elter <elter@kde.org> + * 2004 Boudewijn Rempt <boud@valdyas.org> + * 2004 Adrian Page <adrian@pagenet.plus.com> + * 2004 Sven Langkamp <longamp@reallygood.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. + */ + +#ifndef KIS_GRADIENT_H +#define KIS_GRADIENT_H + +#include <qvaluevector.h> +#include <qcolor.h> + +#include <kio/job.h> + +#include "kis_resource.h" +#include "kis_global.h" + +class QImage; + +enum { + INTERP_LINEAR = 0, + INTERP_CURVED, + INTERP_SINE, + INTERP_SPHERE_INCREASING, + INTERP_SPHERE_DECREASING +}; + +enum { + COLOR_INTERP_RGB, + COLOR_INTERP_HSV_CCW, + COLOR_INTERP_HSV_CW +}; + +// TODO: Replace QColor with KisColor +class Color { + public: + Color() { m_alpha = 0; } + Color(const QColor& color, double alpha) { m_color = color; m_alpha = alpha; } + + const QColor& color() const { return m_color; } + double alpha() const { return m_alpha; } + + private: + QColor m_color; + double m_alpha; +}; + +class KisGradientSegment { + public: + KisGradientSegment(int interpolationType, int colorInterpolationType, double startOffset, double middleOffset, double endOffset, const Color& startColor, const Color& endColor); + + // startOffset <= t <= endOffset + Color colorAt(double t) const; + + const Color& startColor() const; + const Color& endColor() const; + + void setStartColor(const Color& color) { m_startColor = color; } + void setEndColor(const Color& color) { m_endColor = color; } + + double startOffset() const; + double middleOffset() const; + double endOffset() const; + + void setStartOffset(double t); + void setMiddleOffset(double t); + void setEndOffset(double t); + + double length() { return m_length; } + + int interpolation() const; + int colorInterpolation() const; + + void setInterpolation(int interpolationType); + void setColorInterpolation(int colorInterpolationType); + + bool isValid() const; + protected: + + class ColorInterpolationStrategy { + public: + ColorInterpolationStrategy() {} + virtual ~ColorInterpolationStrategy() {} + + virtual Color colorAt(double t, Color start, Color end) const = 0; + virtual int type() const = 0; + }; + + class RGBColorInterpolationStrategy : public ColorInterpolationStrategy { + public: + static RGBColorInterpolationStrategy *instance(); + + virtual Color colorAt(double t, Color start, Color end) const; + virtual int type() const { return COLOR_INTERP_RGB; } + + private: + RGBColorInterpolationStrategy() {} + + static RGBColorInterpolationStrategy *m_instance; + }; + + class HSVCWColorInterpolationStrategy : public ColorInterpolationStrategy { + public: + static HSVCWColorInterpolationStrategy *instance(); + + virtual Color colorAt(double t, Color start, Color end) const; + virtual int type() const { return COLOR_INTERP_HSV_CW; } + private: + HSVCWColorInterpolationStrategy() {} + + static HSVCWColorInterpolationStrategy *m_instance; + }; + + class HSVCCWColorInterpolationStrategy : public ColorInterpolationStrategy { + public: + static HSVCCWColorInterpolationStrategy *instance(); + + virtual Color colorAt(double t, Color start, Color end) const; + virtual int type() const { return COLOR_INTERP_HSV_CCW; } + private: + HSVCCWColorInterpolationStrategy() {} + + static HSVCCWColorInterpolationStrategy *m_instance; + }; + + class InterpolationStrategy { + public: + InterpolationStrategy() {} + virtual ~InterpolationStrategy() {} + + virtual double valueAt(double t, double middle) const = 0; + virtual int type() const = 0; + }; + + class LinearInterpolationStrategy : public InterpolationStrategy { + public: + static LinearInterpolationStrategy *instance(); + + virtual double valueAt(double t, double middle) const; + virtual int type() const { return INTERP_LINEAR; } + + // This does the actual calculation and is made + // static as an optimisation for the other + // strategies that need this for their own calculation. + static double calcValueAt(double t, double middle); + + private: + LinearInterpolationStrategy() {} + + static LinearInterpolationStrategy *m_instance; + }; + + class CurvedInterpolationStrategy : public InterpolationStrategy { + public: + static CurvedInterpolationStrategy *instance(); + + virtual double valueAt(double t, double middle) const; + virtual int type() const { return INTERP_CURVED; } + private: + CurvedInterpolationStrategy(); + + static CurvedInterpolationStrategy *m_instance; + double m_logHalf; + }; + + class SphereIncreasingInterpolationStrategy : public InterpolationStrategy { + public: + static SphereIncreasingInterpolationStrategy *instance(); + + virtual double valueAt(double t, double middle) const; + virtual int type() const { return INTERP_SPHERE_INCREASING; } + private: + SphereIncreasingInterpolationStrategy() {} + + static SphereIncreasingInterpolationStrategy *m_instance; + }; + + class SphereDecreasingInterpolationStrategy : public InterpolationStrategy { + public: + static SphereDecreasingInterpolationStrategy *instance(); + + virtual double valueAt(double t, double middle) const; + virtual int type() const { return INTERP_SPHERE_DECREASING; } + private: + SphereDecreasingInterpolationStrategy() {} + + static SphereDecreasingInterpolationStrategy *m_instance; + }; + + class SineInterpolationStrategy : public InterpolationStrategy { + public: + static SineInterpolationStrategy *instance(); + + virtual double valueAt(double t, double middle) const; + virtual int type() const { return INTERP_SINE; } + private: + SineInterpolationStrategy() {} + + static SineInterpolationStrategy *m_instance; + }; + private: + InterpolationStrategy *m_interpolator; + ColorInterpolationStrategy *m_colorInterpolator; + + double m_startOffset; + double m_middleOffset; + double m_endOffset; + double m_length; + double m_middleT; + + Color m_startColor; + Color m_endColor; +}; + +class KisGradient : public KisResource { + typedef KisResource super; + Q_OBJECT + +public: + KisGradient(const QString& file); + virtual ~KisGradient(); + + virtual bool load(); + virtual bool save(); + virtual QImage img(); + virtual QImage generatePreview(int width, int height) const; + + void colorAt(double t, QColor *color, Q_UINT8 *opacity) const; + + KisGradientSegment *segmentAt(double t) const; + +protected: + inline void pushSegment( KisGradientSegment* segment ) { m_segments.push_back(segment); }; + void setImage(const QImage& img); + + QValueVector<KisGradientSegment *> m_segments; + +private: + bool init(); + +private: + QByteArray m_data; + QImage m_img; +}; + +#endif // KIS_GRADIENT_H + diff --git a/krita/core/kis_gradient_painter.cc b/krita/core/kis_gradient_painter.cc new file mode 100644 index 00000000..b73623aa --- /dev/null +++ b/krita/core/kis_gradient_painter.cc @@ -0,0 +1,723 @@ +/* + * Copyright (c) 2004 Adrian Page <adrian@pagenet.plus.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 <stdlib.h> +#include <string.h> +#include <cfloat> + +#include "qbrush.h" +#include "qcolor.h" +#include "qfontinfo.h" +#include "qfontmetrics.h" +#include "qpen.h" +#include "qregion.h" +#include "qwmatrix.h" +#include <qimage.h> +#include <qmap.h> +#include <qpainter.h> +#include <qpixmap.h> +#include <qpointarray.h> +#include <qrect.h> +#include <qstring.h> + +#include <kdebug.h> +#include <klocale.h> + +#include "kis_brush.h" +#include "kis_debug_areas.h" +#include "kis_gradient.h" +#include "kis_image.h" +#include "kis_iterators_pixel.h" +#include "kis_layer.h" +#include "kis_paint_device.h" +#include "kis_pattern.h" +#include "kis_rect.h" +#include "kis_colorspace.h" +#include "kis_types.h" +#include "kis_vec.h" +#include "kis_selection.h" +#include "kis_gradient_painter.h" +#include "kis_meta_registry.h" +#include "kis_colorspace_factory_registry.h" + +namespace { + + class GradientShapeStrategy { + public: + GradientShapeStrategy(const KisPoint& gradientVectorStart, const KisPoint& gradientVectorEnd); + virtual ~GradientShapeStrategy() {} + + virtual double valueAt(double x, double y) const = 0; + + protected: + KisPoint m_gradientVectorStart; + KisPoint m_gradientVectorEnd; + }; + + GradientShapeStrategy::GradientShapeStrategy(const KisPoint& gradientVectorStart, const KisPoint& gradientVectorEnd) + : m_gradientVectorStart(gradientVectorStart), m_gradientVectorEnd(gradientVectorEnd) + { + } + + + class LinearGradientStrategy : public GradientShapeStrategy { + typedef GradientShapeStrategy super; + public: + LinearGradientStrategy(const KisPoint& gradientVectorStart, const KisPoint& gradientVectorEnd); + + virtual double valueAt(double x, double y) const; + + protected: + double m_normalisedVectorX; + double m_normalisedVectorY; + double m_vectorLength; + }; + + LinearGradientStrategy::LinearGradientStrategy(const KisPoint& gradientVectorStart, const KisPoint& gradientVectorEnd) + : super(gradientVectorStart, gradientVectorEnd) + { + double dx = gradientVectorEnd.x() - gradientVectorStart.x(); + double dy = gradientVectorEnd.y() - gradientVectorStart.y(); + + m_vectorLength = sqrt((dx * dx) + (dy * dy)); + + if (m_vectorLength < DBL_EPSILON) { + m_normalisedVectorX = 0; + m_normalisedVectorY = 0; + } + else { + m_normalisedVectorX = dx / m_vectorLength; + m_normalisedVectorY = dy / m_vectorLength; + } + } + + double LinearGradientStrategy::valueAt(double x, double y) const + { + double vx = x - m_gradientVectorStart.x(); + double vy = y - m_gradientVectorStart.y(); + + // Project the vector onto the normalised gradient vector. + double t = vx * m_normalisedVectorX + vy * m_normalisedVectorY; + + if (m_vectorLength < DBL_EPSILON) { + t = 0; + } + else { + // Scale to 0 to 1 over the gradient vector length. + t /= m_vectorLength; + } + + return t; + } + + + class BiLinearGradientStrategy : public LinearGradientStrategy { + typedef LinearGradientStrategy super; + public: + BiLinearGradientStrategy(const KisPoint& gradientVectorStart, const KisPoint& gradientVectorEnd); + + virtual double valueAt(double x, double y) const; + }; + + BiLinearGradientStrategy::BiLinearGradientStrategy(const KisPoint& gradientVectorStart, const KisPoint& gradientVectorEnd) + : super(gradientVectorStart, gradientVectorEnd) + { + } + + double BiLinearGradientStrategy::valueAt(double x, double y) const + { + double t = super::valueAt(x, y); + + // Reflect + if (t < -DBL_EPSILON) { + t = -t; + } + + return t; + } + + + class RadialGradientStrategy : public GradientShapeStrategy { + typedef GradientShapeStrategy super; + public: + RadialGradientStrategy(const KisPoint& gradientVectorStart, const KisPoint& gradientVectorEnd); + + virtual double valueAt(double x, double y) const; + + protected: + double m_radius; + }; + + RadialGradientStrategy::RadialGradientStrategy(const KisPoint& gradientVectorStart, const KisPoint& gradientVectorEnd) + : super(gradientVectorStart, gradientVectorEnd) + { + double dx = gradientVectorEnd.x() - gradientVectorStart.x(); + double dy = gradientVectorEnd.y() - gradientVectorStart.y(); + + m_radius = sqrt((dx * dx) + (dy * dy)); + } + + double RadialGradientStrategy::valueAt(double x, double y) const + { + double dx = x - m_gradientVectorStart.x(); + double dy = y - m_gradientVectorStart.y(); + + double distance = sqrt((dx * dx) + (dy * dy)); + + double t; + + if (m_radius < DBL_EPSILON) { + t = 0; + } + else { + t = distance / m_radius; + } + + return t; + } + + + class SquareGradientStrategy : public GradientShapeStrategy { + typedef GradientShapeStrategy super; + public: + SquareGradientStrategy(const KisPoint& gradientVectorStart, const KisPoint& gradientVectorEnd); + + virtual double valueAt(double x, double y) const; + + protected: + double m_normalisedVectorX; + double m_normalisedVectorY; + double m_vectorLength; + }; + + SquareGradientStrategy::SquareGradientStrategy(const KisPoint& gradientVectorStart, const KisPoint& gradientVectorEnd) + : super(gradientVectorStart, gradientVectorEnd) + { + double dx = gradientVectorEnd.x() - gradientVectorStart.x(); + double dy = gradientVectorEnd.y() - gradientVectorStart.y(); + + m_vectorLength = sqrt((dx * dx) + (dy * dy)); + + if (m_vectorLength < DBL_EPSILON) { + m_normalisedVectorX = 0; + m_normalisedVectorY = 0; + } + else { + m_normalisedVectorX = dx / m_vectorLength; + m_normalisedVectorY = dy / m_vectorLength; + } + } + + double SquareGradientStrategy::valueAt(double x, double y) const + { + double px = x - m_gradientVectorStart.x(); + double py = y - m_gradientVectorStart.y(); + + double distance1 = 0; + double distance2 = 0; + + if (m_vectorLength > DBL_EPSILON) { + + // Point to line distance is: + // distance = ((l0.y() - l1.y()) * p.x() + (l1.x() - l0.x()) * p.y() + l0.x() * l1.y() - l1.x() * l0.y()) / m_vectorLength; + // + // Here l0 = (0, 0) and |l1 - l0| = 1 + + distance1 = -m_normalisedVectorY * px + m_normalisedVectorX * py; + distance1 = fabs(distance1); + + // Rotate point by 90 degrees and get the distance to the perpendicular + distance2 = -m_normalisedVectorY * -py + m_normalisedVectorX * px; + distance2 = fabs(distance2); + } + + double t = QMAX(distance1, distance2) / m_vectorLength; + + return t; + } + + + class ConicalGradientStrategy : public GradientShapeStrategy { + typedef GradientShapeStrategy super; + public: + ConicalGradientStrategy(const KisPoint& gradientVectorStart, const KisPoint& gradientVectorEnd); + + virtual double valueAt(double x, double y) const; + + protected: + double m_vectorAngle; + }; + + ConicalGradientStrategy::ConicalGradientStrategy(const KisPoint& gradientVectorStart, const KisPoint& gradientVectorEnd) + : super(gradientVectorStart, gradientVectorEnd) + { + double dx = gradientVectorEnd.x() - gradientVectorStart.x(); + double dy = gradientVectorEnd.y() - gradientVectorStart.y(); + + // Get angle from 0 to 2 PI. + m_vectorAngle = atan2(dy, dx) + M_PI; + } + + double ConicalGradientStrategy::valueAt(double x, double y) const + { + double px = x - m_gradientVectorStart.x(); + double py = y - m_gradientVectorStart.y(); + + double angle = atan2(py, px) + M_PI; + + angle -= m_vectorAngle; + + if (angle < 0) { + angle += 2 * M_PI; + } + + double t = angle / (2 * M_PI); + + return t; + } + + + class ConicalSymetricGradientStrategy : public GradientShapeStrategy { + typedef GradientShapeStrategy super; + public: + ConicalSymetricGradientStrategy(const KisPoint& gradientVectorStart, const KisPoint& gradientVectorEnd); + + virtual double valueAt(double x, double y) const; + + protected: + double m_vectorAngle; + }; + + ConicalSymetricGradientStrategy::ConicalSymetricGradientStrategy(const KisPoint& gradientVectorStart, const KisPoint& gradientVectorEnd) + : super(gradientVectorStart, gradientVectorEnd) + { + double dx = gradientVectorEnd.x() - gradientVectorStart.x(); + double dy = gradientVectorEnd.y() - gradientVectorStart.y(); + + // Get angle from 0 to 2 PI. + m_vectorAngle = atan2(dy, dx) + M_PI; + } + + double ConicalSymetricGradientStrategy::valueAt(double x, double y) const + { + double px = x - m_gradientVectorStart.x(); + double py = y - m_gradientVectorStart.y(); + + double angle = atan2(py, px) + M_PI; + + angle -= m_vectorAngle; + + if (angle < 0) { + angle += 2 * M_PI; + } + + double t; + + if (angle < M_PI) { + t = angle / M_PI; + } + else { + t = 1 - ((angle - M_PI) / M_PI); + } + + return t; + } + + + class GradientRepeatStrategy { + public: + GradientRepeatStrategy() {} + virtual ~GradientRepeatStrategy() {} + + virtual double valueAt(double t) const = 0; + }; + + + class GradientRepeatNoneStrategy : public GradientRepeatStrategy { + public: + static GradientRepeatNoneStrategy *instance(); + + virtual double valueAt(double t) const; + + private: + GradientRepeatNoneStrategy() {} + + static GradientRepeatNoneStrategy *m_instance; + }; + + GradientRepeatNoneStrategy *GradientRepeatNoneStrategy::m_instance = 0; + + GradientRepeatNoneStrategy *GradientRepeatNoneStrategy::instance() + { + if (m_instance == 0) { + m_instance = new GradientRepeatNoneStrategy(); + Q_CHECK_PTR(m_instance); + } + + return m_instance; + } + + // Output is clamped to 0 to 1. + double GradientRepeatNoneStrategy::valueAt(double t) const + { + double value = t; + + if (t < DBL_EPSILON) { + value = 0; + } + else + if (t > 1 - DBL_EPSILON) { + value = 1; + } + + return value; + } + + + class GradientRepeatForwardsStrategy : public GradientRepeatStrategy { + public: + static GradientRepeatForwardsStrategy *instance(); + + virtual double valueAt(double t) const; + + private: + GradientRepeatForwardsStrategy() {} + + static GradientRepeatForwardsStrategy *m_instance; + }; + + GradientRepeatForwardsStrategy *GradientRepeatForwardsStrategy::m_instance = 0; + + GradientRepeatForwardsStrategy *GradientRepeatForwardsStrategy::instance() + { + if (m_instance == 0) { + m_instance = new GradientRepeatForwardsStrategy(); + Q_CHECK_PTR(m_instance); + } + + return m_instance; + } + + // Output is 0 to 1, 0 to 1, 0 to 1... + double GradientRepeatForwardsStrategy::valueAt(double t) const + { + int i = static_cast<int>(t); + + if (t < DBL_EPSILON) { + i--; + } + + double value = t - i; + + return value; + } + + + class GradientRepeatAlternateStrategy : public GradientRepeatStrategy { + public: + static GradientRepeatAlternateStrategy *instance(); + + virtual double valueAt(double t) const; + + private: + GradientRepeatAlternateStrategy() {} + + static GradientRepeatAlternateStrategy *m_instance; + }; + + GradientRepeatAlternateStrategy *GradientRepeatAlternateStrategy::m_instance = 0; + + GradientRepeatAlternateStrategy *GradientRepeatAlternateStrategy::instance() + { + if (m_instance == 0) { + m_instance = new GradientRepeatAlternateStrategy(); + Q_CHECK_PTR(m_instance); + } + + return m_instance; + } + + // Output is 0 to 1, 1 to 0, 0 to 1, 1 to 0... + double GradientRepeatAlternateStrategy::valueAt(double t) const + { + if (t < 0) { + t = -t; + } + + int i = static_cast<int>(t); + + double value = t - i; + + if (i % 2 == 1) { + value = 1 - value; + } + + return value; + } +} + +KisGradientPainter::KisGradientPainter() + : super() +{ + m_gradient = 0; +} + +KisGradientPainter::KisGradientPainter(KisPaintDeviceSP device) : super(device), m_gradient(0) +{ +} + +bool KisGradientPainter::paintGradient(const KisPoint& gradientVectorStart, + const KisPoint& gradientVectorEnd, + enumGradientShape shape, + enumGradientRepeat repeat, + double antiAliasThreshold, + bool reverseGradient, + Q_INT32 startx, + Q_INT32 starty, + Q_INT32 width, + Q_INT32 height) +{ + m_cancelRequested = false; + + if (!m_gradient) return false; + + GradientShapeStrategy *shapeStrategy = 0; + + switch (shape) { + case GradientShapeLinear: + shapeStrategy = new LinearGradientStrategy(gradientVectorStart, gradientVectorEnd); + break; + case GradientShapeBiLinear: + shapeStrategy = new BiLinearGradientStrategy(gradientVectorStart, gradientVectorEnd); + break; + case GradientShapeRadial: + shapeStrategy = new RadialGradientStrategy(gradientVectorStart, gradientVectorEnd); + break; + case GradientShapeSquare: + shapeStrategy = new SquareGradientStrategy(gradientVectorStart, gradientVectorEnd); + break; + case GradientShapeConical: + shapeStrategy = new ConicalGradientStrategy(gradientVectorStart, gradientVectorEnd); + break; + case GradientShapeConicalSymetric: + shapeStrategy = new ConicalSymetricGradientStrategy(gradientVectorStart, gradientVectorEnd); + break; + } + Q_CHECK_PTR(shapeStrategy); + + GradientRepeatStrategy *repeatStrategy = 0; + + switch (repeat) { + case GradientRepeatNone: + repeatStrategy = GradientRepeatNoneStrategy::instance(); + break; + case GradientRepeatForwards: + repeatStrategy = GradientRepeatForwardsStrategy::instance(); + break; + case GradientRepeatAlternate: + repeatStrategy = GradientRepeatAlternateStrategy::instance(); + break; + } + Q_ASSERT(repeatStrategy != 0); + + + //If the device has a selection only iterate over that selection + QRect r; + if( m_device->hasSelection() ) { + r = m_device->selection()->selectedExactRect(); + startx = r.x(); + starty = r.y(); + width = r.width(); + height = r.height(); + } + + Q_INT32 endx = startx + width - 1; + Q_INT32 endy = starty + height - 1; + + QImage layer (width, height, 32); + layer.setAlphaBuffer(true); + + int pixelsProcessed = 0; + int lastProgressPercent = 0; + + emit notifyProgressStage(i18n("Rendering gradient..."), 0); + + int totalPixels = width * height; + + if (antiAliasThreshold < 1 - DBL_EPSILON) { + totalPixels *= 2; + } + + for (int y = starty; y <= endy; y++) { + for (int x = startx; x <= endx; x++) { + + double t = shapeStrategy->valueAt( x, y); + t = repeatStrategy->valueAt(t); + + if (reverseGradient) { + t = 1 - t; + } + + QColor color; + Q_UINT8 opacity; + + m_gradient->colorAt(t, &color, &opacity); + + layer.setPixel(x - startx, y - starty, + qRgba(color.red(), color.green(), color.blue(), opacity)); + + pixelsProcessed++; + + int progressPercent = (pixelsProcessed * 100) / totalPixels; + + if (progressPercent > lastProgressPercent) { + emit notifyProgress(progressPercent); + lastProgressPercent = progressPercent; + + if (m_cancelRequested) { + break; + } + } + if (m_cancelRequested) { + break; + } + } + } + + if (!m_cancelRequested && antiAliasThreshold < 1 - DBL_EPSILON) { + + QColor color; + emit notifyProgressStage(i18n("Anti-aliasing gradient..."), lastProgressPercent); + Q_UINT8 * layerPointer = layer.bits(); + for (int y = starty; y <= endy; y++) { + for (int x = startx; x <= endx; x++) { + + double maxDistance = 0; + + Q_UINT8 redThis = layerPointer[2]; + Q_UINT8 greenThis = layerPointer[1]; + Q_UINT8 blueThis = layerPointer[0]; + Q_UINT8 thisPixelOpacity = layerPointer[3]; + + for (int yOffset = -1; yOffset < 2; yOffset++) { + for (int xOffset = -1; xOffset < 2; xOffset++) { + + if (xOffset != 0 || yOffset != 0) { + int sampleX = x + xOffset; + int sampleY = y + yOffset; + + if (sampleX >= startx && sampleX <= endx && sampleY >= starty && sampleY <= endy) { + uint x = sampleX - startx; + uint y = sampleY - starty; + Q_UINT8 * pixelPos = layer.bits() + (y * width * 4) + (x * 4); + Q_UINT8 red = *(pixelPos +2); + Q_UINT8 green = *(pixelPos + 1); + Q_UINT8 blue = *pixelPos; + Q_UINT8 opacity = *(pixelPos + 3); + + double dRed = (red * opacity - redThis * thisPixelOpacity) / 65535.0; + double dGreen = (green * opacity - greenThis * thisPixelOpacity) / 65535.0; + double dBlue = (blue * opacity - blueThis * thisPixelOpacity) / 65535.0; + + #define SQRT_3 1.7320508 + + double distance =/* sqrt(*/dRed * dRed + dGreen * dGreen + dBlue * dBlue/*) / SQRT_3*/; + + if (distance > maxDistance) { + maxDistance = distance; + } + } + } + } + } + + if (maxDistance > 3.*antiAliasThreshold*antiAliasThreshold) { + const int numSamples = 4; + + int totalRed = 0; + int totalGreen = 0; + int totalBlue = 0; + int totalOpacity = 0; + + for (int ySample = 0; ySample < numSamples; ySample++) { + for (int xSample = 0; xSample < numSamples; xSample++) { + + double sampleWidth = 1.0 / numSamples; + + double sampleX = x - 0.5 + (sampleWidth / 2) + xSample * sampleWidth; + double sampleY = y - 0.5 + (sampleWidth / 2) + ySample * sampleWidth; + + double t = shapeStrategy->valueAt(sampleX, sampleY); + t = repeatStrategy->valueAt(t); + + if (reverseGradient) { + t = 1 - t; + } + + Q_UINT8 opacity; + + m_gradient->colorAt(t, &color, &opacity); + + totalRed += color.red(); + totalGreen += color.green(); + totalBlue += color.blue(); + totalOpacity += opacity; + } + } + + int red = totalRed / (numSamples * numSamples); + int green = totalGreen / (numSamples * numSamples); + int blue = totalBlue / (numSamples * numSamples); + int opacity = totalOpacity / (numSamples * numSamples); + + layer.setPixel(x - startx, y - starty, qRgba(red, green, blue, opacity)); + } + + pixelsProcessed++; + + int progressPercent = (pixelsProcessed * 100) / totalPixels; + + if (progressPercent > lastProgressPercent) { + emit notifyProgress(progressPercent); + lastProgressPercent = progressPercent; + + if (m_cancelRequested) { + break; + } + } + layerPointer += 4; + } + + if (m_cancelRequested) { + break; + } + } + } + + if (!m_cancelRequested) { + kdDebug() << "Have we got a selection? " << m_device->hasSelection() << endl; + KisPaintDeviceSP dev = new KisPaintDevice(KisMetaRegistry::instance()->csRegistry()->getRGB8(), "temporary device for gradient"); + dev->writeBytes(layer.bits(), startx, starty, width, height); + bltSelection(startx, starty, m_compositeOp, dev, m_opacity, startx, starty, width, height); + } + delete shapeStrategy; + + emit notifyProgressDone(); + + return !m_cancelRequested; +} diff --git a/krita/core/kis_gradient_painter.h b/krita/core/kis_gradient_painter.h new file mode 100644 index 00000000..ccfd5c3c --- /dev/null +++ b/krita/core/kis_gradient_painter.h @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2004 Adrian Page <adrian@pagenet.plus.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_GRADIENT_PAINTER_H_ +#define KIS_GRADIENT_PAINTER_H_ + +#include <kcommand.h> + +#include "kis_global.h" +#include "kis_types.h" +#include "kis_point.h" +#include "kis_painter.h" +#include <koffice_export.h> + +class KisGradient; + + +// XXX: Need to set dirtyRect in KisPainter +class KRITACORE_EXPORT KisGradientPainter : public KisPainter +{ + + typedef KisPainter super; + +public: + + KisGradientPainter(); + KisGradientPainter(KisPaintDeviceSP device); + + + enum enumGradientShape { + GradientShapeLinear, + GradientShapeBiLinear, + GradientShapeRadial, + GradientShapeSquare, + GradientShapeConical, + GradientShapeConicalSymetric + }; + + enum enumGradientRepeat { + GradientRepeatNone, + GradientRepeatForwards, + GradientRepeatAlternate + }; + + void setGradient(KisGradient& gradient) { m_gradient = &gradient; } + void setGradient(KisGradient* gradient) { m_gradient = gradient; } + + /** + * Paint a gradient in the rect between startx, starty, width and height. + * XXX: What does the returned bool mean? + * XXX: Make cs-independent + */ + bool paintGradient(const KisPoint& gradientVectorStart, + const KisPoint& gradientVectorEnd, + enumGradientShape shape, + enumGradientRepeat repeat, + double antiAliasThreshold, + bool reverseGradient, + Q_INT32 startx, + Q_INT32 starty, + Q_INT32 width, + Q_INT32 height); + + +private: + KisGradient *m_gradient; + + +}; +#endif //KIS_GRADIENT_PAINTER_H_ diff --git a/krita/core/kis_group_layer.cc b/krita/core/kis_group_layer.cc new file mode 100644 index 00000000..2a2ed325 --- /dev/null +++ b/krita/core/kis_group_layer.cc @@ -0,0 +1,428 @@ +/* + * Copyright (c) 2005 Casper Boemann <cbr@boemann.dk> + * + * 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. + */ + +#include <kdebug.h> +#include <kglobal.h> +#include <qimage.h> +#include <qdatetime.h> + +#include "kis_types.h" +#include "kis_layer.h" +#include "kis_group_layer.h" +#include "kis_layer_visitor.h" +#include "kis_debug_areas.h" +#include "kis_image.h" +#include "kis_paint_device.h" +#include "kis_merge_visitor.h" +#include "kis_fill_painter.h" + +KisGroupLayer::KisGroupLayer(KisImage *img, const QString &name, Q_UINT8 opacity) : + super(img, name, opacity), + m_x(0), + m_y(0) +{ + m_projection = new KisPaintDevice(this, img->colorSpace(), name.latin1()); +} + +KisGroupLayer::KisGroupLayer(const KisGroupLayer &rhs) : + super(rhs), + m_x(rhs.m_x), + m_y(rhs.m_y) +{ + for(vKisLayerSP_cit it = rhs.m_layers.begin(); it != rhs.m_layers.end(); ++it) + { + this->addLayer(it->data()->clone(), 0); + } + m_projection = new KisPaintDevice(*rhs.m_projection.data()); + m_projection->setParentLayer(this); +} + +KisLayerSP KisGroupLayer::clone() const +{ + return new KisGroupLayer(*this); +} + +KisGroupLayer::~KisGroupLayer() +{ + m_layers.clear(); +} + + +void KisGroupLayer::setDirty(bool propagate) +{ + KisLayer::setDirty(propagate); + if (propagate) emit (sigDirty(m_dirtyRect)); +} + +void KisGroupLayer::setDirty(const QRect & rc, bool propagate) +{ + KisLayer::setDirty(rc, propagate); + if (propagate) emit sigDirty(rc); +} + +void KisGroupLayer::resetProjection(KisPaintDevice* to) +{ + if (to) + m_projection = new KisPaintDevice(*to); /// XXX ### look into Copy on Write here (CoW) + else + m_projection = new KisPaintDevice(this, image()->colorSpace(), name().latin1()); +} + +bool KisGroupLayer::paintLayerInducesProjectionOptimization(KisPaintLayer* l) { + return l && l->paintDevice()->colorSpace() == m_image->colorSpace() && l->visible() + && l->opacity() == OPACITY_OPAQUE && !l->temporaryTarget() && !l->hasMask(); +} + +KisPaintDeviceSP KisGroupLayer::projection(const QRect & rect) +{ + // We don't have a parent, and we've got only one child: abuse the child's + // paint device as the projection if the child is visible and 100% opaque + if (parent() == 0 && childCount() == 1) { + KisPaintLayerSP l = dynamic_cast<KisPaintLayer*>(firstChild().data()); + if (paintLayerInducesProjectionOptimization(l)) { + l->setClean(rect); + setClean(rect); + return l->paintDevice(); + } + } + // No need for updates, we're clean + if (!dirty()) { + return m_projection; + } + // No need for updates -- the desired area wasn't dirty + if (!rect.intersects(m_dirtyRect)) { + return m_projection; + } + + + // Okay, we need to update the intersection between + // what's dirty and what's asked us to be updated. + // XXX Nooo, that doesn't work, since the call to setClean following this, is actually: + // m_dirtyRect = QRect(); So the non-intersecting part gets brilliantly lost otherwise. + const QRect rc = m_dirtyRect;//rect.intersect(m_dirtyRect); + + updateProjection(rc); + setClean(rect); + + return m_projection; +} + +uint KisGroupLayer::childCount() const +{ + return m_layers.count(); +} + +KisLayerSP KisGroupLayer::firstChild() const +{ + return at(0); +} + +KisLayerSP KisGroupLayer::lastChild() const +{ + return at(childCount() - 1); +} + +KisLayerSP KisGroupLayer::at(int index) const +{ + if (childCount() && index >= 0 && kClamp(uint(index), uint(0), childCount() - 1) == uint(index)) + return m_layers.at(reverseIndex(index)); + return 0; +} + +int KisGroupLayer::index(KisLayerSP layer) const +{ + if (layer->parent().data() == this) + return layer->index(); + return -1; +} + +void KisGroupLayer::setIndex(KisLayerSP layer, int index) +{ + if (layer->parent().data() != this) + return; + //TODO optimize + removeLayer(layer); + addLayer(layer, index); +} + +bool KisGroupLayer::addLayer(KisLayerSP newLayer, int x) +{ + if (x < 0 || kClamp(uint(x), uint(0), childCount()) != uint(x) || + newLayer->parent() || m_layers.contains(newLayer)) + { + kdWarning() << "invalid input to KisGroupLayer::addLayer(KisLayerSP newLayer, int x)!" << endl; + return false; + } + uint index(x); + if (index == 0) + m_layers.append(newLayer); + else + m_layers.insert(m_layers.begin() + reverseIndex(index) + 1, newLayer); + for (uint i = childCount() - 1; i > index; i--) + at(i)->m_index++; + newLayer->m_parent = this; + newLayer->m_index = index; + newLayer->setImage(image()); + newLayer->setDirty(newLayer->extent()); + setDirty(); + return true; +} + +bool KisGroupLayer::addLayer(KisLayerSP newLayer, KisLayerSP aboveThis) +{ + if (aboveThis && aboveThis->parent().data() != this) + { + kdWarning() << "invalid input to KisGroupLayer::addLayer(KisLayerSP newLayer, KisLayerSP aboveThis)!" << endl; + return false; + } + return addLayer(newLayer, aboveThis ? aboveThis->index() : childCount()); +} + +bool KisGroupLayer::removeLayer(int x) +{ + if (x >= 0 && kClamp(uint(x), uint(0), childCount() - 1) == uint(x)) + { + uint index(x); + for (uint i = childCount() - 1; i > index; i--) + at(i)->m_index--; + KisLayerSP removedLayer = at(index); + + removedLayer->m_parent = 0; + removedLayer->m_index = -1; + m_layers.erase(m_layers.begin() + reverseIndex(index)); + setDirty(removedLayer->extent()); + if (childCount() < 1) { + // No children, nothing to show for it. + m_projection->clear(); + setDirty(); + } + return true; + } + kdWarning() << "invalid input to KisGroupLayer::removeLayer()!" << endl; + return false; +} + +bool KisGroupLayer::removeLayer(KisLayerSP layer) +{ + if (layer->parent().data() != this) + { + kdWarning() << "invalid input to KisGroupLayer::removeLayer()!" << endl; + return false; + } + + return removeLayer(layer->index()); +} + +void KisGroupLayer::setImage(KisImage *image) +{ + super::setImage(image); + for (vKisLayerSP_it it = m_layers.begin(); it != m_layers.end(); ++it) + { + (*it)->setImage(image); + } +} + +QRect KisGroupLayer::extent() const +{ + QRect groupExtent; + + for (vKisLayerSP_cit it = m_layers.begin(); it != m_layers.end(); ++it) + { + groupExtent |= (*it)->extent(); + } + + return groupExtent; +} + +QRect KisGroupLayer::exactBounds() const +{ + QRect groupExactBounds; + + for (vKisLayerSP_cit it = m_layers.begin(); it != m_layers.end(); ++it) + { + groupExactBounds |= (*it)->exactBounds(); + } + + return groupExactBounds; +} + +Q_INT32 KisGroupLayer::x() const +{ + return m_x; +} + +void KisGroupLayer::setX(Q_INT32 x) +{ + Q_INT32 delta = x - m_x; + + for (vKisLayerSP_cit it = m_layers.begin(); it != m_layers.end(); ++it) + { + KisLayerSP layer = *it; + layer->setX(layer->x() + delta); + } + m_x = x; +} + +Q_INT32 KisGroupLayer::y() const +{ + return m_y; +} + +void KisGroupLayer::setY(Q_INT32 y) +{ + Q_INT32 delta = y - m_y; + + for (vKisLayerSP_cit it = m_layers.begin(); it != m_layers.end(); ++it) + { + KisLayerSP layer = *it; + layer->setY(layer->y() + delta); + } + + m_y = y; +} + +QImage KisGroupLayer::createThumbnail(Q_INT32 w, Q_INT32 h) +{ + return m_projection->createThumbnail(w, h); +} + +void KisGroupLayer::updateProjection(const QRect & rc) +{ + if (!m_dirtyRect.isValid()) return; + + // Get the first layer in this group to start compositing with + KisLayerSP child = lastChild(); + + // No child -- clear the projection. Without children, a group layer is empty. + if (!child) m_projection->clear(); + + KisLayerSP startWith = 0; + KisAdjustmentLayerSP adjLayer = 0; + KisLayerSP tmpPaintLayer = 0; + + // If this is the rootlayer, don't do anything with adj. layers that are below the + // first paintlayer + bool gotPaintLayer = (parent() != 0); + + // Look through all the child layers, searching for the first dirty layer + // if it's found, and if we have found an adj. layer before the the dirty layer, + // composite from the first adjustment layer searching back from the first dirty layer + while (child) { + KisAdjustmentLayerSP tmpAdjLayer = dynamic_cast<KisAdjustmentLayer*>(child.data()); + if (tmpAdjLayer) { + if (gotPaintLayer) { + // If this adjustment layer is dirty, start compositing with the + // previous layer, if there's one. + if (tmpAdjLayer->dirty(rc) && adjLayer != 0 && adjLayer->visible()) { + startWith = adjLayer->prevSibling(); + break; + } + else if (tmpAdjLayer->visible() && !tmpAdjLayer->dirty(rc)) { + // This is the first adj. layer that is not dirty -- the perfect starting point + adjLayer = tmpAdjLayer; + } + else { + startWith = tmpPaintLayer; + } + } + } + else { + tmpPaintLayer = child; + gotPaintLayer = true; + // A non-adjustmentlayer that's dirty; if there's an adjustmentlayer + // with a cache, we'll start from there. + if (child->dirty(rc)) { + if (adjLayer != 0 && adjLayer->visible()) { + // the first layer on top of the adj. layer + startWith = adjLayer->prevSibling(); + } + else { + startWith = child; + } + // break here: if there's no adj layer, we'll start with the layer->lastChild + break; + } + } + child = child->prevSibling(); + } + + if (adjLayer != 0 && startWith == 0 && gotPaintLayer && adjLayer->prevSibling()) { + startWith = adjLayer->prevSibling(); + } + + // No adj layer -- all layers inside the group must be recomposited + if (adjLayer == 0) { + startWith = lastChild(); + } + + if (startWith == 0) { + return; + } + + bool first = true; // The first layer in a stack needs special compositing + + // Fill the projection either with the cached data, or erase it. + KisFillPainter gc(m_projection); + if (adjLayer != 0) { + gc.bitBlt(rc.left(), rc.top(), + COMPOSITE_COPY, adjLayer->cachedPaintDevice(), OPACITY_OPAQUE, + rc.left(), rc.top(), rc.width(), rc.height()); + first = false; + } + else { + gc.eraseRect(rc); + first = true; + } + gc.end(); + + KisMergeVisitor visitor(m_projection, rc); + + child = startWith; + + while(child) + { + if(first) + { + // Copy the lowest layer rather than compositing it with the background + // or an empty image. This means the layer's composite op is ignored, + // which is consistent with Photoshop and gimp. + const KisCompositeOp cop = child->compositeOp(); + const bool block = child->signalsBlocked(); + child->blockSignals(true); + // Composite op copy doesn't take a mask/selection into account, so we need + // to make a difference between a paintlayer with a mask, and one without + KisPaintLayer* l = dynamic_cast<KisPaintLayer*>(child.data()); + if (l && l->hasMask()) + child->m_compositeOp = COMPOSITE_OVER; + else + child->m_compositeOp = COMPOSITE_COPY; + child->blockSignals(block); + child->accept(visitor); + child->blockSignals(true); + child->m_compositeOp = cop; + child->blockSignals(block); + first = false; + } + else + child->accept(visitor); + + child = child->prevSibling(); + } +} + +#include "kis_group_layer.moc" diff --git a/krita/core/kis_group_layer.h b/krita/core/kis_group_layer.h new file mode 100644 index 00000000..7b1a764a --- /dev/null +++ b/krita/core/kis_group_layer.h @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2005 Casper Boemann <cbr@boemann.dk> + * + * 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_GROUP_LAYER_H_ +#define KIS_GROUP_LAYER_H_ + +#include <ksharedptr.h> + +#include "kis_layer.h" +#include "kis_types.h" + +#include "kis_paint_layer.h" + +class KisMergeVisitor; + +/** + * A KisLayer that bundles child layers into a single layer. + * The top layer is firstChild(), with index 0; the bottommost lastChild() with index childCount() - 1. + * KisLayer::nextSibling() moves towards higher indices, from the top to the bottom layer; prevSibling() the reverse. + * (Implementation detail: internally, the indices are reversed, for speed.) + **/ +class KisGroupLayer : public KisLayer { + typedef KisLayer super; + + Q_OBJECT + +public: + KisGroupLayer(KisImage *img, const QString &name, Q_UINT8 opacity); + KisGroupLayer(const KisGroupLayer& rhs); + virtual ~KisGroupLayer(); + + virtual KisLayerSP clone() const; +public: + + /** + * Set the entire layer extent dirty; this percolates up to parent layers all the + * way to the root layer. + */ + virtual void setDirty(bool propagate = true); + + /** + * Add the given rect to the set of dirty rects for this layer; + * this percolates up to parent layers all the way to the root + * layer. + */ + virtual void setDirty(const QRect & rect, bool propagate = true); + + virtual void activate() {}; + + virtual void deactivate() {}; + + virtual Q_INT32 x() const; + virtual void setX(Q_INT32); + + virtual Q_INT32 y() const; + virtual void setY(Q_INT32); + + // Sets this layer and all its descendants' owner image to the given image. + virtual void setImage(KisImage *image); + + virtual QRect extent() const; + virtual QRect exactBounds() const; + + virtual bool accept(KisLayerVisitor &v) + { +// kdDebug(41001) << "GROUP\t\t" << name() +// << " dirty: " << dirty() +// << ", " << m_layers.count() << " children " +// << ", projection: " << m_projection +// << "\n"; + return v.visit(this); + }; + + virtual void resetProjection(KisPaintDevice* to = 0); /// will copy from to, if !0, CoW!! + virtual KisPaintDeviceSP projection(const QRect & rect); + + virtual uint childCount() const; + + virtual KisLayerSP firstChild() const; + virtual KisLayerSP lastChild() const; + + /// Returns the layer at the specified index. + virtual KisLayerSP at(int index) const; + + /// Returns the index of the layer if it's in this group, or -1 otherwise. + virtual int index(KisLayerSP layer) const; + + /// Moves the specified layer to the specified index in the group, if it's already a member of this group. + virtual void setIndex(KisLayerSP layer, int index); + + /** Adds the layer to this group at the specified index. childCount() is a valid index and appends to the end. + Fails and returns false if the layer is already in this group or any other (remove it first.) */ + virtual bool addLayer(KisLayerSP newLayer, int index); + + /** + * Add the specified layer above the specified layer (if aboveThis == 0, the bottom is used) */ + virtual bool addLayer(KisLayerSP newLayer, KisLayerSP aboveThis); + + /// Removes the layer at the specified index from the group. + virtual bool removeLayer(int index); + + /// Removes the layer from this group. Fails if there's no such layer in this group. + virtual bool removeLayer(KisLayerSP layer); + + virtual QImage createThumbnail(Q_INT32 w, Q_INT32 h); + + /// Returns if the layer will induce the projection hack (if the only layer in this group) + virtual bool paintLayerInducesProjectionOptimization(KisPaintLayer* l); +signals: + + void sigDirty(QRect rc); + +private: + + void updateProjection(const QRect & rc); + + inline int reverseIndex(int index) const { return childCount() - 1 - index; }; + vKisLayerSP m_layers; // Contains the list of all layers + KisPaintDeviceSP m_projection; // The cached composition of all layers in this group + + Q_INT32 m_x; + Q_INT32 m_y; +}; + +#endif // KIS_GROUP_LAYER_H_ + diff --git a/krita/core/kis_histogram.cc b/krita/core/kis_histogram.cc new file mode 100644 index 00000000..97fdeac4 --- /dev/null +++ b/krita/core/kis_histogram.cc @@ -0,0 +1,215 @@ +/* + * Copyright (c) 2004 Boudewijn Rempt + * (c) 2005 Bart Coppens <kde@bartcoppens.be> + * + * 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 <kdebug.h> +#include <qdatetime.h> // ### Debug + +#include "kis_types.h" +#include "kis_histogram.h" +#include "kis_paint_layer.h" +#include "kis_iterators_pixel.h" +#include "kis_colorspace.h" +#include "kis_debug_areas.h" + +KisHistogram::KisHistogram(KisPaintLayerSP layer, + KisHistogramProducerSP producer, + const enumHistogramType type) +{ + m_dev = layer->paintDevice(); + m_type = type; + m_producer = producer; + m_selection = false; + m_channel = 0; + + updateHistogram(); +} + +KisHistogram::KisHistogram(KisPaintDeviceSP paintdev, + KisHistogramProducerSP producer, + const enumHistogramType type) +{ + m_dev = paintdev; + m_type = type; + m_producer = producer; + m_selection = false; + m_channel = 0; + + updateHistogram(); +} + +KisHistogram::~KisHistogram() +{ +} + +void KisHistogram::updateHistogram() +{ + Q_INT32 x,y,w,h; + m_dev->exactBounds(x,y,w,h); + KisRectIteratorPixel srcIt = m_dev->createRectIterator(x,y,w,h, false); + KisColorSpace* cs = m_dev->colorSpace(); + + QTime t; + t.start(); + + // Let the producer do it's work + m_producer->clear(); + int i; + // Handle degenerate case (this happens with the accumulating histogram, + // which has an empty device) + if (srcIt.isDone()) { + m_producer->addRegionToBin(0, 0, 0, cs); + } else { + while ( !srcIt.isDone() ) { + i = srcIt.nConseqPixels(); + m_producer->addRegionToBin(srcIt.rawData(), srcIt.selectionMask(), i, cs); + srcIt += i; + } + } + + computeHistogram(); +} + +void KisHistogram::computeHistogram() +{ + m_completeCalculations = calculateForRange(m_producer->viewFrom(), + m_producer->viewFrom() + m_producer->viewWidth()); + + if (m_selection) { + m_selectionCalculations = calculateForRange(m_selFrom, m_selTo); + } else { + m_selectionCalculations.clear(); + } + +#if 1 + dump(); +#endif +} + +KisHistogram::Calculations KisHistogram::calculations() { + return m_completeCalculations.at(m_channel); +} + +KisHistogram::Calculations KisHistogram::selectionCalculations() { + return m_selectionCalculations.at(m_channel); +} + +QValueVector<KisHistogram::Calculations> KisHistogram::calculateForRange(double from, double to) { + QValueVector<Calculations> calculations; + uint count = m_producer->channels().count(); + + for (uint i = 0; i < count; i++) { + calculations.append(calculateSingleRange(i, from, to)); + } + + return calculations; +} + +KisHistogram::Calculations KisHistogram::calculateSingleRange(int channel, double from, double to) { + Calculations c; + + // XXX If from == to, we only want a specific bin, handle that properly! + + double max = from, min = to, total = 0.0, mean = 0.0; //, median = 0.0, stddev = 0.0; + Q_UINT32 high = 0, low = (Q_UINT32) -1, count = 0; + + if (m_producer->count() == 0) { + // We won't get anything, even if a range is specified + // XXX make sure all initial '0' values are correct here! + return c; + } + + Q_INT32 totbins = m_producer->numberOfBins(); + Q_UINT32 current; + + // convert the double range into actual bins: + double factor = static_cast<double>(totbins) / m_producer->viewWidth(); + + Q_INT32 fromBin = static_cast<Q_INT32>((from - m_producer->viewFrom()) * factor); + Q_INT32 toBin = fromBin + static_cast<Q_INT32>((to - from) * factor); + + // Min, max, count, low, high + for (Q_INT32 i = fromBin; i < toBin; i++) { + current = m_producer->getBinAt(channel, i); + double pos = static_cast<double>(i) / factor + from; + if (current > high) + high = current; + if (current < low) + low = current; + if (current > 0) { + if (pos < min) + min = pos; + if (pos > max) + max = pos; + } + // We do the count here as well. + // we can't use m_producer->count() for this, because of the range + count += current; + total += current * pos; + } + + if (count > 0) + mean = total / count; + + c.m_high = high; + c.m_low = low; + c.m_count = count; + c.m_min = min; + c.m_max = max; + c.m_mean = mean; + c.m_total = total; + + return c; +} + + +void KisHistogram::dump() { + kdDebug(DBG_AREA_MATH) << "Histogram\n"; + + switch (m_type) { + case LINEAR: + kdDebug(DBG_AREA_MATH) << "Linear histogram\n"; + break; + case LOGARITHMIC: + kdDebug(DBG_AREA_MATH) << "Logarithmic histogram\n"; + } + + kdDebug(DBG_AREA_MATH) << "Dumping channel " << m_channel << endl; + Calculations c = calculations(); + +/* for( int i = 0; i <256; ++i ) { + kdDebug(DBG_AREA_MATH) << "Value " + << QString().setNum(i) + << ": " + << QString().setNum(m_values[i]) + << "\n"; + }*/ + kdDebug(DBG_AREA_MATH) << "\n"; + + kdDebug(DBG_AREA_MATH) << "Max: " << QString().setNum(c.getMax()) << "\n"; + kdDebug(DBG_AREA_MATH) << "Min: " << QString().setNum(c.getMin()) << "\n"; + kdDebug(DBG_AREA_MATH) << "High: " << QString().setNum(c.getHighest()) << "\n"; + kdDebug(DBG_AREA_MATH) << "Low: " << QString().setNum(c.getLowest()) << "\n"; + kdDebug(DBG_AREA_MATH) << "Mean: " << m_producer->positionToString(c.getMean()) << "\n"; + kdDebug(DBG_AREA_MATH) << "Total: " << QString().setNum(c.getTotal()) << "\n"; +// kdDebug(DBG_AREA_MATH) << "Median: " << QString().setNum(m_median) << "\n"; +// kdDebug(DBG_AREA_MATH) << "Stddev: " << QString().setNum(m_stddev) << "\n"; +// kdDebug(DBG_AREA_MATH) << "percentile: " << QString().setNum(m_percentile) << "\n"; + + kdDebug(DBG_AREA_MATH) << "\n"; +} diff --git a/krita/core/kis_histogram.h b/krita/core/kis_histogram.h new file mode 100644 index 00000000..3365bde1 --- /dev/null +++ b/krita/core/kis_histogram.h @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2004 Boudewijn Rempt + * (c) 2005 Bart Coppens <kde@bartcoppens.be> + * + * 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_HISTOGRAM_ +#define KIS_HISTOGRAM_ + +#include "kis_types.h" +#include "kis_colorspace.h" +#include "kis_histogram_producer.h" + +enum enumHistogramType { + LINEAR, + LOGARITHMIC +}; +/** + * The histogram class computes the histogram data from the specified layer + * for the specified channel, through the use of a KisHistogramProducer. + * This class is only for layers and paintdevices. KisImages are not supported, + * but you can use the mergedImage function to create a paintdevice and feed that to this class. + * + * A Histogram also can have a selection: this is a specific range in the current histogram + * that will get calculations done on it as well. If the range's begin and end are the same, + * it is supposed to specify a single bin in the histogram. + * + * The calculations are done in the range 0 - 1, instead of the native range that a pixel + * might have, so it's not always as precise as it could be. But you can't have it all... + */ +class KisHistogram : public KShared { + +public: + /** + * Class that stores the result of histogram calculations. + * Doubles are in the 0-1 range, use the producer's positionToString function to display it. + **/ + class Calculations { + + double m_max, m_min, m_mean, m_total, m_median, m_stddev; + + Q_UINT32 m_high, m_low, m_count; + + friend class KisHistogram; + + public: + + Calculations() : m_max(0.0), m_min(0.0), m_mean(0.0), m_total(0.0), m_median(0.0), + m_stddev(0.0), m_high(0), m_low(0), m_count(0) {} + /** + * This function return the maximum bound of the histogram + * (values at greater position than the maximum are null) + */ + inline double getMax() { return m_max; } + /** + * This function return the minimum bound of the histogram + * (values at smaller position than the minimum are null) + */ + inline double getMin() { return m_min; } + /// This function return the highest value of the histogram + inline Q_UINT32 getHighest() { return m_high; } + /// This function return the lowest value of the histogram + inline Q_UINT32 getLowest() { return m_low; } + /// This function return the mean of value of the histogram + inline double getMean() { return m_mean; } + //double getMedian() { return m_median; } + //double getStandardDeviation() { return m_stddev; } + /// This function return the number of pixels used by the histogram + inline Q_UINT32 getCount() { return m_count; } + /** The sum of (the contents of every bin * the double value of that bin)*/ + inline double getTotal() { return m_total; } + //Q_UINT8 getPercentile() { return m_percentile; } // What is this exactly? XXX + }; + + KisHistogram(KisPaintLayerSP layer, + KisHistogramProducerSP producer, + const enumHistogramType type); + + KisHistogram(KisPaintDeviceSP paintdev, + KisHistogramProducerSP producer, + const enumHistogramType type); + + virtual ~KisHistogram(); + + /** Updates the information in the producer */ + void updateHistogram(); + + /** + * (Re)computes the mathematical information from the information currently in the producer. + * Needs to be called when you change the selection and want to get that information + **/ + void computeHistogram(); + + /** The information on the entire view for the current channel */ + Calculations calculations(); + /** The information on the current selection for the current channel */ + Calculations selectionCalculations(); + + inline Q_UINT32 getValue(Q_UINT8 i) { return m_producer->getBinAt(m_channel, i); } + + inline enumHistogramType getHistogramType() { return m_type; } + inline void setHistogramType(enumHistogramType type) { m_type = type; } + inline void setProducer(KisHistogramProducerSP producer) { m_producer = producer; } + inline void setChannel(Q_INT32 channel) { m_channel = channel; } + inline KisHistogramProducerSP producer() { return m_producer; } + inline Q_INT32 channel() { return m_channel; } + + inline bool hasSelection() { return m_selection; } + inline double selectionFrom() { return m_selFrom; } + inline double selectionTo() { return m_selTo; } + inline void setNoSelection() { m_selection = false; } + /** Sets the current selection */ + inline void setSelection(double from, double to) + { m_selection = true; m_selFrom = from; m_selTo = to; } + + +private: + // Dump the histogram to debug. + void dump(); + QValueVector<Calculations> calculateForRange(double from, double to); + Calculations calculateSingleRange(int channel, double from, double to); + + KisPaintDeviceSP m_device; + KisHistogramProducerSP m_producer; + + enumHistogramType m_type; + + Q_INT32 m_channel; + double m_selFrom, m_selTo; + bool m_selection; + + KisPaintDeviceSP m_dev; + + QValueVector<Calculations> m_completeCalculations, m_selectionCalculations; +}; + + +#endif // KIS_HISTOGRAM_WIDGET_ diff --git a/krita/core/kis_image.cc b/krita/core/kis_image.cc new file mode 100644 index 00000000..2c56b606 --- /dev/null +++ b/krita/core/kis_image.cc @@ -0,0 +1,1702 @@ +/* + * Copyright (c) 2002 Patrick Julien <freak@codepimps.org> + * Copyright (c) 2007 Benjamin Schleimer <bensch128@yahoo.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 <stdlib.h> +#include <math.h> + +#include <config.h> +#include LCMS_HEADER + +#include <qimage.h> +#include <qpainter.h> +#include <qsize.h> +#include <qtl.h> +#include <qapplication.h> +#include <qthread.h> +#include <qdatetime.h> + +#include <kcommand.h> +#include <kdebug.h> +#include <klocale.h> + +#include "kis_image_iface.h" + +#include "kis_annotation.h" +#include "kis_colorspace_factory_registry.h" +#include "kis_color.h" +#include "kis_command.h" +#include "kis_types.h" +//#include "kis_guide.h" +#include "kis_image.h" +#include "kis_paint_device.h" +#include "kis_paint_device_action.h" +#include "kis_selection.h" +#include "kis_painter.h" +#include "kis_fill_painter.h" +#include "kis_layer.h" +#include "kis_group_layer.h" +#include "kis_adjustment_layer.h" +#include "kis_paint_layer.h" +#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" +#include "kis_transaction.h" +#include "kis_crop_visitor.h" +#include "kis_transform_visitor.h" +#include "kis_filter_strategy.h" +#include "kis_profile.h" +#include "kis_paint_layer.h" +#include "kis_perspective_grid.h" +#include "kis_change_profile_visitor.h" +#include "kis_group_layer.h" +#include "kis_iterators_pixel.h" +#include "kis_shear_visitor.h" + +class KisImage::KisImagePrivate { +public: + KisColor backgroundColor; + Q_UINT32 lockCount; + bool sizeChangedWhileLocked; + bool selectionChangedWhileLocked; + KisSubstrateSP substrate; + KisPerspectiveGrid* perspectiveGrid; +}; + + +namespace { + + class KisResizeImageCmd : public KNamedCommand { + typedef KNamedCommand super; + + public: + KisResizeImageCmd(KisUndoAdapter *adapter, + KisImageSP img, + Q_INT32 width, + Q_INT32 height, + Q_INT32 oldWidth, + Q_INT32 oldHeight) : super(i18n("Resize Image")) + { + m_adapter = adapter; + m_img = img; + m_before = QSize(oldWidth, oldHeight); + m_after = QSize(width, height); + } + + virtual ~KisResizeImageCmd() + { + } + + public: + virtual void execute() + { + m_adapter->setUndo(false); + m_img->resize(m_after.width(), m_after.height()); + m_adapter->setUndo(true); + } + + virtual void unexecute() + { + m_adapter->setUndo(false); + m_img->resize(m_before.width(), m_before.height()); + m_adapter->setUndo(true); + } + + private: + KisUndoAdapter *m_adapter; + KisImageSP m_img; + QSize m_before; + QSize m_after; + }; + + // ------------------------------------------------------- + + class KisChangeLayersCmd : public KNamedCommand { + typedef KNamedCommand super; + + public: + KisChangeLayersCmd(KisUndoAdapter *adapter, KisImageSP img, + KisGroupLayerSP oldRootLayer, KisGroupLayerSP newRootLayer, const QString& name) + : super(name) + { + m_adapter = adapter; + m_img = img; + m_oldRootLayer = oldRootLayer; + m_newRootLayer = newRootLayer; + } + + virtual ~KisChangeLayersCmd() + { + } + + public: + virtual void execute() + { + m_adapter->setUndo(false); + m_img->setRootLayer(m_newRootLayer); + m_adapter->setUndo(true); + m_img->notifyLayersChanged(); + } + + virtual void unexecute() + { + m_adapter->setUndo(false); + m_img->setRootLayer(m_oldRootLayer); + m_adapter->setUndo(true); + m_img->notifyLayersChanged(); + } + + private: + KisUndoAdapter *m_adapter; + KisImageSP m_img; + KisGroupLayerSP m_oldRootLayer; + KisGroupLayerSP m_newRootLayer; + }; + + + // ------------------------------------------------------- + + class KisConvertImageTypeCmd : public KNamedCommand { + typedef KNamedCommand super; + + public: + KisConvertImageTypeCmd(KisUndoAdapter *adapter, KisImageSP img, + KisColorSpace * beforeColorSpace, KisColorSpace * afterColorSpace + ) : super(i18n("Convert Image Type")) + { + m_adapter = adapter; + m_img = img; + m_beforeColorSpace = beforeColorSpace; + m_afterColorSpace = afterColorSpace; + } + + virtual ~KisConvertImageTypeCmd() + { + } + + public: + virtual void execute() + { + m_adapter->setUndo(false); + + m_img->setColorSpace(m_afterColorSpace); + m_img->setProfile(m_afterColorSpace->getProfile()); + + m_adapter->setUndo(true); + } + + virtual void unexecute() + { + m_adapter->setUndo(false); + + m_img->setColorSpace(m_beforeColorSpace); + m_img->setProfile(m_beforeColorSpace->getProfile()); + + m_adapter->setUndo(true); + } + + private: + KisUndoAdapter *m_adapter; + KisImageSP m_img; + KisColorSpace * m_beforeColorSpace; + KisColorSpace * m_afterColorSpace; + }; + + + // ------------------------------------------------------- + + class KisImageCommand : public KNamedCommand { + typedef KNamedCommand super; + + public: + KisImageCommand(const QString& name, KisImageSP image); + virtual ~KisImageCommand() {} + + virtual void execute() = 0; + virtual void unexecute() = 0; + + protected: + void setUndo(bool undo); + + KisImageSP m_image; + }; + + KisImageCommand::KisImageCommand(const QString& name, KisImageSP image) : + super(name), m_image(image) + { + } + + void KisImageCommand::setUndo(bool undo) + { + if (m_image->undoAdapter()) { + m_image->undoAdapter()->setUndo(undo); + } + } + + + // ------------------------------------------------------- + + class KisLayerPositionCommand : public KisImageCommand { + typedef KisImageCommand super; + + public: + KisLayerPositionCommand(const QString& name, KisImageSP image, KisLayerSP layer, KisGroupLayerSP parent, KisLayerSP aboveThis) : super(name, image) + { + m_layer = layer; + m_oldParent = layer->parent(); + m_oldAboveThis = layer->nextSibling(); + m_newParent = parent; + m_newAboveThis = aboveThis; + } + + virtual void execute() + { + setUndo(false); + m_image->moveLayer(m_layer, m_newParent, m_newAboveThis); + setUndo(true); + } + + virtual void unexecute() + { + setUndo(false); + m_image->moveLayer(m_layer, m_oldParent, m_oldAboveThis); + setUndo(true); + } + + private: + KisLayerSP m_layer; + KisGroupLayerSP m_oldParent; + KisLayerSP m_oldAboveThis; + KisGroupLayerSP m_newParent; + KisLayerSP m_newAboveThis; + }; + + + // ------------------------------------------------------- + + class LayerAddCmd : public KisCommand { + typedef KisCommand super; + + public: + LayerAddCmd(KisUndoAdapter *adapter, KisImageSP img, KisLayerSP layer) : super(i18n("Add Layer"), adapter) + { + m_img = img; + m_layer = layer; + m_parent = layer->parent(); + m_aboveThis = layer->nextSibling(); + } + + virtual ~LayerAddCmd() + { + } + + virtual void execute() + { + adapter()->setUndo(false); + m_img->addLayer(m_layer, m_parent.data(), m_aboveThis); + adapter()->setUndo(true); + } + + virtual void unexecute() + { + adapter()->setUndo(false); + m_img->removeLayer(m_layer); + adapter()->setUndo(true); + } + + private: + KisImageSP m_img; + KisLayerSP m_layer; + KisGroupLayerSP m_parent; + KisLayerSP m_aboveThis; + }; + + // ------------------------------------------------------- + + class LayerRmCmd : public KNamedCommand { + typedef KNamedCommand super; + + public: + LayerRmCmd(KisUndoAdapter *adapter, KisImageSP img, + KisLayerSP layer, KisGroupLayerSP wasParent, KisLayerSP wasAbove) + : super(i18n("Remove Layer")) + { + m_adapter = adapter; + m_img = img; + m_layer = layer; + m_prevParent = wasParent; + m_prevAbove = wasAbove; + } + + virtual ~LayerRmCmd() + { + } + + virtual void execute() + { + m_adapter->setUndo(false); + m_img->removeLayer(m_layer); + m_adapter->setUndo(true); + } + + virtual void unexecute() + { + m_adapter->setUndo(false); + m_img->addLayer(m_layer, m_prevParent.data(), m_prevAbove); + m_adapter->setUndo(true); + } + + private: + KisUndoAdapter *m_adapter; + KisImageSP m_img; + KisLayerSP m_layer; + KisGroupLayerSP m_prevParent; + KisLayerSP m_prevAbove; + }; + + class LayerMoveCmd: public KNamedCommand { + typedef KNamedCommand super; + + public: + LayerMoveCmd(KisUndoAdapter *adapter, KisImageSP img, + KisLayerSP layer, KisGroupLayerSP wasParent, KisLayerSP wasAbove) + : super(i18n("Move Layer")) + { + m_adapter = adapter; + m_img = img; + m_layer = layer; + m_prevParent = wasParent; + m_prevAbove = wasAbove; + m_newParent = layer->parent(); + m_newAbove = layer->nextSibling(); + } + + virtual ~LayerMoveCmd() + { + } + + virtual void execute() + { + m_adapter->setUndo(false); + m_img->moveLayer(m_layer, m_newParent.data(), m_newAbove); + m_adapter->setUndo(true); + } + + virtual void unexecute() + { + m_adapter->setUndo(false); + m_img->moveLayer(m_layer, m_prevParent.data(), m_prevAbove); + m_adapter->setUndo(true); + } + + private: + KisUndoAdapter *m_adapter; + KisImageSP m_img; + KisLayerSP m_layer; + KisGroupLayerSP m_prevParent; + KisLayerSP m_prevAbove; + KisGroupLayerSP m_newParent; + KisLayerSP m_newAbove; + }; + + + // ------------------------------------------------------- + + class LayerPropsCmd : public KNamedCommand { + typedef KNamedCommand super; + + public: + LayerPropsCmd(KisLayerSP layer, + KisImageSP img, + KisUndoAdapter *adapter, + const QString& name, + Q_INT32 opacity, + const KisCompositeOp& compositeOp) : super(i18n("Layer Property Changes")) + { + m_layer = layer; + m_img = img; + m_adapter = adapter; + m_name = name; + m_opacity = opacity; + m_compositeOp = compositeOp; + } + + virtual ~LayerPropsCmd() + { + } + + public: + virtual void execute() + { + QString name = m_layer->name(); + Q_INT32 opacity = m_layer->opacity(); + KisCompositeOp compositeOp = m_layer->compositeOp(); + + m_adapter->setUndo(false); + m_img->setLayerProperties(m_layer, + m_opacity, + m_compositeOp, + m_name); + m_adapter->setUndo(true); + m_name = name; + m_opacity = opacity; + m_compositeOp = compositeOp; + m_layer->setDirty(); + } + + virtual void unexecute() + { + execute(); + } + + private: + KisUndoAdapter *m_adapter; + KisLayerSP m_layer; + KisImageSP m_img; + QString m_name; + Q_INT32 m_opacity; + KisCompositeOp m_compositeOp; + }; + + // ------------------------------------------------------- + + class LockImageCommand : public KNamedCommand { + typedef KNamedCommand super; + + public: + LockImageCommand(KisImageSP img, bool lockImage) : super("lock image") // Not for translation, this + { // is only ever used inside a macro command. + m_img = img; + m_lockImage = lockImage; + } + + virtual ~LockImageCommand() + { + } + + virtual void execute() + { + if (m_lockImage) { + m_img->lock(); + } else { + m_img->unlock(); + } + } + + virtual void unexecute() + { + if (m_lockImage) { + m_img->unlock(); + } else { + m_img->lock(); + } + } + + private: + KisImageSP m_img; + bool m_lockImage; + }; +} + +KisImage::KisImage(KisUndoAdapter *adapter, Q_INT32 width, Q_INT32 height, KisColorSpace * colorSpace, const QString& name) + : QObject(0, name.latin1()), KShared() +{ + init(adapter, width, height, colorSpace, name); + setName(name); + m_dcop = 0L; +} + +KisImage::KisImage(const KisImage& rhs) : QObject(), KShared(rhs) +{ + m_dcop = 0L; + if (this != &rhs) { + m_private = new KisImagePrivate(*rhs.m_private); + m_private->perspectiveGrid = new KisPerspectiveGrid(*rhs.m_private->perspectiveGrid); + m_uri = rhs.m_uri; + m_name = QString::null; + m_width = rhs.m_width; + m_height = rhs.m_height; + m_xres = rhs.m_xres; + m_yres = rhs.m_yres; + m_unit = rhs.m_unit; + m_colorSpace = rhs.m_colorSpace; + m_dirty = rhs.m_dirty; + m_adapter = rhs.m_adapter; + + m_bkg = new KisBackground(); + Q_CHECK_PTR(m_bkg); + + m_rootLayer = static_cast<KisGroupLayer*>(rhs.m_rootLayer->clone().data()); + connect(m_rootLayer, SIGNAL(sigDirty(QRect)), this, SIGNAL(sigImageUpdated(QRect))); + + m_annotations = rhs.m_annotations; // XXX the annotations would probably need to be deep-copied + + m_nserver = new KisNameServer(i18n("Layer %1"), rhs.m_nserver->currentSeed() + 1); + Q_CHECK_PTR(m_nserver); + + //m_guides = rhs.m_guides; + + // Set this as the current image for the layers + m_rootLayer->setImage(this); + // Set the active paint layer + if(rhs.activeLayer() != NULL) { + QString layerName = rhs.activeLayer()->name(); + // kdDebug(12345) << "KisImage::KisImage: active layer = " << layerName << "\n"; + KisLayerSP activeLayer = rootLayer()->findLayer(layerName); + Q_ASSERT(activeLayer); + activate(activeLayer); + } else { + activate(NULL); + } + } +} + + + +DCOPObject * KisImage::dcopObject() +{ + if (!m_dcop) { + m_dcop = new KisImageIface(this); + Q_CHECK_PTR(m_dcop); + } + return m_dcop; +} + +KisImage::~KisImage() +{ + delete m_private->perspectiveGrid; + delete m_private; + delete m_nserver; + delete m_dcop; +} + +QString KisImage::name() const +{ + return m_name; +} + +void KisImage::setName(const QString& name) +{ + if (!name.isEmpty()) + m_name = name; +} + +QString KisImage::description() const +{ + return m_description; +} + +void KisImage::setDescription(const QString& description) +{ + if (!description.isEmpty()) + m_description = description; +} + + +KisColor KisImage::backgroundColor() const +{ + return m_private->backgroundColor; +} + +void KisImage::setBackgroundColor(const KisColor & color) +{ + m_private->backgroundColor = color; +} + + +QString KisImage::nextLayerName() const +{ + if (m_nserver->currentSeed() == 0) { + m_nserver->number(); + return i18n("background"); + } + + return m_nserver->name(); +} + +void KisImage::rollBackLayerName() +{ + m_nserver->rollback(); +} + +void KisImage::init(KisUndoAdapter *adapter, Q_INT32 width, Q_INT32 height, KisColorSpace * colorSpace, const QString& name) +{ + Q_ASSERT(colorSpace); + + if (colorSpace == 0) { + colorSpace = KisMetaRegistry::instance()->csRegistry()->getRGB8(); + kdWarning(41010) << "No colorspace specified: using RGBA\n"; + } + + m_private = new KisImagePrivate(); + m_private->backgroundColor = KisColor(Qt::white, colorSpace); + m_private->lockCount = 0; + m_private->sizeChangedWhileLocked = false; + m_private->selectionChangedWhileLocked = false; + m_private->substrate = 0; + m_private->perspectiveGrid = new KisPerspectiveGrid(); + + m_adapter = adapter; + + m_nserver = new KisNameServer(i18n("Layer %1"), 1); + m_name = name; + + m_colorSpace = colorSpace; + m_bkg = new KisBackground(); + + m_rootLayer = new KisGroupLayer(this,"root", OPACITY_OPAQUE); + connect(m_rootLayer, SIGNAL(sigDirty(QRect)), this, SIGNAL(sigImageUpdated(QRect))); + + m_xres = 1.0; + m_yres = 1.0; + m_unit = KoUnit::U_PT; + m_dirty = false; + m_width = width; + m_height = height; +} + +bool KisImage::locked() const +{ + return m_private->lockCount != 0; +} + +void KisImage::lock() +{ + if (!locked()) { + if (m_rootLayer) disconnect(m_rootLayer, SIGNAL(sigDirty(QRect)), this, SIGNAL(sigImageUpdated(QRect))); + m_private->sizeChangedWhileLocked = false; + m_private->selectionChangedWhileLocked = false; + } + m_private->lockCount++; +} + +void KisImage::unlock() +{ + Q_ASSERT(locked()); + + if (locked()) { + m_private->lockCount--; + + if (m_private->lockCount == 0) { + if (m_private->sizeChangedWhileLocked) { + // A size change implies a full image update so only send this. + emit sigSizeChanged(m_width, m_height); + } else { + if (m_rootLayer->dirty()) emit sigImageUpdated( m_rootLayer->dirtyRect() ); + } + + if (m_private->selectionChangedWhileLocked) { + emit sigActiveSelectionChanged(this); + } + + if (m_rootLayer) connect(m_rootLayer, SIGNAL(sigDirty(QRect)), this, SIGNAL(sigImageUpdated(QRect))); + } + } +} + +void KisImage::emitSizeChanged() +{ + if (!locked()) { + emit sigSizeChanged(m_width, m_height); + } else { + m_private->sizeChangedWhileLocked = true; + } +} + +void KisImage::notifyLayerUpdated(KisLayerSP layer, QRect rc) +{ + emit sigLayerUpdated(layer, rc); +} + +void KisImage::resize(Q_INT32 w, Q_INT32 h, Q_INT32 x, Q_INT32 y, bool cropLayers) +{ + if (w != width() || h != height()) { + + lock(); + + if (undo()) { + if (cropLayers) + m_adapter->beginMacro(i18n("Crop Image")); + else + m_adapter->beginMacro(i18n("Resize Image")); + + m_adapter->addCommand(new LockImageCommand(this, true)); + m_adapter->addCommand(new KisResizeImageCmd(m_adapter, this, w, h, width(), height())); + } + + m_width = w; + m_height = h; + + if (cropLayers) { + KisCropVisitor v(QRect(x, y, w, h)); + m_rootLayer->accept(v); + } + + emitSizeChanged(); + + unlock(); + + if (undo()) { + m_adapter->addCommand(new LockImageCommand(this, false)); + m_adapter->endMacro(); + } + } +} + +void KisImage::resize(const QRect& rc, bool cropLayers) +{ + resize(rc.width(), rc.height(), rc.x(), rc.y(), cropLayers); +} + + +void KisImage::scale(double sx, double sy, KisProgressDisplayInterface *progress, KisFilterStrategy *filterStrategy) +{ + if (nlayers() == 0) return; // Nothing to scale + + // New image size. XXX: Pass along to discourage rounding errors? + Q_INT32 w, h; + w = (Q_INT32)(( width() * sx) + 0.5); + h = (Q_INT32)(( height() * sy) + 0.5); + + if (w != width() || h != height()) { + + lock(); + + if (undo()) { + m_adapter->beginMacro(i18n("Scale Image")); + m_adapter->addCommand(new LockImageCommand(this, true)); + } +#if 0 + 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 { +#endif + KisTransformVisitor visitor (this, sx, sy, 0.0, 0.0, 0.0, 0, 0, progress, filterStrategy); + m_rootLayer->accept(visitor); +// } + + if (undo()) { + m_adapter->addCommand(new KisResizeImageCmd(m_adapter, this, w, h, width(), height())); + } + + m_width = w; + m_height = h; + + emitSizeChanged(); + + unlock(); + + if (undo()) { + m_adapter->addCommand(new LockImageCommand(this, false)); + m_adapter->endMacro(); + } + } +} + + + +void KisImage::rotate(double radians, KisProgressDisplayInterface *progress) +{ + lock(); + + Q_INT32 w = width(); + Q_INT32 h = height(); + Q_INT32 tx = Q_INT32((w*cos(radians) - h*sin(radians) - w) / 2 + 0.5); + Q_INT32 ty = Q_INT32((h*cos(radians) + w*sin(radians) - h) / 2 + 0.5); + w = (Q_INT32)(width()*QABS(cos(radians)) + height()*QABS(sin(radians)) + 0.5); + h = (Q_INT32)(height()*QABS(cos(radians)) + width()*QABS(sin(radians)) + 0.5); + + tx -= (w - width()) / 2; + ty -= (h - height()) / 2; + + if (undo()) { + m_adapter->beginMacro(i18n("Rotate Image")); + m_adapter->addCommand(new LockImageCommand(this, true)); + } + + KisFilterStrategy *filter = KisFilterStrategyRegistry::instance()->get(KisID("Triangle")); + KisTransformVisitor visitor (this, 1.0, 1.0, 0, 0, radians, -tx, -ty, progress, filter); + m_rootLayer->accept(visitor); + + if (undo()) m_adapter->addCommand(new KisResizeImageCmd(undoAdapter(), this, w, h, width(), height())); + + m_width = w; + m_height = h; + + emitSizeChanged(); + + unlock(); + + if (undo()) { + m_adapter->addCommand(new LockImageCommand(this, false)); + m_adapter->endMacro(); + } +} + +void KisImage::shear(double angleX, double angleY, KisProgressDisplayInterface *m_progress) +{ + const double pi=3.1415926535897932385; + + //new image size + Q_INT32 w=width(); + Q_INT32 h=height(); + + + if(angleX != 0 || angleY != 0){ + double deltaY=height()*QABS(tan(angleX*pi/180)*tan(angleY*pi/180)); + w = (Q_INT32) ( width() + QABS(height()*tan(angleX*pi/180)) ); + //ugly fix for the problem of having two extra pixels if only a shear along one + //axis is done. This has to be fixed in the cropping code in KisRotateVisitor! + if (angleX == 0 || angleY == 0) + h = (Q_INT32) ( height() + QABS(w*tan(angleY*pi/180)) ); + else if (angleX > 0 && angleY > 0) + h = (Q_INT32) ( height() + QABS(w*tan(angleY*pi/180))- 2 * deltaY + 2 ); + else if (angleX < 0 && angleY < 0) + h = (Q_INT32) ( height() + QABS(w*tan(angleY*pi/180))- 2 * deltaY + 2 ); + else + h = (Q_INT32) ( height() + QABS(w*tan(angleY*pi/180)) ); + } + + if (w != width() || h != height()) { + + lock(); + + if (undo()) { + m_adapter->beginMacro(i18n("Shear Image")); + m_adapter->addCommand(new LockImageCommand(this, true)); + } + + KisShearVisitor v(angleX, angleY, m_progress); + v.setUndoAdapter(undoAdapter()); + rootLayer()->accept(v); + + if (undo()) m_adapter->addCommand(new KisResizeImageCmd(m_adapter, this, w, h, width(), height())); + + m_width = w; + m_height = h; + + emitSizeChanged(); + + unlock(); + + if (undo()) { + m_adapter->addCommand(new LockImageCommand(this, false)); + m_adapter->endMacro(); + } + } +} + +void KisImage::convertTo(KisColorSpace * dstColorSpace, Q_INT32 renderingIntent) +{ + if ( m_colorSpace == dstColorSpace ) + { + return; + } + + lock(); + + KisColorSpace * oldCs = m_colorSpace; + + if (undo()) { + m_adapter->beginMacro(i18n("Convert Image Type")); + m_adapter->addCommand(new LockImageCommand(this, true)); + } + + setColorSpace(dstColorSpace); + + KisColorSpaceConvertVisitor visitor(dstColorSpace, renderingIntent); + m_rootLayer->accept(visitor); + + unlock(); + + emit sigLayerPropertiesChanged( m_activeLayer ); + + if (undo()) { + + m_adapter->addCommand(new KisConvertImageTypeCmd(undoAdapter(), this, + oldCs, dstColorSpace)); + m_adapter->addCommand(new LockImageCommand(this, false)); + m_adapter->endMacro(); + } +} + +KisProfile * KisImage::getProfile() const +{ + return colorSpace()->getProfile(); +} + +void KisImage::setProfile(const KisProfile * profile) +{ + if (profile == 0) return; + + KisColorSpace * dstCs= KisMetaRegistry::instance()->csRegistry()->getColorSpace( colorSpace()->id(), + profile); + if (dstCs) { + + lock(); + + KisColorSpace * oldCs = colorSpace(); + setColorSpace(dstCs); + emit(sigProfileChanged(const_cast<KisProfile *>(profile))); + + KisChangeProfileVisitor visitor(oldCs, dstCs); + m_rootLayer->accept(visitor); + + unlock(); + } +} + +double KisImage::xRes() +{ + return m_xres; +} + +double KisImage::yRes() +{ + return m_yres; +} + +void KisImage::setResolution(double xres, double yres) +{ + m_xres = xres; + m_yres = yres; +} + +Q_INT32 KisImage::width() const +{ + return m_width; +} + +Q_INT32 KisImage::height() const +{ + return m_height; +} + +KisPaintDeviceSP KisImage::activeDevice() +{ + if (KisPaintLayer* layer = dynamic_cast<KisPaintLayer*>(m_activeLayer.data())) { + return layer->paintDeviceOrMask(); + } + else if (KisAdjustmentLayer* layer = dynamic_cast<KisAdjustmentLayer*>(m_activeLayer.data())) { + if (layer->selection()) { + return layer->selection().data(); + } + } + else if (KisGroupLayer * layer = dynamic_cast<KisGroupLayer*>(m_activeLayer.data())) { + // Find first child + KisLayerSP child = layer->lastChild(); + while(child) + { + if (KisPaintLayer* layer = dynamic_cast<KisPaintLayer*>(child.data())) { + return layer->paintDevice(); + } + child = child->prevSibling(); + } + KisLayerSP sibling = layer->nextSibling(); + while (sibling) { + if (KisPaintLayer* layer = dynamic_cast<KisPaintLayer*>(sibling.data())) { + return layer->paintDevice(); + } + sibling = sibling->nextSibling(); + } + } + else if (KisLayerSP layer = m_activeLayer) { + // A weird layer -- let's not return it, but a sibling + KisLayerSP sibling = layer->nextSibling(); + while (sibling) { + if (KisPaintLayer* layer = dynamic_cast<KisPaintLayer*>(sibling.data())) { + return layer->paintDevice(); + } + sibling = sibling->nextSibling(); + } + } + // XXX: We're buggered! + return 0; +} + +KisLayerSP KisImage::newLayer(const QString& name, Q_UINT8 opacity, const KisCompositeOp& compositeOp, KisColorSpace * colorstrategy) +{ + KisPaintLayer * layer; + if (colorstrategy) + layer = new KisPaintLayer(this, name, opacity, colorstrategy); + else + layer = new KisPaintLayer(this, name, opacity); + Q_CHECK_PTR(layer); + + if (compositeOp.isValid()) + layer->setCompositeOp(compositeOp); + layer->setVisible(true); + + if (m_activeLayer != 0) { + addLayer(layer, m_activeLayer->parent().data(), m_activeLayer->nextSibling()); + } + else { + addLayer(layer, m_rootLayer, 0); + } + activate(layer); + + return layer; +} + +void KisImage::setLayerProperties(KisLayerSP layer, Q_UINT8 opacity, const KisCompositeOp& compositeOp, const QString& name) +{ + if (layer && (layer->opacity() != opacity || layer->compositeOp() != compositeOp || layer->name() != name)) { + if (undo()) { + QString oldname = layer->name(); + Q_INT32 oldopacity = layer->opacity(); + KisCompositeOp oldCompositeOp = layer->compositeOp(); + layer->setName(name); + layer->setOpacity(opacity); + layer->setCompositeOp(compositeOp); + m_adapter->addCommand(new LayerPropsCmd(layer, this, m_adapter, oldname, oldopacity, oldCompositeOp)); + } else { + layer->setName(name); + layer->setOpacity(opacity); + layer->setCompositeOp(compositeOp); + } + } +} + +KisGroupLayerSP KisImage::rootLayer() const +{ + return m_rootLayer; +} + +KisLayerSP KisImage::activeLayer() const +{ + return m_activeLayer; +} + +KisPaintDeviceSP KisImage::projection() +{ + return m_rootLayer->projection(QRect(0, 0, m_width, m_height)); +} + +KisLayerSP KisImage::activate(KisLayerSP layer) +{ + if (layer != m_activeLayer) { + if (m_activeLayer) m_activeLayer->deactivate(); + m_activeLayer = layer; + if (m_activeLayer) m_activeLayer->activate(); + emit sigLayerActivated(m_activeLayer); + emit sigMaskInfoChanged(); + } + + return layer; +} + +KisLayerSP KisImage::findLayer(const QString& name) const +{ + return rootLayer()->findLayer(name); +} + +KisLayerSP KisImage::findLayer(int id) const +{ + return rootLayer()->findLayer(id); +} + + +bool KisImage::addLayer(KisLayerSP layer, KisGroupLayerSP parent) +{ + return addLayer(layer, parent, parent->firstChild()); +} + +bool KisImage::addLayer(KisLayerSP layer, KisGroupLayerSP parent, KisLayerSP aboveThis) +{ + if (!parent) + return false; + + const bool success = parent->addLayer(layer, aboveThis); + if (success) + { + KisPaintLayerSP player = dynamic_cast<KisPaintLayer*>(layer.data()); + if (player != 0) { + + // XXX: This should also be done whenever a layer grows! + QValueVector<KisPaintDeviceAction *> actions = KisMetaRegistry::instance() -> + csRegistry()->paintDeviceActionsFor(player->paintDevice()->colorSpace()); + for (uint i = 0; i < actions.count(); i++) { + actions.at(i)->act(player.data()->paintDevice(), width(), height()); + } + + connect(player, SIGNAL(sigMaskInfoChanged()), this, SIGNAL(sigMaskInfoChanged())); + } + + if (layer->extent().isValid()) layer->setDirty(); + + if (!layer->temporary()) { + emit sigLayerAdded(layer); + activate(layer); + } + + + if (!layer->temporary() && undo()) { + m_adapter->addCommand(new LayerAddCmd(m_adapter, this, layer)); + } + } + + return success; +} + +bool KisImage::removeLayer(KisLayerSP layer) +{ + if (!layer || layer->image() != this) + return false; + + if (KisGroupLayerSP parent = layer->parent()) { + // Adjustment layers should mark the layers underneath them, whose rendering + // they have cached, diryt on removal. Otherwise, the group won't be re-rendered. + KisAdjustmentLayer * al = dynamic_cast<KisAdjustmentLayer*>(layer.data()); + if (al) { + QRect r = al->extent(); + lock(); // Lock the image, because we are going to dirty a lot of layers + KisLayerSP l = layer->nextSibling(); + while (l) { + KisAdjustmentLayer * al2 = dynamic_cast<KisAdjustmentLayer*>(l.data()); + l->setDirty(r, false); + if (al2 != 0) break; + l = l->nextSibling(); + } + unlock(); + } + KisPaintLayerSP player = dynamic_cast<KisPaintLayer*>(layer.data()); + if (player != 0) { + disconnect(player, SIGNAL(sigMaskInfoChanged()), + this, SIGNAL(sigMaskInfoChanged())); + } + KisLayerSP l = layer->prevSibling(); + QRect r = layer->extent(); + while (l) { + l->setDirty(r, false); + l = l->prevSibling(); + } + + KisLayerSP wasAbove = layer->nextSibling(); + KisLayerSP wasBelow = layer->prevSibling(); + const bool wasActive = layer == activeLayer(); + // sigLayerRemoved can set it to 0, we don't want that in the else of wasActive! + KisLayerSP actLayer = activeLayer(); + const bool success = parent->removeLayer(layer); + if (success) { + layer->setImage(0); + if (!layer->temporary() && undo()) { + m_adapter->addCommand(new LayerRmCmd(m_adapter, this, layer, parent, wasAbove)); + } + if (!layer->temporary()) { + emit sigLayerRemoved(layer, parent, wasAbove); + if (wasActive) { + if (wasBelow) + activate(wasBelow); + else if (wasAbove) + activate(wasAbove); + else if (parent != rootLayer()) + activate(parent.data()); + else + activate(rootLayer()->firstChild()); + } else { + activate(actLayer); + } + } + } + return success; + } + + return false; +} + +bool KisImage::raiseLayer(KisLayerSP layer) +{ + if (!layer) + return false; + return moveLayer(layer, layer->parent().data(), layer->prevSibling()); +} + +bool KisImage::lowerLayer(KisLayerSP layer) +{ + if (!layer) + return false; + if (KisLayerSP next = layer->nextSibling()) + return moveLayer(layer, layer->parent().data(), next->nextSibling()); + return false; +} + +bool KisImage::toTop(KisLayerSP layer) +{ + if (!layer) + return false; + return moveLayer(layer, rootLayer(), rootLayer()->firstChild()); +} + +bool KisImage::toBottom(KisLayerSP layer) +{ + if (!layer) + return false; + return moveLayer(layer, rootLayer(), 0); +} + +bool KisImage::moveLayer(KisLayerSP layer, KisGroupLayerSP parent, KisLayerSP aboveThis) +{ + if (!parent) + return false; + + KisGroupLayerSP wasParent = layer->parent(); + KisLayerSP wasAbove = layer->nextSibling(); + + if (wasParent.data() == parent.data() && wasAbove.data() == aboveThis.data()) + return false; + + lock(); + + if (!wasParent->removeLayer(layer)) { + unlock(); + return false; + } + + const bool success = parent->addLayer(layer, aboveThis); + + layer->setDirty(); + + unlock(); + + if (success) + { + emit sigLayerMoved(layer, wasParent, wasAbove); + if (undo()) + m_adapter->addCommand(new LayerMoveCmd(m_adapter, this, layer, wasParent, wasAbove)); + } + else //we already removed the layer above, but re-adding it failed, so... + { + emit sigLayerRemoved(layer, wasParent, wasAbove); + if (undo()) + m_adapter->addCommand(new LayerRmCmd(m_adapter, this, layer, wasParent, wasAbove)); + } + + return success; +} + +Q_INT32 KisImage::nlayers() const +{ + return rootLayer()->numLayers() - 1; +} + +Q_INT32 KisImage::nHiddenLayers() const +{ + return rootLayer()->numLayers(KisLayer::Hidden); +} + +void KisImage::flatten() +{ + KisGroupLayerSP oldRootLayer = m_rootLayer; + disconnect(oldRootLayer, SIGNAL(sigDirty(QRect)), this, SIGNAL(sigImageUpdated(QRect))); + + KisPaintLayer *dst = new KisPaintLayer(this, nextLayerName(), OPACITY_OPAQUE, colorSpace()); + Q_CHECK_PTR(dst); + + QRect rc = mergedImage()->extent(); + + KisPainter gc(dst->paintDevice()); + gc.bitBlt(rc.x(), rc.y(), COMPOSITE_COPY, mergedImage(), OPACITY_OPAQUE, rc.left(), rc.top(), rc.width(), rc.height()); + + m_rootLayer = new KisGroupLayer(this, "", OPACITY_OPAQUE); + connect(m_rootLayer, SIGNAL(sigDirty(QRect)), this, SIGNAL(sigImageUpdated(QRect))); + + if (undo()) { + m_adapter->beginMacro(i18n("Flatten Image")); + m_adapter->addCommand(new LockImageCommand(this, true)); + m_adapter->addCommand(new KisChangeLayersCmd(m_adapter, this, oldRootLayer, m_rootLayer, "")); + } + + lock(); + + addLayer(dst, m_rootLayer, 0); + activate(dst); + + unlock(); + + notifyLayersChanged(); + + if (undo()) { + m_adapter->addCommand(new LockImageCommand(this, false)); + m_adapter->endMacro(); + } +} + + +void KisImage::mergeLayer(KisLayerSP layer) +{ + KisPaintLayer *player = new KisPaintLayer(this, layer->name(), OPACITY_OPAQUE, colorSpace()); + Q_CHECK_PTR(player); + + QRect rc = layer->extent() | layer->nextSibling()->extent(); + + undoAdapter()->beginMacro(i18n("Merge with Layer Below")); + + //Abuse the merge visitor to only merge two layers (if either are groups they'll recursively merge) + KisMergeVisitor visitor(player->paintDevice(), rc); + layer->nextSibling()->accept(visitor); + layer->accept(visitor); + + removeLayer(layer->nextSibling()); + addLayer(player, layer->parent(), layer); + removeLayer(layer); + + undoAdapter()->endMacro(); +} + + +void KisImage::setModified() +{ + emit sigImageModified(); +} + +void KisImage::renderToPainter(Q_INT32 x1, + Q_INT32 y1, + Q_INT32 x2, + Q_INT32 y2, + QPainter &painter, + KisProfile * monitorProfile, + PaintFlags paintFlags, + float exposure) +{ + + QImage img = convertToQImage(x1, y1, x2, y2, monitorProfile, exposure); + + Q_INT32 w = x2 - x1 + 1; + Q_INT32 h = y2 - y1 + 1; + + + if (paintFlags & PAINT_BACKGROUND) { + m_bkg->paintBackground(img, x1, y1); + img.setAlphaBuffer(false); + } + + if (paintFlags & PAINT_SELECTION) { + if (m_activeLayer != 0) { + m_activeLayer->paintSelection(img, x1, y1, w, h); + } + } + + if (paintFlags & PAINT_MASKINACTIVELAYERS) { + if (m_activeLayer != 0) { + m_activeLayer->paintMaskInactiveLayers(img, x1, y1, w, h); + } + } + + painter.drawImage(x1, y1, img, 0, 0, w, h); +} + +QImage KisImage::convertToQImage(Q_INT32 x1, + Q_INT32 y1, + Q_INT32 x2, + Q_INT32 y2, + KisProfile * profile, + float exposure) +{ + Q_INT32 w = x2 - x1 + 1; + Q_INT32 h = y2 - y1 + 1; + + KisPaintDeviceSP dev = m_rootLayer->projection(QRect(x1, y1, w, h)); + QImage img = dev->convertToQImage(profile, x1, y1, w, h, exposure); + + if (!img.isNull()) { + +#ifdef __BIG_ENDIAN__ + uchar * data = img.bits(); + for (int i = 0; i < w * h; ++i) { + uchar r, g, b, a; + a = data[0]; + b = data[1]; + g = data[2]; + r = data[3]; + data[0] = r; + data[1] = g; + data[2] = b; + data[3] = a; + data += 4; + } +#endif + + return img; + } + + return QImage(); +} + +QImage KisImage::convertToQImage(const QRect& r, const QSize& scaledImageSize, KisProfile *profile, PaintFlags paintFlags, float exposure) +{ + + if (r.isEmpty() || scaledImageSize.isEmpty()) { + return QImage(); + } + + Q_INT32 imageWidth = width(); + Q_INT32 imageHeight = height(); + Q_UINT32 pixelSize = colorSpace()->pixelSize(); + + double xScale = static_cast<double>(imageWidth) / scaledImageSize.width(); + double yScale = static_cast<double>(imageHeight) / scaledImageSize.height(); + + QRect srcRect; + + srcRect.setLeft(static_cast<int>(r.left() * xScale)); + srcRect.setRight(static_cast<int>(ceil((r.right() + 1) * xScale)) - 1); + srcRect.setTop(static_cast<int>(r.top() * yScale)); + srcRect.setBottom(static_cast<int>(ceil((r.bottom() + 1) * yScale)) - 1); + + KisPaintDeviceSP mergedImage = m_rootLayer->projection(srcRect); + QTime t; + t.start(); + + Q_UINT8 *scaledImageData = new Q_UINT8[r.width() * r.height() * pixelSize]; + + Q_UINT8 *imageRow = new Q_UINT8[srcRect.width() * pixelSize]; + const Q_INT32 imageRowX = srcRect.x(); + + for (Q_INT32 y = 0; y < r.height(); ++y) { + + Q_INT32 dstY = r.y() + y; + Q_INT32 dstX = r.x(); + Q_INT32 srcY = (dstY * imageHeight) / scaledImageSize.height(); + + mergedImage->readBytes(imageRow, imageRowX, srcY, srcRect.width(), 1); + + Q_UINT8 *dstPixel = scaledImageData + (y * r.width() * pixelSize); + Q_UINT32 columnsRemaining = r.width(); + + while (columnsRemaining > 0) { + + Q_INT32 srcX = (dstX * imageWidth) / scaledImageSize.width(); + + memcpy(dstPixel, imageRow + ((srcX - imageRowX) * pixelSize), pixelSize); + + ++dstX; + dstPixel += pixelSize; + --columnsRemaining; + } + } + kdDebug() << "Time elapsed scaling image: " << t.elapsed() << endl; + + delete [] imageRow; + + QImage image = colorSpace()->convertToQImage(scaledImageData, r.width(), r.height(), profile, INTENT_PERCEPTUAL, exposure); + delete [] scaledImageData; + +#ifdef __BIG_ENDIAN__ + uchar * data = image.bits(); + for (int i = 0; i < image.width() * image.height(); ++i) { + uchar r, g, b, a; + a = data[0]; + b = data[1]; + g = data[2]; + r = data[3]; + data[0] = r; + data[1] = g; + data[2] = b; + data[3] = a; + data += 4; + } +#endif + + if (paintFlags & PAINT_BACKGROUND) { + m_bkg->paintBackground(image, r, scaledImageSize, QSize(imageWidth, imageHeight)); + image.setAlphaBuffer(false); + } + + if (paintFlags & PAINT_SELECTION) { + if (m_activeLayer != 0) { + m_activeLayer->paintSelection(image, r, scaledImageSize, QSize(imageWidth, imageHeight)); + } + } + + /*if (paintFlags & PAINT_MASKINACTIVELAYERS) { + if (m_activeLayer != 0) { + m_activeLayer->paintMaskInactiveLayers(img, x1, y1, w, h); + } + }*/ + + return image; +} + +KisPaintDeviceSP KisImage::mergedImage() +{ + return m_rootLayer->projection(QRect(0, 0, m_width, m_height)); +} + +KisColor KisImage::mergedPixel(Q_INT32 x, Q_INT32 y) +{ + return m_rootLayer->projection(QRect(x, y, 1, 1))->colorAt(x, y); +} + +void KisImage::notifyLayersChanged() +{ + emit sigLayersChanged(rootLayer()); +} + +void KisImage::notifyPropertyChanged(KisLayerSP layer) +{ + emit sigLayerPropertiesChanged(layer); +} + +void KisImage::notifyImageLoaded() +{ +} + +QRect KisImage::bounds() const +{ + return QRect(0, 0, width(), height()); +} + + +void KisImage::setUndoAdapter(KisUndoAdapter * adapter) +{ + m_adapter = adapter; +} + + +KisUndoAdapter* KisImage::undoAdapter() const +{ + return m_adapter; +} + +bool KisImage::undo() const +{ + return (m_adapter && m_adapter->undo()); +} + +//KisGuideMgr *KisImage::guides() const +//{ +// return const_cast<KisGuideMgr*>(&m_guides); +//} + +void KisImage::slotSelectionChanged() +{ + slotSelectionChanged(bounds()); +} + +void KisImage::slotSelectionChanged(const QRect& r) +{ + QRect r2(r.x() - 1, r.y() - 1, r.width() + 2, r.height() + 2); + + if (!locked()) { + emit sigActiveSelectionChanged(this); + emit sigSelectionChanged(this); + } else { + m_private->selectionChangedWhileLocked = true; + } +} + +KisColorSpace * KisImage::colorSpace() const +{ + return m_colorSpace; +} + +void KisImage::setColorSpace(KisColorSpace * colorSpace) +{ + m_colorSpace = colorSpace; + m_rootLayer->resetProjection(); + emit sigColorSpaceChanged(colorSpace); +} + +void KisImage::setRootLayer(KisGroupLayerSP rootLayer) +{ + disconnect(m_rootLayer, SIGNAL(sigDirty(QRect)), this, SIGNAL(sigImageUpdated(QRect))); + + m_rootLayer = rootLayer; + + if (!locked()) { + connect(m_rootLayer, SIGNAL(sigDirty(QRect)), this, SIGNAL(sigImageUpdated(QRect))); + } + activate(m_rootLayer->firstChild()); +} + +void KisImage::addAnnotation(KisAnnotationSP annotation) +{ + // Find the icc annotation, if there is one + vKisAnnotationSP_it it = m_annotations.begin(); + while (it != m_annotations.end()) { + if ((*it)->type() == annotation->type()) { + *it = annotation; + return; + } + ++it; + } + m_annotations.push_back(annotation); +} + +KisAnnotationSP KisImage::annotation(QString type) +{ + vKisAnnotationSP_it it = m_annotations.begin(); + while (it != m_annotations.end()) { + if ((*it)->type() == type) { + return *it; + } + ++it; + } + return 0; +} + +void KisImage::removeAnnotation(QString type) +{ + vKisAnnotationSP_it it = m_annotations.begin(); + while (it != m_annotations.end()) { + if ((*it)->type() == type) { + m_annotations.erase(it); + return; + } + ++it; + } +} + +vKisAnnotationSP_it KisImage::beginAnnotations() +{ + KisProfile * profile = colorSpace()->getProfile(); + KisAnnotationSP annotation; + + if (profile) + annotation = profile->annotation(); + + if (annotation) + addAnnotation(annotation); + else + removeAnnotation("icc"); + + return m_annotations.begin(); +} + +vKisAnnotationSP_it KisImage::endAnnotations() +{ + return m_annotations.end(); +} + +KisBackgroundSP KisImage::background() const +{ + return m_bkg; +} + +KisPerspectiveGrid* KisImage::perspectiveGrid() +{ + return m_private->perspectiveGrid; +} + +#include "kis_image.moc" + diff --git a/krita/core/kis_image.h b/krita/core/kis_image.h new file mode 100644 index 00000000..fccb2fd3 --- /dev/null +++ b/krita/core/kis_image.h @@ -0,0 +1,460 @@ +/* + * Copyright (c) 2002 Patrick Julien <freak@codepimps.org> + * 2006 Boudewijn Rempt <boud@valdyas.org> + * + * 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_IMAGE_H_ +#define KIS_IMAGE_H_ + +#include <qobject.h> +#include <qstring.h> +#include <qvaluevector.h> +#include <qmutex.h> + +#include <config.h> + +#include <ksharedptr.h> +#include <kurl.h> + +#include "KoUnit.h" + +#include "kis_composite_op.h" +#include "kis_global.h" +#include "kis_types.h" +#include "kis_annotation.h" +#include "kis_paint_device.h" + +#include <koffice_export.h> + + +class DCOPObject; +class KCommand; + +class KoCommandHistory; + +class KisColorSpace; +class KisNameServer; +class KisUndoAdapter; +class KisPainter; +class KCommand; +class KisColor; +class KisFilterStrategy; +class KisImageIface; +class KisProfile; +class KisProgressDisplayInterface; +class KisPaintLayer; +class KisPerspectiveGrid; + +class KRITACORE_EXPORT KisImage : public QObject, public KShared { + Q_OBJECT + +public: + KisImage(KisUndoAdapter * adapter, Q_INT32 width, Q_INT32 height, KisColorSpace * colorSpace, const QString& name); + KisImage(const KisImage& rhs); + virtual ~KisImage(); + virtual DCOPObject *dcopObject(); + +public: + typedef enum enumPaintFlags { + PAINT_IMAGE_ONLY = 0, + PAINT_BACKGROUND = 1, + PAINT_SELECTION = 2, + PAINT_MASKINACTIVELAYERS = 4, + PAINT_EMBEDDED_RECT = 8 // If the current layer is an embedded part draw a rect around it + } PaintFlags; + + /// Paint the specified rect onto the painter, adjusting the colors using the + /// given profile. The exposure setting is used if the image has a high dynamic range. + virtual void renderToPainter(Q_INT32 x1, + Q_INT32 y1, + Q_INT32 x2, + Q_INT32 y2, + QPainter &painter, + KisProfile *profile, + PaintFlags paintFlags, + float exposure = 0.0f); + /** + * Render the projection onto a QImage. In contrast with the above method, the + * selection is not rendered. + */ + virtual QImage convertToQImage(Q_INT32 x1, + Q_INT32 y1, + Q_INT32 x2, + Q_INT32 y2, + KisProfile * profile, + float exposure = 0.0f); + + virtual QImage convertToQImage(const QRect& r, const QSize& fullImageSize, KisProfile *profile, PaintFlags paintFlags, float exposure = 0.0f); + + KisBackgroundSP background() const; + KisSubstrateSP substrate() const; + + +public: + + /** + * Lock the image to make sure no recompositing-causing signals get emitted + * while you're messing with the layers. Don't forget to unlock again. + */ + void lock(); + + /** + * Unlock the image to make sure the rest of Krita learns about changes in the image + * again. If the rootLayer is dirty on unlocking, an imgUpdated signal is sent out + * immediately. + */ + void unlock(); + + /** + * Returns true if the image is locked. + */ + bool locked() const; + + KisColor backgroundColor() const; + void setBackgroundColor(const KisColor & color); + + QString name() const; + void setName(const QString& name); + + QString description() const; + void setDescription(const QString& description); + + QString nextLayerName() const; + void rollBackLayerName(); + + KisPerspectiveGrid* perspectiveGrid(); + void createPerspectiveGrid(QPoint topLeft, QPoint topRight, QPoint bottomRight, QPoint bottomLeft); + /** + * Resize the image to the specified width and height. The resize + * method handles the creating on an undo step itself. + * + * @param w the new width of the image + * @param h the new height of the image + * @param x the x position of the crop on all layer if cropLayers is true + * @param y the y position of the crop on all layer if cropLayers is true + * @param cropLayers if true, all layers are cropped to the new size. + */ + void resize(Q_INT32 w, Q_INT32 h, Q_INT32 x = 0, Q_INT32 y = 0, bool cropLayers = false); + + /** + * Resize the image to the specified width and height. The resize + * method handles the creating on an undo step itself. + * + * @param rc the rect describing the new width and height of the image + * @param cropLayers if true, all layers are cropped to the new rect + */ + void resize(const QRect& rc, bool cropLayers = false); + + void scale(double sx, double sy, KisProgressDisplayInterface *m_progress, KisFilterStrategy *filterStrategy); + void rotate(double radians, KisProgressDisplayInterface *m_progress); + void shear(double angleX, double angleY, KisProgressDisplayInterface *m_progress); + + /** + * Convert the image and all its layers to the dstColorSpace + */ + void convertTo(KisColorSpace * dstColorSpace, Q_INT32 renderingIntent = INTENT_PERCEPTUAL); + + // Get the profile associated with this image + KisProfile * getProfile() const; + + /** + * Set the profile of the image to the new profile and do the same for + * all layers that have the same colorspace and profile as the image. + * It doesn't do any pixel conversion. + * + * This is essential if you have loaded an image that didn't + * have an embedded profile to which you want to attach the right profile. + */ + + void setProfile(const KisProfile * profile); + + /** + * Replace the current undo adapter with the specified undo adapter. + * The current undo adapter will _not_ be deleted. + */ + void setUndoAdapter(KisUndoAdapter * undoAdapter); + + /** + * Returns the current undo adapter. You can add new commands to the + * undo stack using the adapter + */ + KisUndoAdapter *undoAdapter() const; + + /** + * Returns true if this image wants undo information, false otherwise + */ + bool undo() const; + /** + * Tell the image it's modified; this emits the sigImageModified signal. This happens + * when the image needs to be saved + */ + void setModified(); + + KisColorSpace * colorSpace() const; + + // Resolution of the image == XXX: per inch? + double xRes(); + double yRes(); + void setResolution(double xres, double yres); + + Q_INT32 width() const; + Q_INT32 height() const; + + bool empty() const; + + /** + * returns a paintdevice that contains the merged layers of this image, within + * the bounds of this image (with the colorspace and profile of this image) + */ + KisPaintDeviceSP mergedImage(); + + /* + * Returns the colour of the merged image at pixel (x, y). + */ + KisColor mergedPixel(Q_INT32 x, Q_INT32 y); + + /// Creates a new paint layer with the specified properties, adds it to the image, and returns it. + KisLayerSP newLayer(const QString& name, Q_UINT8 opacity, + const KisCompositeOp& compositeOp = KisCompositeOp(), KisColorSpace * colorstrategy = 0); + + /// Get the active painting device. Returns 0 if the active layer does not have a paint device. + KisPaintDeviceSP activeDevice(); + + void setLayerProperties(KisLayerSP layer, Q_UINT8 opacity, const KisCompositeOp& compositeOp, const QString& name); + + KisGroupLayerSP rootLayer() const; + KisLayerSP activeLayer() const; + + /// Return the projection; that is, the complete, composited representation + /// of this image. + KisPaintDeviceSP projection(); + + KisLayerSP activate(KisLayerSP layer); + KisLayerSP findLayer(const QString& name) const; + KisLayerSP findLayer(int id) const; + + /// Move layer to specified position + bool moveLayer(KisLayerSP layer, KisGroupLayerSP parent, KisLayerSP aboveThis); + + /** + * Add an already existing layer to the image. The layer is put on top + * of the layers in the specified layergroup + * @param layer the layer to be added + * @param parent the parent layer + */ + bool addLayer(KisLayerSP layer, KisGroupLayerSP parent); + + /** + * Add already existing layer to image. + * + * @param layer the layer to be added + * @param parent the parent layer + * @param aboveThis in the list with child layers of the specified + * parent, add this layer above the specified sibling. + * if 0, the layer is put in the lowermost position in + * its group. + * @param notify If true, the image is immediately recomposited, if false, + * no recomposition is done yet. The added layer is all + * + * returns false if adding the layer didn't work, true if the layer got added + */ + bool addLayer(KisLayerSP layer, KisGroupLayerSP parent, KisLayerSP aboveThis); + + /// Remove layer + bool removeLayer(KisLayerSP layer); + + /// Move layer up one slot + bool raiseLayer(KisLayerSP layer); + + /// Move layer down one slot + bool lowerLayer(KisLayerSP layer); + + /// Move layer to top slot + bool toTop(KisLayerSP layer); + + /// Move layer to bottom slot + bool toBottom(KisLayerSP layer); + + Q_INT32 nlayers() const; + Q_INT32 nHiddenLayers() const; + + KCommand *raiseLayerCommand(KisLayerSP layer); + KCommand *lowerLayerCommand(KisLayerSP layer); + KCommand *topLayerCommand(KisLayerSP layer); + KCommand *bottomLayerCommand(KisLayerSP layer); + + /** + * Merge all visible layers and discard hidden ones. + * The resulting layer will be activated. + */ + void flatten(); + + /** + * Merge the specified layer with the layer + * below this layer, remove the specified layer. + */ + void mergeLayer(KisLayerSP l); + + QRect bounds() const; + + /// use if the layers have changed _completely_ (eg. when flattening) + void notifyLayersChanged(); + + void notifyPropertyChanged(KisLayerSP layer); + + void notifyImageLoaded(); + + void notifyLayerUpdated(KisLayerSP layer, QRect rc); + + void setColorSpace(KisColorSpace * colorSpace); + void setRootLayer(KisGroupLayerSP rootLayer); + + //KisGuideMgr *guides() const; + + /** + * Add an annotation for this image. This can be anything: Gamma, EXIF, etc. + * Note that the "icc" annotation is reserved for the colour strategies. + * If the annotation already exists, overwrite it with this one. + */ + void addAnnotation(KisAnnotationSP annotation); + + /** get the annotation with the given type, can return 0 */ + KisAnnotationSP annotation(QString type); + + /** delete the annotation, if the image contains it */ + void removeAnnotation(QString type); + + /** + * Start of an iteration over the annotations of this image (including the ICC Profile) + */ + vKisAnnotationSP_it beginAnnotations(); + + /** end of an iteration over the annotations of this image */ + vKisAnnotationSP_it endAnnotations(); + +signals: + + void sigActiveSelectionChanged(KisImageSP image); + void sigSelectionChanged(KisImageSP image); + void sigSelectionChanged(KisImageSP image, const QRect& rect); + + /// Emitted after a different layer is made active. + void sigLayerActivated(KisLayerSP layer); + + /// Emitted after a layer is added: you can find out where by asking it for its parent(), et al. + void sigLayerAdded(KisLayerSP layer); + + /** Emitted after a layer is removed. + It's no longer in the image, but still exists, so @p layer is valid. + + @param layer the removed layer + @param parent the parent of the layer, before it was removed + @param wasAboveThis the layer it was above, before it was removed. + */ + void sigLayerRemoved(KisLayerSP layer, KisGroupLayerSP wasParent, KisLayerSP wasAboveThis); + + /** Emitted after a layer is moved to a different position under its parent layer, or its parent changes. + + @param previousParent the parent of the layer, before it was moved + @param wasAboveThis the layer it was above, before it was moved. + */ + void sigLayerMoved(KisLayerSP layer, KisGroupLayerSP previousParent, KisLayerSP wasAboveThis); + + /// Emitted after a layer's properties (visible, locked, opacity, composite op, name, ...) change + void sigLayerPropertiesChanged(KisLayerSP layer); + + /** Emitted when the list of layers has changed completely. + This means e.g. when the image is flattened, but not when it is rotated, + as the layers only change internally then. + */ + void sigLayersChanged(KisGroupLayerSP rootLayer); + + /** + * Emitted whenever an action has caused the image to be recomposited. This happens + * after calls to recomposite(). + * + * @param rc The rect that has been recomposited. + */ + void sigImageUpdated(QRect rc); + + /** + * Emitted whenever a layer is modified. + * + * @param layer The layer that has been modified. + * @param rc The rectangle that has been modified. + */ + void sigLayerUpdated(KisLayerSP layer, QRect rc); + + /** + * Emitted whenever the image has been modified, so that it doesn't match with the version saved on disk. + */ + void sigImageModified(); + + void sigSizeChanged(Q_INT32 w, Q_INT32 h); + void sigProfileChanged(KisProfile * profile); + void sigColorSpaceChanged(KisColorSpace* cs); + + + /// Emitted when any layer's mask info got updated (or when the current layer changes) + void sigMaskInfoChanged(); +public slots: + void slotSelectionChanged(); + void slotSelectionChanged(const QRect& r); + + +private: + KisImage& operator=(const KisImage& rhs); + void init(KisUndoAdapter * adapter, Q_INT32 width, Q_INT32 height, KisColorSpace * colorSpace, const QString& name); + void emitSizeChanged(); + +private: + + KURL m_uri; + QString m_name; + QString m_description; + + Q_INT32 m_width; + Q_INT32 m_height; + + double m_xres; + double m_yres; + + KoUnit::Unit m_unit; + + KisColorSpace * m_colorSpace; + + bool m_dirty; + QRect m_dirtyRect; + + KisBackgroundSP m_bkg; + + KisGroupLayerSP m_rootLayer; // The layers are contained in here + KisLayerSP m_activeLayer; + + KisNameServer *m_nserver; + KisUndoAdapter *m_adapter; + //KisGuideMgr m_guides; + + DCOPObject *m_dcop; + + vKisAnnotationSP m_annotations; + + class KisImagePrivate; + KisImagePrivate * m_private; + +}; + +#endif // KIS_IMAGE_H_ diff --git a/krita/core/kis_image_iface.cc b/krita/core/kis_image_iface.cc new file mode 100644 index 00000000..5a74c480 --- /dev/null +++ b/krita/core/kis_image_iface.cc @@ -0,0 +1,97 @@ +/* + * This file is part of the KDE project + * + * Copyright (C) 2002 Laurent Montel <lmontel@mandrakesoft.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 <kapplication.h> + +#include "kis_image_iface.h" +#include "kis_types.h" +#include "kis_image.h" +#include "kis_paint_device.h" +#include "kis_paint_device_iface.h" +#include <dcopclient.h> + +KisImageIface::KisImageIface( KisImage * img ) + : DCOPObject(img->name().utf8()) +{ + m_img = img; +} + +int KisImageIface::height() const +{ + return m_img->height(); +} + +int KisImageIface::width() const +{ + return m_img->width(); +} + +void KisImageIface::setName(const QString& name) +{ + m_img->setName( name ); +} + +void KisImageIface::rotateCCW() +{ + // XXX: Add progress display if there is a view + m_img->rotate(270, 0); +} + +void KisImageIface::rotateCW() +{ + // XXX: Add progressdisplay if there is a view + m_img->rotate(90, 0); +} + +void KisImageIface::rotate180() +{ + // XXX: Add progressdisplay if there is a view + m_img->rotate(180, 0); +} + +void KisImageIface::rotate(double angle) +{ + // XXX: Add progressdisplay if there is a view + angle *= M_PI/180; + m_img->rotate(angle, 0); +} + +DCOPRef KisImageIface::activeDevice() +{ + KisPaintDeviceSP dev = m_img->activeDevice(); + + if( !dev ) + return DCOPRef(); + else + return DCOPRef( kapp->dcopClient()->appId(), + dev->dcopObject()->objId(), + "KisPaintDeviceIface"); + +} + +DCOPRef KisImageIface::colorSpace() const +{ + KisColorSpace * cs = m_img->colorSpace(); + if ( !cs ) + return DCOPRef(); + else + return DCOPRef( kapp->dcopClient()->appId(), + cs->dcopObject()->objId(), + "KisColorSpaceIface" ); +} diff --git a/krita/core/kis_image_iface.h b/krita/core/kis_image_iface.h new file mode 100644 index 00000000..77baa516 --- /dev/null +++ b/krita/core/kis_image_iface.h @@ -0,0 +1,65 @@ +/* + * This file is part of the KDE project + * Copyright (C) 2002 Laurent Montel <lmontel@mandrakesoft.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_IMAGE_IFACE_H +#define KIS_IMAGE_IFACE_H + +#include <dcopref.h> +#include <dcopobject.h> + + +#include <qstring.h> + +class KisImage; +class KisPaintDeviceIface; + +class KisImageIface : virtual public DCOPObject +{ + K_DCOP +public: + KisImageIface( KisImage *img_ ); +k_dcop: + + int height() const; + int width() const; + + void setName(const QString& name); + + void rotateCCW(); + void rotateCW(); + void rotate180(); + void rotate(double angle); + + /** + * Get the active painting device. + */ + DCOPRef activeDevice(); + + /** + * Get the colorspace of this image + */ + DCOPRef colorSpace() const; + + +private: + + KisImage *m_img; +}; + +#endif diff --git a/krita/core/kis_imagepipe_brush.cc b/krita/core/kis_imagepipe_brush.cc new file mode 100644 index 00000000..ad745014 --- /dev/null +++ b/krita/core/kis_imagepipe_brush.cc @@ -0,0 +1,456 @@ +/* + * Copyright (c) 2004 Boudewijn Rempt <boud@valdyas.org> + * Copyright (c) 2005 Bart Coppens <kde@bartcoppens.be> + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#ifdef HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif + +#include <math.h> + +#include <netinet/in.h> +#include <limits.h> +#include <stdlib.h> + +#include <qimage.h> +#include <qpoint.h> +#include <qvaluevector.h> +#include <qfile.h> +#include <qregexp.h> +#include <qstringlist.h> +#include <qtextstream.h> + +#include <kdebug.h> +#include <klocale.h> +#include <kapplication.h> + +#include "kis_global.h" +#include "kis_paint_device.h" +#include "kis_imagepipe_brush.h" +#include "kis_brush.h" +#include "kis_alpha_mask.h" +#include "kis_layer.h" +#include "kis_meta_registry.h" +#include "kis_colorspace_factory_registry.h" + + +KisPipeBrushParasite::KisPipeBrushParasite(const QString& source) +{ + needsMovement = false; + QRegExp basicSplitter(" ", true); + QRegExp parasiteSplitter(":", true); + QStringList parasites = QStringList::split(basicSplitter, source); + for (uint i = 0; i < parasites.count(); i++) { + QStringList splitted = QStringList::split(parasiteSplitter, *parasites.at(i)); + if (splitted.count() != 2) { + kdWarning(41001) << "Wrong count for this parasite key/value:" << *parasites.at(i) << endl; + continue; + } + QString index = *splitted.at(0); + if (index == "dim") { + dim = (*splitted.at(1)).toInt(); + if (dim < 1 || dim > MaxDim) { + dim = 1; + } + } else if (index.startsWith("sel")) { + int selIndex = index.mid(strlen("sel")).toInt(); + if (selIndex >= 0 && selIndex < dim) { + QString selectionMode = *splitted.at(1); + if (selectionMode == "incremental") + selection[selIndex] = Incremental; + else if (selectionMode == "angular") { + selection[selIndex] = Angular; + needsMovement = true; + } else if (selectionMode == "random") + selection[selIndex] = Random; + else if (selectionMode == "pressure") + selection[selIndex] = Pressure; + else if (selectionMode == "xtilt") + selection[selIndex] = TiltX; + else if (selectionMode == "ytilt") + selection[selIndex] = TiltY; + else + selection[selIndex] = Constant; + } else { + kdWarning(41001)<< "Sel: wrong index: " << selIndex << "(dim = " << dim << ")" << endl; + } + } else if (index.startsWith("rank")) { + int rankIndex = index.mid(strlen("rank")).toInt(); + if (rankIndex < 0 || rankIndex > dim) { + kdWarning(41001) << "Rankindex out of range: " << rankIndex << endl; + continue; + } + rank[rankIndex] = (*splitted.at(1)).toInt(); + } else if (index == "ncells") { + ncells = (*splitted.at(1)).toInt(); + if (ncells < 1 ) { + kdWarning(41001) << "ncells out of range: " << ncells << endl; + ncells = 1; + } + } + } + + for (int i = 0; i < dim; i++) { + index[i] = 0; + } + + setBrushesCount(); +} + +void KisPipeBrushParasite::setBrushesCount() { + // I assume ncells is correct. If it isn't, complain to the parasite header. + brushesCount[0] = ncells / rank[0]; + for (int i = 1; i < dim; i++) { + brushesCount[i] = brushesCount[i-1] / rank[i]; + } +} + +bool KisPipeBrushParasite::saveToDevice(QIODevice* dev) const { + // write out something like + // <count> ncells:<count> dim:<dim> rank0:<rank0> sel0:<sel0> <...> + + QTextStream stream(dev); + /// FIXME things like step, placement and so are not added (nor loaded, as a matter of fact) + stream << ncells << " ncells:" << ncells << " dim:" << dim; + + for (int i = 0; i < dim; i++) { + stream << " rank" << i << ":" << rank[i] << " sel" << i << ":"; + switch (selection[i]) { + case Constant: stream << "constant"; break; + case Incremental: stream << "incremental"; break; + case Angular: stream << "angular"; break; + case Velocity: stream << "velocity"; break; + case Random: stream << "random"; break; + case Pressure: stream << "pressure"; break; + case TiltX: stream << "xtilt"; break; + case TiltY: stream << "ytilt"; break; + } + } + + return true; +} + +KisImagePipeBrush::KisImagePipeBrush(const QString& filename) : super(filename) +{ + m_brushType = INVALID; + m_numOfBrushes = 0; + m_currentBrush = 0; +} + +KisImagePipeBrush::KisImagePipeBrush(const QString& name, int w, int h, + QValueVector< QValueVector<KisPaintDevice*> > devices, + QValueVector<KisPipeBrushParasite::SelectionMode> modes) + : super("") +{ + Q_ASSERT(devices.count() == modes.count()); + Q_ASSERT(devices.count() > 0); + Q_ASSERT(devices.count() < 2); // XXX Multidimensionals not supported yet, change to MaxDim! + + setName(name); + + m_parasite.dim = devices.count(); + // XXX Change for multidim! : + m_parasite.ncells = devices.at(0).count(); + m_parasite.rank[0] = m_parasite.ncells; + m_parasite.selection[0] = modes.at(0); + // XXX needsmovement! + + m_parasite.setBrushesCount(); + + for (uint i = 0; i < devices.at(0).count(); i++) { + m_brushes.append(new KisBrush(devices.at(0).at(i), 0, 0, w, h)); + } + + setImage(m_brushes.at(0)->img()); + + m_brushType = PIPE_IMAGE; +} + +KisImagePipeBrush::~KisImagePipeBrush() +{ + m_brushes.setAutoDelete(true); + m_brushes.clear(); +} + +bool KisImagePipeBrush::load() +{ + QFile file(filename()); + file.open(IO_ReadOnly); + m_data = file.readAll(); + file.close(); + return init(); +} + +bool KisImagePipeBrush::init() +{ + // XXX: this doesn't correctly load the image pipe brushes yet. + + // XXX: This stuff is in utf-8, too. + // The first line contains the name -- this means we look until we arrive at the first newline + QValueVector<char> line1; + + Q_UINT32 i = 0; + + while (m_data[i] != '\n' && i < m_data.size()) { + line1.append(m_data[i]); + i++; + } + setName(i18n(QString::fromUtf8(&line1[0], i).ascii())); + + i++; // Skip past the first newline + + // The second line contains the number of brushes, separated by a space from the parasite + + // XXX: This stuff is in utf-8, too. + QValueVector<char> line2; + while (m_data[i] != '\n' && i < m_data.size()) { + line2.append(m_data[i]); + i++; + } + + QString paramline = QString::fromUtf8((&line2[0]), line2.size()); + Q_UINT32 m_numOfBrushes = paramline.left(paramline.find(' ')).toUInt(); + m_parasite = paramline.mid(paramline.find(' ') + 1); + i++; // Skip past the second newline + + Q_UINT32 numOfBrushes = 0; + while (numOfBrushes < m_numOfBrushes && i < m_data.size()){ + KisBrush * brush = new KisBrush(name() + "_" + numOfBrushes, + m_data, + i); + Q_CHECK_PTR(brush); + + m_brushes.append(brush); + + numOfBrushes++; + } + + if (!m_brushes.isEmpty()) { + setValid(true); + if (m_brushes.at( 0 )->brushType() == MASK) { + m_brushType = PIPE_MASK; + } + else { + m_brushType = PIPE_IMAGE; + } + setSpacing(m_brushes.at(m_brushes.count() - 1)->spacing()); + setWidth(m_brushes.at(0)->width()); + setHeight(m_brushes.at(0)->height()); + } + + m_data.resize(0); + return true; +} + +bool KisImagePipeBrush::save() +{ + QFile file(filename()); + file.open(IO_WriteOnly | IO_Truncate); + bool ok = saveToDevice(&file); + file.close(); + return ok; +} + +bool KisImagePipeBrush::saveToDevice(QIODevice* dev) const +{ + QCString utf8Name = name().utf8(); // Names in v2 brushes are in UTF-8 + char const* name = utf8Name.data(); + int len = qstrlen(name); + + if (parasite().dim != 1) { + kdWarning(41001) << "Save to file for pipe brushes with dim != not yet supported!" << endl; + return false; + } + + // Save this pipe brush: first the header, and then all individual brushes consecutively + // (this needs some care for when we have > 1 dimension), FIXME + + // Gimp Pipe Brush header format: Name\n<number of brushes> <parasite>\n + + // The name\n + if (dev->writeBlock(name, len) == -1) + return false; + + if (dev->putch('\n') == -1) + return false; + + // Write the parasite (also writes number of brushes) + if (!m_parasite.saveToDevice(dev)) + return false; + + if (dev->putch('\n') == -1) + return false; + + // <gbr brushes> + for (uint i = 0; i < m_brushes.count(); i++) + if (!m_brushes.at(i)->saveToDevice(dev)) + return false; + + return true; +} + +QImage KisImagePipeBrush::img() +{ + if (m_brushes.isEmpty()) { + return 0; + } + else { + return m_brushes.at(0)->img(); + } +} + +KisAlphaMaskSP KisImagePipeBrush::mask(const KisPaintInformation& info, double subPixelX, double subPixelY) const +{ + if (m_brushes.isEmpty()) return 0; + selectNextBrush(info); + return m_brushes.at(m_currentBrush)->mask(info, subPixelX, subPixelY); +} + +KisPaintDeviceSP KisImagePipeBrush::image(KisColorSpace * colorSpace, const KisPaintInformation& info, double subPixelX, double subPixelY) const +{ + if (m_brushes.isEmpty()) return 0; + selectNextBrush(info); + return m_brushes.at(m_currentBrush)->image(colorSpace, info, subPixelX, subPixelY); +} + +void KisImagePipeBrush::setParasiteString(const QString& parasite) +{ + m_parasiteString = parasite; + m_parasite = KisPipeBrushParasite(parasite); +} + + +enumBrushType KisImagePipeBrush::brushType() const +{ + if (m_brushType == PIPE_IMAGE && useColorAsMask()) { + return PIPE_MASK; + } + else { + return m_brushType; + } +} + +bool KisImagePipeBrush::useColorAsMask() const +{ + if (m_brushes.count() > 0) { + return m_brushes.at(0)->useColorAsMask(); + } + else { + return false; + } +} + +void KisImagePipeBrush::setUseColorAsMask(bool useColorAsMask) +{ + for (uint i = 0; i < m_brushes.count(); i++) { + m_brushes.at(i)->setUseColorAsMask(useColorAsMask); + } +} + +bool KisImagePipeBrush::hasColor() const +{ + if (m_brushes.count() > 0) { + return m_brushes.at(0)->hasColor(); + } + else { + return false; + } +} + +KisBoundary KisImagePipeBrush::boundary() { + Q_ASSERT(!m_brushes.isEmpty()); + return m_brushes.at(0)->boundary(); +} + +void KisImagePipeBrush::selectNextBrush(const KisPaintInformation& info) const { + m_currentBrush = 0; + double angle; + for (int i = 0; i < m_parasite.dim; i++) { + int index = m_parasite.index[i]; + switch (m_parasite.selection[i]) { + case KisPipeBrushParasite::Constant: break; + case KisPipeBrushParasite::Incremental: + index = (index + 1) % m_parasite.rank[i]; break; + case KisPipeBrushParasite::Random: + index = int(float(m_parasite.rank[i])*KApplication::random() / RAND_MAX); break; + case KisPipeBrushParasite::Pressure: + index = static_cast<int>(info.pressure * (m_parasite.rank[i] - 1) + 0.5); break; + case KisPipeBrushParasite::Angular: + // + M_PI_2 to be compatible with the gimp + angle = atan2(info.movement.y(), info.movement.x()) + M_PI_2; + // We need to be in the [0..2*Pi[ interval so that we can more nicely select it + if (angle < 0) + angle += 2.0 * M_PI; + else if (angle > 2.0 * M_PI) + angle -= 2.0 * M_PI; + index = static_cast<int>(angle / (2.0 * M_PI) * m_parasite.rank[i]); + break; + default: + kdWarning(41001) << "This parasite selectionMode has not been implemented. Reselecting" + << " to Incremental" << endl; + m_parasite.selection[i] = KisPipeBrushParasite::Incremental; + index = 0; + } + m_parasite.index[i] = index; + m_currentBrush += m_parasite.brushesCount[i] * index; + } +} + +bool KisImagePipeBrush::canPaintFor(const KisPaintInformation& info) { + if (info.movement.isNull() && m_parasite.needsMovement) + return false; + return true; +} + +void KisImagePipeBrush::makeMaskImage() { + for (uint i = 0; i < m_brushes.count(); i++) + m_brushes.at(i)->makeMaskImage(); + + setBrushType(PIPE_MASK); + setUseColorAsMask(false); +} + +KisImagePipeBrush* KisImagePipeBrush::clone() const { + // The obvious way of cloning each brush in this one doesn't work for some reason... + + // XXX Multidimensionals not supported yet, change together with the constructor... + QValueVector< QValueVector<KisPaintDevice*> > devices; + QValueVector<KisPipeBrushParasite::SelectionMode> modes; + + devices.push_back(QValueVector<KisPaintDevice*>()); + modes.push_back(m_parasite.selection[0]); + + for (uint i = 0; i < m_brushes.count(); i++) { + KisPaintDevice* pd = new KisPaintDevice( + KisMetaRegistry::instance()->csRegistry()->getColorSpace(KisID("RGBA",""),""), "clone pd" ); + pd->convertFromQImage(m_brushes.at(i)->img(), ""); + devices.at(0).append(pd); + } + + KisImagePipeBrush* c = new KisImagePipeBrush(name(), width(), height(), devices, modes); + // XXX clean up devices + + return c; +} + +#include "kis_imagepipe_brush.moc" + diff --git a/krita/core/kis_imagepipe_brush.h b/krita/core/kis_imagepipe_brush.h new file mode 100644 index 00000000..8a183848 --- /dev/null +++ b/krita/core/kis_imagepipe_brush.h @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2004 Boudewijn Rempt <boud@valdyas.org> + * Copyright (c) 2005 Bart Coppens <kde@bartcoppens.be> + * + * 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_IMAGEPIPE_BRUSH_ +#define KIS_IMAGEPIPE_BRUSH_ + +#include <qptrlist.h> +#include <qvaluelist.h> +#include <qvaluevector.h> +#include <qmap.h> +#include <qstring.h> + +#include <kio/job.h> + +#include "kis_resource.h" +#include "kis_brush.h" +#include "kis_global.h" + +class QCString; +class QImage; +class QPoint; +class QSize; + +/** + * The parasite info that gets loaded from the terribly documented gimp pipe brush parasite. + * We only store data we actually use. + * BC: How it seems the dimension stuff interacts with rank, selectionMode and the actual + * selection of a brush to be drawn. So apparantly you can have at most 4 'dimensions'. + * Each dimension has a number of brushes, the rank. Each dimension has an associated selection + * mode and placement mode (which we don't use). The selection mode says us in which way + * which of the brushes or brush sets will be selected. In the case of a 1-dimensional pipe + * brush it is easy. + * However, when there are more dimensions it is a bit harder. You can according to the gimp + * source maximally use 4 dimensions. When you want to select a brush, you first go to the + * first dimension. Say it has a rank of 2. The code chooses one of the 2 according to the + * selection mode. Say we choose 2. Then the currentBrush will skip over all the brushes + * from the first element in dimension 1. Then in dimension we pick again from the choices + * we have in dimension 2. We again add the appropriate amount to currentBrush. And so on, + * until we have reached dimension dim. Or at least, that is how it looks like, we'll know + * for sure when we can test it better with >1 dim brushes and Angular selectionMode. + **/ +class KisPipeBrushParasite { +public: + /// Set some default values + KisPipeBrushParasite() : ncells(0), dim(0), needsMovement(false) { + for (int i = 0; i < MaxDim; i++) { + rank[i] = index[i] = brushesCount[i] = 0; + selection[i] = Constant; + } + } + /// Initializes the brushesCount helper + void setBrushesCount(); + /// Load the parasite from the source string + KisPipeBrushParasite(const QString& source); + /** + * Saves a GIMP-compatible representation of this parasite to the device. Also writes the + * number of brushes (== ncells) (no trailing '\n') */ + bool saveToDevice(QIODevice* dev) const; + + /** Velocity won't be supported, atm Angular and Tilt aren't either, but have chances of implementation */ + enum SelectionMode { + Constant, Incremental, Angular, Velocity, Random, Pressure, TiltX, TiltY + }; + enum Placement { DefaultPlacement, ConstantPlacement, RandomPlacement }; + static int const MaxDim = 4; + //Q_INT32 step; + Q_INT32 ncells; + Q_INT32 dim; + // Apparantly only used for editing a pipe brush, which we won't at the moment + // Q_INT32 cols, rows; + // Q_INT32 cellwidth, cellheight; + // Aparantly the gimp doesn't use this anymore? Anyway it is a bit weird to + // paint at someplace else than where your cursor displays it will... + //Placement placement; + Q_INT32 rank[MaxDim]; + SelectionMode selection[MaxDim]; + /// The total count of brushes in each dimension (helper) + Q_INT32 brushesCount[MaxDim]; + /// The current index in each dimension, so that the selection modes know where to start + Q_INT32 index[MaxDim]; + /// If true, the brush won't be painted when there is no motion + bool needsMovement; +}; + + +class KisImagePipeBrush : public KisBrush { + typedef KisBrush super; + Q_OBJECT + +public: + KisImagePipeBrush(const QString& filename); + /** + * Specialized constructor that makes a new pipe brush from a sequence of samesize + * devices. The fact that it's a vector of a vector, is to support multidimensional + * brushes (not yet supported!) */ + KisImagePipeBrush(const QString& name, int w, int h, + QValueVector< QValueVector<KisPaintDevice*> > devices, + QValueVector<KisPipeBrushParasite::SelectionMode> modes); + virtual ~KisImagePipeBrush(); + + virtual bool load(); + virtual bool save(); + /// Will call KisBrush's saveToDevice as well + virtual bool saveToDevice(QIODevice* dev) const; + + /** + @return the next image in the pipe. + */ + virtual QImage img(); + + /** + @return the next mask in the pipe. + */ + virtual KisAlphaMaskSP mask(const KisPaintInformation& info, + double subPixelX = 0, double subPixelY = 0) const; + virtual KisPaintDeviceSP image(KisColorSpace * colorSpace, const KisPaintInformation& info, + double subPixelX = 0, double subPixelY = 0) const; + + virtual bool useColorAsMask() const; + virtual void setUseColorAsMask(bool useColorAsMask); + virtual bool hasColor() const; + + virtual enumBrushType brushType() const; + + virtual KisBoundary boundary(); + + KisPipeBrushParasite const& parasite() const { return m_parasite; } + + virtual bool canPaintFor(const KisPaintInformation& info); + + virtual void makeMaskImage(); + + virtual KisImagePipeBrush* clone() const; + +private: + bool init(); + void setParasiteString(const QString& parasite); + void selectNextBrush(const KisPaintInformation& info) const; + + QString m_name; + QString m_parasiteString; // Contains instructions on how to use the brush + mutable KisPipeBrushParasite m_parasite; + Q_UINT32 m_numOfBrushes; + mutable Q_UINT32 m_currentBrush; + + QByteArray m_data; + mutable QPtrList<KisBrush> m_brushes; + + enumBrushType m_brushType; + +}; + +#endif // KIS_IMAGEPIPE_BRUSH_ diff --git a/krita/core/kis_iterator.cc b/krita/core/kis_iterator.cc new file mode 100644 index 00000000..f43f3d5b --- /dev/null +++ b/krita/core/kis_iterator.cc @@ -0,0 +1,142 @@ +/* This file is part of the KDE project + * Copyright (c) 2004 Casper Boemann <cbr@boemann.dkt> + * + * 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 <qglobal.h> + +#include <kdebug.h> + +#include "kis_iterator.h" +#include "kis_datamanager.h" +#include "kis_tilediterator.h" + +KisRectIterator::KisRectIterator ( KisDataManager *dm, Q_INT32 x, Q_INT32 y, Q_INT32 w, Q_INT32 h, bool writable) +{ + m_iter = new KisTiledRectIterator(dm, x, y, w, h, writable); +} +KisRectIterator::KisRectIterator(const KisRectIterator& rhs) +{ + m_iter = rhs.m_iter; +} + +KisRectIterator& KisRectIterator::operator=(const KisRectIterator& rhs) +{ + m_iter = rhs.m_iter; + return *this; +} + +KisRectIterator::~KisRectIterator() +{ +} + +Q_UINT8 * KisRectIterator::rawData() const { return m_iter->rawData();} + +const Q_UINT8 * KisRectIterator::oldRawData() const { return m_iter->oldRawData();} + +Q_INT32 KisRectIterator::nConseqPixels() const { return m_iter->nConseqPixels(); } + +KisRectIterator & KisRectIterator::operator+=(int n) { m_iter->operator+=(n); return *this; } + +KisRectIterator & KisRectIterator::operator++() { m_iter->operator++(); return *this; } + +bool KisRectIterator::isDone() const { return m_iter->isDone(); } + +Q_INT32 KisRectIterator::x() const { return m_iter->x(); } +Q_INT32 KisRectIterator::y() const { return m_iter->y(); } + +//--------------------------------------------------------------------------------------- + +KisHLineIterator::KisHLineIterator ( KisDataManager *dm, Q_INT32 x, Q_INT32 y, Q_INT32 w, bool writable) +{ + m_iter = new KisTiledHLineIterator(dm, x, y, w, writable); +} + +KisHLineIterator::KisHLineIterator(const KisHLineIterator& rhs) +{ + m_iter = rhs.m_iter; +} + +KisHLineIterator& KisHLineIterator::operator=(const KisHLineIterator& rhs) +{ + + m_iter=rhs.m_iter; + return *this; +} + +KisHLineIterator::~KisHLineIterator() +{ +} + +Q_UINT8 *KisHLineIterator::rawData() const +{ + return m_iter->rawData(); +} + +const Q_UINT8 *KisHLineIterator::oldRawData() const { return m_iter->oldRawData();} + +KisHLineIterator & KisHLineIterator::operator++() { m_iter->operator++(); return *this; } + +Q_INT32 KisHLineIterator::nConseqHPixels() const { return m_iter->nConseqHPixels(); } + +KisHLineIterator & KisHLineIterator::operator+=(int n) { m_iter->operator+=(n); return *this; } + +KisHLineIterator & KisHLineIterator::operator--() { m_iter->operator--(); return *this; } + +bool KisHLineIterator::isDone() const { return m_iter->isDone(); } + +Q_INT32 KisHLineIterator::x() const { return m_iter->x(); } + +Q_INT32 KisHLineIterator::y() const { return m_iter->y(); } + +void KisHLineIterator::nextRow() { m_iter->nextRow(); } + +//--------------------------------------------------------------------------------------- + +KisVLineIterator::KisVLineIterator ( KisDataManager *dm, Q_INT32 x, Q_INT32 y, Q_INT32 h, bool writable) +{ + m_iter = new KisTiledVLineIterator(dm, x, y, h, writable); +} + +KisVLineIterator::KisVLineIterator(const KisVLineIterator& rhs) +{ + m_iter = rhs.m_iter; +} + +KisVLineIterator& KisVLineIterator::operator=(const KisVLineIterator& rhs) +{ + m_iter = rhs.m_iter; + return *this; +} + +KisVLineIterator::~KisVLineIterator() +{ +} + +Q_UINT8 *KisVLineIterator::rawData() const { return m_iter->rawData();} + +const Q_UINT8 * KisVLineIterator::oldRawData() const { return m_iter->oldRawData();} + +KisVLineIterator & KisVLineIterator::operator++() { m_iter->operator++(); return *this; } + +bool KisVLineIterator::isDone() const { return m_iter->isDone(); } + +Q_INT32 KisVLineIterator::x() const { return m_iter->x(); } + +Q_INT32 KisVLineIterator::y() const { return m_iter->y(); } + +void KisVLineIterator::nextCol() { return m_iter->nextCol(); } diff --git a/krita/core/kis_iterator.h b/krita/core/kis_iterator.h new file mode 100644 index 00000000..67206272 --- /dev/null +++ b/krita/core/kis_iterator.h @@ -0,0 +1,173 @@ +/* This file is part of the KDE project + * Copyright (c) 2004 Casper Boemann <cbr@boemann.dkt> + * + * 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_ITERATOR_H_ +#define KIS_ITERATOR_H_ + +#include <qglobal.h> +#include <ksharedptr.h> + +class KisTiledRectIterator; +typedef KSharedPtr<KisTiledRectIterator> KisTiledRectIteratorSP; + +class KisTiledVLineIterator; +typedef KSharedPtr<KisTiledVLineIterator> KisTiledVLineIteratorSP; + +class KisTiledHLineIterator; +typedef KSharedPtr<KisTiledHLineIterator> KisTiledHLineIteratorSP; + +class KisDataManager; + +/** + * The KisRectIterator iterators over a rectangular area in the most efficient order. That is, + * there is no guarantee that the iterator will work scanline by scanline. + */ +class KisRectIterator +{ + + +public: + KisRectIterator ( KisDataManager *dm, Q_INT32 x, Q_INT32 y, Q_INT32 w, Q_INT32 h, bool writable); + +public: + virtual ~KisRectIterator(); + KisRectIterator(const KisRectIterator& rhs); + KisRectIterator& operator=(const KisRectIterator& rhs); + + +public: + /// returns a pointer to the pixel data. Do NOT interpret the data - leave that to a colorstrategy + Q_UINT8 * rawData() const; + + /// Returns a pointer to the pixel data as it was at the moment of the last memento creation. + const Q_UINT8 * oldRawData() const; + + /// Returns the number of consequtive pixels that we point at + /// This is useful for optimizing + Q_INT32 nConseqPixels() const; + + /// Advances a number of pixels until it reaches the end of the rect + KisRectIterator & operator+=(int n); + + /// Advances one pixel going to the beginning of the next line when it reaches the end of a line + KisRectIterator & operator++(); + + /// returns true when iterators has reached the end + bool isDone() const; + + // current x position + Q_INT32 x() const; + + // current y position + Q_INT32 y() const; + +private: + + KisTiledRectIteratorSP m_iter; +}; + +class KisHLineIterator +{ + +public: + + KisHLineIterator ( KisDataManager *dm, Q_INT32 x, Q_INT32 y, Q_INT32 w, bool writable); + +public: + + virtual ~KisHLineIterator(); + KisHLineIterator(const KisHLineIterator& rhs); + KisHLineIterator& operator=(const KisHLineIterator& rhs); + +public: + /// Returns a pointer to the pixel data. Do NOT interpret the data - leave that to a colorstrategy + Q_UINT8 *rawData() const; + + /// Returns a pointer to the pixel data as it was at the moment of the last memento creation. + const Q_UINT8 *oldRawData() const; + + /// Advances one pixel until it reaches the end of the line + KisHLineIterator & operator++(); + + /// Returns the number of consequtive horizontal pixels that we point at + /// This is useful for optimizing + Q_INT32 nConseqHPixels() const; + + /// Advances a number of pixels until it reaches the end of the line + KisHLineIterator & operator+=(int n); + + /// Goes back one pixel until it reaches the beginning of the line + KisHLineIterator & operator--(); + + /// returns true when iterators has reached the end + bool isDone() const; + + /// current x position + Q_INT32 x() const; + + /// current y position + Q_INT32 y() const; + + /// increment to the next row and rewind to the begining + void nextRow(); + + +private: + + KisTiledHLineIteratorSP m_iter; +}; + +class KisVLineIterator +{ + +public: + KisVLineIterator ( KisDataManager *dm, Q_INT32 x, Q_INT32 y, Q_INT32 h, bool writable); +public: + ~KisVLineIterator(); + KisVLineIterator(const KisVLineIterator& rhs); + KisVLineIterator& operator=(const KisVLineIterator& rhs); + +public: + /// returns a pointer to the pixel data. Do NOT interpret the data - leave that to a colorstrategy + Q_UINT8 *rawData() const; + + /// Returns a pointer to the pixel data as it was at the moment of the last memento creation. + const Q_UINT8 * oldRawData() const; + + /// Advances one pixel until it reaches the end of the line + KisVLineIterator & operator++(); + + /// returns true when iterators has reached the end + bool isDone() const; + + /// current x position + Q_INT32 x() const; + + /// current y position + Q_INT32 y() const; + + /// increment to the next column and rewind to the begining + void nextCol(); + +private: + + KisTiledVLineIteratorSP m_iter; + +}; + +#endif diff --git a/krita/core/kis_iteratorpixeltrait.h b/krita/core/kis_iteratorpixeltrait.h new file mode 100644 index 00000000..5aadad56 --- /dev/null +++ b/krita/core/kis_iteratorpixeltrait.h @@ -0,0 +1,131 @@ +/* This file is part of the KDE project + * Copyright (c) 2004 Cyrille Berger <cberger@cberger.net>, the original iteratorpixel + * Copyright (c) 2005 Casper Boemann <cbr@boemann.dk>, made it into a trait + * + * 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_ITERATORPIXELTRAIT_H_ +#define KIS_ITERATORPIXELTRAIT_H_ + +#include "kis_iterator.h" +#include <kis_paint_device.h> + +template< typename _iTp> +class KisIteratorPixelTrait +{ +public: + KisIteratorPixelTrait(KisPaintDevice * ndevice, _iTp *underlyingIterator) + : m_device(ndevice), + m_underlyingIterator(underlyingIterator) + { + m_selectionIterator = NULL; + }; + + ~KisIteratorPixelTrait() + { + delete m_selectionIterator; + }; + + KisIteratorPixelTrait(const KisIteratorPixelTrait& rhs) + { + if (this == &rhs) + return; + m_device = rhs.m_device; + m_underlyingIterator = rhs.m_underlyingIterator; + + if (rhs.m_selectionIterator) { + m_selectionIterator = new _iTp(*rhs.m_selectionIterator); + } else { + m_selectionIterator = 0; + } + } + + KisIteratorPixelTrait& operator=(const KisIteratorPixelTrait& rhs) + { + if (this == &rhs) + return *this; + m_device = rhs.m_device; + m_underlyingIterator = rhs.m_underlyingIterator; + + delete m_selectionIterator; + if (rhs.m_selectionIterator) { + m_selectionIterator = new _iTp(*rhs.m_selectionIterator); + } else { + m_selectionIterator = 0; + } + + return *this; + } + + +public: + /** + * Return one channel from the current kispixel. Does not check whether + * channel index actually exists in this colorspace. + */ + inline Q_UINT8 operator[](int index) const + { return m_underlyingIterator->rawData()[index]; }; + + /** + * Returns if the pixel is selected or not. This is much faster than first building a KisPixel + */ + inline bool isSelected() const + { + if (m_selectionIterator) + return *(m_selectionIterator->rawData()) > SELECTION_THRESHOLD; + else + return true; + }; + + /** + * Returns the degree of selectedness of the pixel. + */ + inline Q_UINT8 selectedness() const + { + if (m_selectionIterator) + return *(m_selectionIterator->rawData()); + else { + return MAX_SELECTED; + } + }; + + /** + * Returns the selectionmask from the current point; this is guaranteed + * to have the same number of consecutive pixels that the iterator has + * at a given point. It return a 0 if there is no selection. + */ + inline Q_UINT8 * selectionMask() const + { + if ( m_selectionIterator ) + return m_selectionIterator->rawData(); + else + return 0; + } + + +protected: + KisPaintDevice *m_device; + + inline void advance(int n){if (m_selectionIterator) for(int i=0; i< n; i++) ++(*m_selectionIterator);}; + inline void retreat(){if (m_selectionIterator) --(*m_selectionIterator);}; + + void setSelectionIterator(_iTp *si){m_selectionIterator = si;}; + + _iTp *m_underlyingIterator; + _iTp *m_selectionIterator; +}; + +#endif diff --git a/krita/core/kis_iterators_pixel.cc b/krita/core/kis_iterators_pixel.cc new file mode 100644 index 00000000..062fde73 --- /dev/null +++ b/krita/core/kis_iterators_pixel.cc @@ -0,0 +1,59 @@ +/* + * This file is part of the Krita project + * + * Copyright (c) 2004 Cyrille Berger <cberger@cberger.net> + * + * 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_iterators_pixel.h" +#include "kis_global.h" +#include "kis_paint_device.h" + +KisHLineIteratorPixel::KisHLineIteratorPixel( KisPaintDevice *ndevice, KisDataManager *dm, KisDataManager *sel_dm, Q_INT32 x, Q_INT32 y, Q_INT32 w, Q_INT32 offsetx, Q_INT32 offsety, bool writable) : + KisHLineIterator(dm, x - offsetx, y - offsety, w, writable), + KisIteratorPixelTrait <KisHLineIterator> ( ndevice, this ), + m_offsetx(offsetx), m_offsety(offsety) +{ + if(sel_dm) { + KisHLineIterator * i = new KisHLineIterator(sel_dm, x - offsetx, y - offsety, w, false); + Q_CHECK_PTR(i); + KisIteratorPixelTrait <KisHLineIterator>::setSelectionIterator(i); + } +} + +KisVLineIteratorPixel::KisVLineIteratorPixel( KisPaintDevice *ndevice, KisDataManager *dm, KisDataManager *sel_dm, Q_INT32 x, Q_INT32 y, Q_INT32 h, Q_INT32 offsetx, Q_INT32 offsety, bool writable) : + KisVLineIterator(dm, x - offsetx, y - offsety, h, writable), + KisIteratorPixelTrait <KisVLineIterator> ( ndevice, this ), + m_offsetx(offsetx), m_offsety(offsety) +{ + if(sel_dm) { + KisVLineIterator * i = new KisVLineIterator(sel_dm, x - offsetx, y - offsety, h, false); + Q_CHECK_PTR(i); + KisIteratorPixelTrait <KisVLineIterator>::setSelectionIterator(i); + } +} + +KisRectIteratorPixel::KisRectIteratorPixel( KisPaintDevice *ndevice, KisDataManager *dm, KisDataManager *sel_dm, Q_INT32 x, Q_INT32 y, Q_INT32 w, Q_INT32 h, Q_INT32 offsetx, Q_INT32 offsety, bool writable) : + KisRectIterator(dm, x - offsetx, y - offsety, w, h, writable), + KisIteratorPixelTrait <KisRectIterator> ( ndevice, this ), + m_offsetx(offsetx), m_offsety(offsety) +{ + if(sel_dm) { + KisRectIterator * i = new KisRectIterator(sel_dm, x - offsetx, y - offsety, w, h, false); + Q_CHECK_PTR(i); + KisIteratorPixelTrait <KisRectIterator>::setSelectionIterator(i); + } +} diff --git a/krita/core/kis_iterators_pixel.h b/krita/core/kis_iterators_pixel.h new file mode 100644 index 00000000..ad7d7f1f --- /dev/null +++ b/krita/core/kis_iterators_pixel.h @@ -0,0 +1,154 @@ +/* This file is part of the KDE project + * Copyright (c) 2004 Cyrille Berger <cberger@cberger.net> + * + * 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_ITERATORS_PIXEL_H_ +#define KIS_ITERATORS_PIXEL_H_ + +#include "kis_iterator.h" +#include "kis_iteratorpixeltrait.h" + +/** + * The pixel iterators are high level iterarators. The lower level iterators merely return a pointer to some memory + * where a pixel begins; these iterators return KisPixels -- high-level representations of a pixel together with + * color model, profile and selectedness. You can access individual channels using the KisPixel [] operator, and . + */ + + +class KisHLineIteratorPixel : public KisHLineIterator, public KisIteratorPixelTrait <KisHLineIterator> +{ + +public: + + KisHLineIteratorPixel( KisPaintDevice *ndevice, KisDataManager *dm, KisDataManager *sel_dm, + Q_INT32 x , Q_INT32 y , Q_INT32 w, Q_INT32 offsetx, Q_INT32 offsety, + bool writable); + + KisHLineIteratorPixel(const KisHLineIteratorPixel& rhs) : KisHLineIterator(rhs), KisIteratorPixelTrait<KisHLineIterator>(rhs) + { m_offsetx = rhs.m_offsetx; m_offsety = rhs.m_offsety; } + + KisHLineIteratorPixel& operator=(const KisHLineIteratorPixel& rhs) + { + KisHLineIterator::operator=(rhs); + KisIteratorPixelTrait<KisHLineIterator>::operator=(rhs); + m_offsetx = rhs.m_offsetx; m_offsety = rhs.m_offsety; + return *this; + } + + inline KisHLineIteratorPixel & operator ++() { KisHLineIterator::operator++(); advance(1); return *this;} + inline KisHLineIteratorPixel & operator --() { KisHLineIterator::operator--(); retreat(); return *this;} + + inline void nextRow() { + KisHLineIterator::nextRow(); + if (m_selectionIterator) m_selectionIterator->nextRow(); + } + + /// Advances a number of pixels until it reaches the end of the line + KisHLineIteratorPixel & operator+=(int n) { KisHLineIterator::operator+=(n); advance(n); return *this; }; + + Q_INT32 x() const { return KisHLineIterator::x() + m_offsetx; } + + Q_INT32 y() const { return KisHLineIterator::y() + m_offsety; } + + Q_INT32 nConseqHPixels() const { + if (m_selectionIterator) { + Q_INT32 parent = KisHLineIteratorPixel::nConseqHPixels(); + Q_INT32 selection = m_selectionIterator->nConseqHPixels(); + if (parent < selection) + return parent; + return selection; + } + return KisHLineIteratorPixel::nConseqHPixels(); + } +protected: + + Q_INT32 m_offsetx, m_offsety; +}; + +class KisVLineIteratorPixel : public KisVLineIterator, public KisIteratorPixelTrait <KisVLineIterator> +{ +public: + KisVLineIteratorPixel( KisPaintDevice *ndevice, KisDataManager *dm, KisDataManager *sel_dm, + Q_INT32 xpos , Q_INT32 ypos , Q_INT32 height, Q_INT32 offsetx, Q_INT32 offsety, + bool writable); + + KisVLineIteratorPixel(const KisVLineIteratorPixel& rhs) : KisVLineIterator(rhs), KisIteratorPixelTrait<KisVLineIterator>(rhs) + { m_offsetx = rhs.m_offsetx; m_offsety = rhs.m_offsety; } + + KisVLineIteratorPixel& operator=(const KisVLineIteratorPixel& rhs) + { + KisVLineIterator::operator=(rhs); + KisIteratorPixelTrait<KisVLineIterator>::operator=(rhs); + m_offsetx = rhs.m_offsetx; m_offsety = rhs.m_offsety; + return *this; } + + inline KisVLineIteratorPixel & operator ++() { KisVLineIterator::operator++(); advance(1); return *this;} + + inline void nextRow() { + KisVLineIterator::nextCol(); + if (m_selectionIterator) m_selectionIterator->nextCol(); + } + + Q_INT32 x() const { return KisVLineIterator::x() + m_offsetx; } + + Q_INT32 y() const { return KisVLineIterator::y() + m_offsety; } + +protected: + + Q_INT32 m_offsetx, m_offsety; +}; + +class KisRectIteratorPixel : public KisRectIterator, public KisIteratorPixelTrait <KisRectIterator> +{ +public: + KisRectIteratorPixel( KisPaintDevice *ndevice, KisDataManager *dm, KisDataManager *sel_dm, + Q_INT32 x, Q_INT32 y, Q_INT32 w, Q_INT32 h, Q_INT32 offsetx, Q_INT32 offsety, + bool writable); + + KisRectIteratorPixel(const KisRectIteratorPixel& rhs) : KisRectIterator(rhs), KisIteratorPixelTrait<KisRectIterator>(rhs) + { m_offsetx = rhs.m_offsetx; m_offsety = rhs.m_offsety; } + + KisRectIteratorPixel& operator=(const KisRectIteratorPixel& rhs) + { + KisRectIterator::operator=(rhs); + KisIteratorPixelTrait<KisRectIterator>::operator=(rhs); + m_offsetx = rhs.m_offsetx; m_offsety = rhs.m_offsety; + return *this; } + + inline KisRectIteratorPixel & operator ++() { KisRectIterator::operator++(); advance(1); return *this;} + + Q_INT32 x() const { return KisRectIterator::x() + m_offsetx; } + + Q_INT32 y() const { return KisRectIterator::y() + m_offsety; } + + Q_INT32 nConseqPixels() const { + if (m_selectionIterator) { + Q_INT32 parent = KisRectIterator::nConseqPixels(); + Q_INT32 selection = m_selectionIterator->nConseqPixels(); + if (parent < selection) + return parent; + return selection; + } + return KisRectIterator::nConseqPixels(); + } + +protected: + + Q_INT32 m_offsetx, m_offsety; +}; + +#endif diff --git a/krita/core/kis_layer.cc b/krita/core/kis_layer.cc new file mode 100644 index 00000000..b19a4dcf --- /dev/null +++ b/krita/core/kis_layer.cc @@ -0,0 +1,611 @@ +/* + * Copyright (c) 2002 Patrick Julien <freak@codepimps.org> + * Copyright (c) 2005 Casper Boemann <cbr@boemann.dk> + * + * 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. + */ + +#include <kdebug.h> +#include <qimage.h> + +#include "kis_debug_areas.h" +#include "kis_group_layer.h" +#include "kis_image.h" +#include "kis_layer.h" +#include "kis_painter.h" +#include "kis_undo_adapter.h" + +namespace { + + class KisLayerCommand : public KNamedCommand { + typedef KNamedCommand super; + + public: + KisLayerCommand(const QString& name, KisLayerSP layer); + virtual ~KisLayerCommand() {} + + virtual void execute() = 0; + virtual void unexecute() = 0; + + protected: + void setUndo(bool undo); + + KisLayerSP m_layer; + }; + + KisLayerCommand::KisLayerCommand(const QString& name, KisLayerSP layer) : + super(name), m_layer(layer) + { + } + + void KisLayerCommand::setUndo(bool undo) + { + if (m_layer->undoAdapter()) { + m_layer->undoAdapter()->setUndo(undo); + } + } + + class KisLayerLockedCommand : public KisLayerCommand { + typedef KisLayerCommand super; + + public: + KisLayerLockedCommand(KisLayerSP layer, bool newLocked); + + virtual void execute(); + virtual void unexecute(); + + private: + bool m_newLocked; + }; + + KisLayerLockedCommand::KisLayerLockedCommand(KisLayerSP layer, bool newLocked) : + super(i18n("Lock Layer"), layer) + { + m_newLocked = newLocked; + } + + void KisLayerLockedCommand::execute() + { + setUndo(false); + m_layer->setLocked(m_newLocked); + setUndo(true); + } + + void KisLayerLockedCommand::unexecute() + { + setUndo(false); + m_layer->setLocked(!m_newLocked); + setUndo(true); + } + + class KisLayerOpacityCommand : public KisLayerCommand { + typedef KisLayerCommand super; + + public: + KisLayerOpacityCommand(KisLayerSP layer, Q_UINT8 oldOpacity, Q_UINT8 newOpacity); + + virtual void execute(); + virtual void unexecute(); + + private: + Q_UINT8 m_oldOpacity; + Q_UINT8 m_newOpacity; + }; + + KisLayerOpacityCommand::KisLayerOpacityCommand(KisLayerSP layer, Q_UINT8 oldOpacity, Q_UINT8 newOpacity) : + super(i18n("Layer Opacity"), layer) + { + m_oldOpacity = oldOpacity; + m_newOpacity = newOpacity; + } + + void KisLayerOpacityCommand::execute() + { + setUndo(false); + m_layer->setOpacity(m_newOpacity); + setUndo(true); + } + + void KisLayerOpacityCommand::unexecute() + { + setUndo(false); + m_layer->setOpacity(m_oldOpacity); + setUndo(true); + } + + class KisLayerVisibilityCommand : public KisLayerCommand { + typedef KisLayerCommand super; + + public: + KisLayerVisibilityCommand(KisLayerSP layer, bool newVisibility); + + virtual void execute(); + virtual void unexecute(); + + private: + bool m_newVisibility; + }; + + KisLayerVisibilityCommand::KisLayerVisibilityCommand(KisLayerSP layer, bool newVisibility) : + super(i18n("Layer Visibility"), layer) + { + m_newVisibility = newVisibility; + } + + void KisLayerVisibilityCommand::execute() + { + setUndo(false); + m_layer->setVisible(m_newVisibility); + setUndo(true); + } + + void KisLayerVisibilityCommand::unexecute() + { + setUndo(false); + m_layer->setVisible(!m_newVisibility); + setUndo(true); + } + + class KisLayerCompositeOpCommand : public KisLayerCommand { + typedef KisLayerCommand super; + + public: + KisLayerCompositeOpCommand(KisLayerSP layer, const KisCompositeOp& oldCompositeOp, const KisCompositeOp& newCompositeOp); + + virtual void execute(); + virtual void unexecute(); + + private: + KisCompositeOp m_oldCompositeOp; + KisCompositeOp m_newCompositeOp; + }; + + KisLayerCompositeOpCommand::KisLayerCompositeOpCommand(KisLayerSP layer, const KisCompositeOp& oldCompositeOp, + const KisCompositeOp& newCompositeOp) : + super(i18n("Layer Composite Mode"), layer) + { + m_oldCompositeOp = oldCompositeOp; + m_newCompositeOp = newCompositeOp; + } + + void KisLayerCompositeOpCommand::execute() + { + setUndo(false); + m_layer->setCompositeOp(m_newCompositeOp); + setUndo(true); + } + + void KisLayerCompositeOpCommand::unexecute() + { + setUndo(false); + m_layer->setCompositeOp(m_oldCompositeOp); + setUndo(true); + } + + class KisLayerOffsetCommand : public KNamedCommand { + typedef KNamedCommand super; + + public: + KisLayerOffsetCommand(KisLayerSP layer, const QPoint& oldpos, const QPoint& newpos); + virtual ~KisLayerOffsetCommand(); + + virtual void execute(); + virtual void unexecute(); + + private: + void moveTo(const QPoint& pos); + + private: + KisLayerSP m_layer; + QRect m_updateRect; + QPoint m_oldPos; + QPoint m_newPos; + }; + + KisLayerOffsetCommand::KisLayerOffsetCommand(KisLayerSP layer, const QPoint& oldpos, const QPoint& newpos) : + super(i18n("Move Layer")) + { + m_layer = layer; + m_oldPos = oldpos; + m_newPos = newpos; + + QRect currentBounds = m_layer->exactBounds(); + QRect oldBounds = currentBounds; + oldBounds.moveBy(oldpos.x() - newpos.x(), oldpos.y() - newpos.y()); + + m_updateRect = currentBounds | oldBounds; + } + + KisLayerOffsetCommand::~KisLayerOffsetCommand() + { + } + + void KisLayerOffsetCommand::execute() + { + moveTo(m_newPos); + } + + void KisLayerOffsetCommand::unexecute() + { + moveTo(m_oldPos); + } + + void KisLayerOffsetCommand::moveTo(const QPoint& pos) + { + if (m_layer->undoAdapter()) { + m_layer->undoAdapter()->setUndo(false); + } + + m_layer->setX(pos.x()); + m_layer->setY(pos.y()); + + m_layer->setDirty(m_updateRect); + + if (m_layer->undoAdapter()) { + m_layer->undoAdapter()->setUndo(true); + } + } +} + +static int getID() +{ + static int id = 1; + return id++; +} + + +KisLayer::KisLayer(KisImage *img, const QString &name, Q_UINT8 opacity) : + QObject(0, name.latin1()), + KShared(), + m_id(getID()), + m_index(-1), + m_opacity(opacity), + m_locked(false), + m_visible(true), + m_temporary(false), + m_name(name), + m_parent(0), + m_image(img), + m_compositeOp(COMPOSITE_OVER) +{ +} + +KisLayer::KisLayer(const KisLayer& rhs) : + QObject(), + KShared(rhs) +{ + if (this != &rhs) { + m_id = getID(); + m_index = -1; + m_opacity = rhs.m_opacity; + m_locked = rhs.m_locked; + m_visible = rhs.m_visible; + m_temporary = rhs.m_temporary; + m_dirtyRect = rhs.m_dirtyRect; + m_name = rhs.m_name; + m_image = rhs.m_image; + m_parent = 0; + m_compositeOp = rhs.m_compositeOp; + } +} + +KisLayer::~KisLayer() +{ +} + +void KisLayer::setClean(const QRect & rect) +{ + if (m_dirtyRect.isValid() && rect.isValid()) { + + // XXX: We should only set the parts clean that were actually cleaned. However, extent and exactBounds conspire + // to make that very hard atm. + //if (rect.contains(m_dirtyRect)) m_dirtyRect = QRect(); + m_dirtyRect = QRect(); + } + +} + +bool KisLayer::dirty() +{ + return m_dirtyRect.isValid(); +} + + +bool KisLayer::dirty(const QRect & rc) +{ + if (!m_dirtyRect.isValid() || !rc.isValid()) return false; + + return rc.intersects(m_dirtyRect); +} + +QRect KisLayer::dirtyRect() const +{ + return m_dirtyRect; +} + +void KisLayer::setDirty(bool propagate) +{ + QRect rc = extent(); + + if (rc.isValid()) m_dirtyRect = rc; + + // If we're dirty, our parent is dirty, if we've got a parent + if (propagate && m_parent && rc.isValid()) m_parent->setDirty(m_dirtyRect); + + if (m_image && rc.isValid()) { + m_image->notifyLayerUpdated(this, rc); + } +} + +void KisLayer::setDirty(const QRect & rc, bool propagate) +{ + // If we're dirty, our parent is dirty, if we've got a parent + + if (rc.isValid()) + m_dirtyRect |= rc; + + if (propagate && m_parent && m_dirtyRect.isValid()) + m_parent->setDirty(m_dirtyRect); + + if (m_image && rc.isValid()) { + m_image->notifyLayerUpdated(this, rc); + } +} + +KisGroupLayerSP KisLayer::parent() const +{ + return m_parent; +} + +KisLayerSP KisLayer::prevSibling() const +{ + if (!parent()) + return 0; + return parent()->at(index() - 1); +} + +KisLayerSP KisLayer::nextSibling() const +{ + if (!parent()) + return 0; + return parent()->at(index() + 1); +} + +int KisLayer::index() const +{ + return m_index; +} + +void KisLayer::setIndex(int i) +{ + if (!parent()) + return; + parent()->setIndex(this, i); +} + +KisLayerSP KisLayer::findLayer(const QString& n) const +{ + if (name() == n) + return const_cast<KisLayer*>(this); //HACK any less ugly way? findLayer() is conceptually const... + for (KisLayerSP layer = firstChild(); layer; layer = layer->nextSibling()) + if (KisLayerSP found = layer->findLayer(n)) + return found; + return 0; +} + +KisLayerSP KisLayer::findLayer(int i) const +{ + if (id() == i) + return const_cast<KisLayer*>(this); //HACK + for (KisLayerSP layer = firstChild(); layer; layer = layer->nextSibling()) + if (KisLayerSP found = layer->findLayer(i)) + return found; + return 0; +} + +int KisLayer::numLayers(int flags) const +{ + int num = 0; + if (matchesFlags(flags)) num++; + for (KisLayerSP layer = firstChild(); layer; layer = layer->nextSibling()) + num += layer->numLayers(flags); + return num; +} + +bool KisLayer::matchesFlags(int flags) const +{ + if ((flags & Visible) && !visible()) + return false; + if ((flags & Hidden) && visible()) + return false; + if ((flags & Locked) && !locked()) + return false; + if ((flags & Unlocked) && locked()) + return false; + return true; +} + +Q_UINT8 KisLayer::opacity() const +{ + return m_opacity; +} + +void KisLayer::setOpacity(Q_UINT8 val) +{ + if (m_opacity != val) + { + m_opacity = val; + setDirty(); + notifyPropertyChanged(); + } +} + +KNamedCommand *KisLayer::setOpacityCommand(Q_UINT8 newOpacity) +{ + return new KisLayerOpacityCommand(this, opacity(), newOpacity); +} + +KNamedCommand *KisLayer::setOpacityCommand(Q_UINT8 prevOpacity, Q_UINT8 newOpacity) +{ + return new KisLayerOpacityCommand(this, prevOpacity, newOpacity); +} + +const bool KisLayer::visible() const +{ + return m_visible; +} + +void KisLayer::setVisible(bool v) +{ + if (m_visible != v) { + + m_visible = v; + notifyPropertyChanged(); + setDirty(); + + if (undoAdapter() && undoAdapter()->undo()) { + undoAdapter()->addCommand(setVisibleCommand(v)); + } + } +} + +KNamedCommand *KisLayer::setVisibleCommand(bool newVisibility) +{ + return new KisLayerVisibilityCommand(this, newVisibility); +} + +bool KisLayer::locked() const +{ + return m_locked; +} + +void KisLayer::setLocked(bool l) +{ + if (m_locked != l) { + m_locked = l; + notifyPropertyChanged(); + + if (undoAdapter() && undoAdapter()->undo()) { + undoAdapter()->addCommand(setLockedCommand(l)); + } + } +} + +bool KisLayer::temporary() const +{ + return m_temporary; +} + +void KisLayer::setTemporary(bool t) +{ + m_temporary = t; +} + +KNamedCommand *KisLayer::setLockedCommand(bool newLocked) +{ + return new KisLayerLockedCommand(this, newLocked); +} + +QString KisLayer::name() const +{ + return m_name; +} + +void KisLayer::setName(const QString& name) +{ + if (!name.isEmpty() && m_name != name) + { + m_name = name; + notifyPropertyChanged(); + } +} + +void KisLayer::setCompositeOp(const KisCompositeOp& compositeOp) +{ + if (m_compositeOp != compositeOp) + { + m_compositeOp = compositeOp; + notifyPropertyChanged(); + setDirty(); + + } +} + +KNamedCommand *KisLayer::setCompositeOpCommand(const KisCompositeOp& newCompositeOp) +{ + return new KisLayerCompositeOpCommand(this, compositeOp(), newCompositeOp); +} + +KNamedCommand *KisLayer::moveCommand(QPoint oldPosition, QPoint newPosition) +{ + return new KisLayerOffsetCommand(this, oldPosition, newPosition); +} + +KisUndoAdapter *KisLayer::undoAdapter() const +{ + if (m_image) { + return m_image->undoAdapter(); + } + return 0; +} + +void KisLayer::paintMaskInactiveLayers(QImage &, Q_INT32, Q_INT32, Q_INT32, Q_INT32) +{ +} + +void KisLayer::paintSelection(QImage &, Q_INT32, Q_INT32, Q_INT32, Q_INT32) +{ +} + +void KisLayer::paintSelection(QImage &, const QRect&, const QSize&, const QSize&) +{ +} + +QImage KisLayer::createThumbnail(Q_INT32, Q_INT32) +{ + return 0; +} + +void KisLayer::notifyPropertyChanged() +{ + if(image() && !signalsBlocked()) + image()->notifyPropertyChanged(this); +} + +void KisLayerSupportsIndirectPainting::setTemporaryTarget(KisPaintDeviceSP t) { + m_temporaryTarget = t; +} + +void KisLayerSupportsIndirectPainting::setTemporaryCompositeOp(const KisCompositeOp& c) { + m_compositeOp = c; +} + +void KisLayerSupportsIndirectPainting::setTemporaryOpacity(Q_UINT8 o) { + m_compositeOpacity = o; +} + +KisPaintDeviceSP KisLayerSupportsIndirectPainting::temporaryTarget() { + return m_temporaryTarget; +} + +KisCompositeOp KisLayerSupportsIndirectPainting::temporaryCompositeOp() const { + return m_compositeOp; +} + +Q_UINT8 KisLayerSupportsIndirectPainting::temporaryOpacity() const { + return m_compositeOpacity; +} + +#include "kis_layer.moc" diff --git a/krita/core/kis_layer.h b/krita/core/kis_layer.h new file mode 100644 index 00000000..5da5181d --- /dev/null +++ b/krita/core/kis_layer.h @@ -0,0 +1,256 @@ +/* + * Copyright (c) 2002 Patrick Julien <freak@codepimps.org> + * Copyright (c) 2005 Casper Boemann <cbr@boemann.dk> + * + * 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_LAYER_H_ +#define KIS_LAYER_H_ + +#include <qobject.h> +#include "kis_types.h" +#include "kis_layer_visitor.h" +#include "kis_composite_op.h" +#include <koffice_export.h> + +class KNamedCommand; +class QPainter; +class KisUndoAdapter; +class KisGroupLayer; + +/** + * Abstract class that represents the concept of a Layer in Krita. This is not related + * to the paint devices: this is merely an abstraction of how layers can be stacked and + * rendered differently. + * Regarding the previous-, first-, next- and lastChild() calls, first means that it the layer + * is at the top of the group in the layerlist, using next will iterate to the bottom to last, + * whereas previous will go up to first again. + **/ +class KRITACORE_EXPORT KisLayer : public QObject, public KShared +{ + Q_OBJECT + +public: + KisLayer(KisImage *img, const QString &name, Q_UINT8 opacity); + KisLayer(const KisLayer& rhs); + virtual ~KisLayer(); + + + /** + * Set the specified rect to clean + */ + virtual void setClean(const QRect & rect); + + /** + * If the layer has been changed and not been composited yet, this returns true + */ + virtual bool dirty(); + + /** + * Return true if the given rect intersects the dirty rect(s) of this layer + */ + virtual bool dirty(const QRect & rc); + + + virtual QRect dirtyRect() const; + + + /** + * Set the entire layer extent dirty; this percolates up to parent layers all the + * way to the root layer. + */ + virtual void setDirty(bool propagate = true); + + /** + * Add the given rect to the set of dirty rects for this layer; + * this percolates up to parent layers all the way to the root + * layer. + */ + virtual void setDirty(const QRect & rect, bool propagate = true); + + /// Return a copy of this layer + virtual KisLayerSP clone() const = 0; + + /// Returns the ID of the layer, which is guaranteed to be unique among all KisLayers. + int id() const { return m_id; } + + /* Returns the index of the layer in its parent's list of child layers. Indices + * increase from 0, which is the topmost layer in the list, to the bottommost. + */ + virtual int index() const; + + /// Moves this layer to the specified index within its parent's list of child layers. + virtual void setIndex(int index); + + /** + * Returns the parent layer of a layer. This is 0 only for a root layer; otherwise + * this will be an actual GroupLayer */ + virtual KisGroupLayerSP parent() const; + + /** + * Returns the previous sibling of this layer in the parent's list. This is the layer + * *above* this layer. 0 is returned if there is no parent, or if this child has no more + * previous siblings (== firstChild()) + */ + virtual KisLayerSP prevSibling() const; + + /** + * Returns the next sibling of this layer in the parent's list. This is the layer *below* + * this layer. 0 is returned if there is no parent, or if this child has no more next + * siblings (== lastChild()) + */ + virtual KisLayerSP nextSibling() const; + + /** + * Returns the sibling above this layer in its parent's list. 0 is returned if there is no parent, + * or if this layer is the topmost layer in its group. This is the same as calling prevSibling(). + */ + KisLayerSP siblingAbove() const { return prevSibling(); } + + /** + * Returns the sibling below this layer in its parent's list. 0 is returned if there is no parent, + * or if this layer is the bottommost layer in its group. This is the same as calling nextSibling(). + */ + KisLayerSP siblingBelow() const { return nextSibling(); } + + /// Returns how many direct child layers this layer has (not recursive). + virtual uint childCount() const { return 0; } + + /// Returns the first child layer of this layer (if it supports that). + virtual KisLayerSP firstChild() const { return 0; } + + /// Returns the last child layer of this layer (if it supports that). + virtual KisLayerSP lastChild() const { return 0; } + + /// Recursively searches this layer and any child layers for a layer with the specified name. + virtual KisLayerSP findLayer(const QString& name) const; + + /// Recursively searches this layer and any child layers for a layer with the specified ID. + virtual KisLayerSP findLayer(int id) const; + + enum { Visible = 1, Hidden = 2, Locked = 4, Unlocked = 8 }; + + /// Returns the total number of layers in this layer, its child layers, and their child layers recursively, optionally ones with the specified properties Visible or Locked, which you can OR together. + virtual int numLayers(int type = 0) const; + +public: + /// Called when the layer is made active + virtual void activate() {}; + + /// Called when another layer is made active + virtual void deactivate() {}; + +public: + virtual Q_INT32 x() const = 0; + virtual void setX(Q_INT32) = 0; + + virtual Q_INT32 y() const = 0; + virtual void setY(Q_INT32) = 0; + + virtual KNamedCommand *moveCommand(QPoint oldPosition, QPoint newPosition); + + /// Returns an approximation of where the bounds on actual data are in this layer + virtual QRect extent() const = 0; + /// Returns the exact bounds of where the actual data resides in this layer + virtual QRect exactBounds() const = 0; + + virtual const bool visible() const; + virtual void setVisible(bool v); + KNamedCommand *setVisibleCommand(bool visiblel); + + Q_UINT8 opacity() const; + void setOpacity(Q_UINT8 val); + KNamedCommand *setOpacityCommand(Q_UINT8 val); + KNamedCommand *setOpacityCommand(Q_UINT8 prevOpacity, Q_UINT8 newOpacity); + + bool locked() const; + void setLocked(bool l); + KNamedCommand *setLockedCommand(bool locked); + + void notifyPropertyChanged(); + + bool temporary() const; + void setTemporary(bool t); + + virtual QString name() const; + virtual void setName(const QString& name); + + KisCompositeOp compositeOp() { return m_compositeOp; } + void setCompositeOp(const KisCompositeOp& compositeOp); + KNamedCommand *setCompositeOpCommand(const KisCompositeOp& compositeOp); + + KisImage *image() const { return m_image; } + virtual void setImage(KisImage *image) { m_image = image; } + + KisUndoAdapter *undoAdapter() const; + + /// paints a mask where the selection on this layer resides + virtual void paintSelection(QImage &img, Q_INT32 x, Q_INT32 y, Q_INT32 w, Q_INT32 h); + virtual void paintSelection(QImage &img, const QRect& scaledImageRect, const QSize& scaledImageSize, const QSize& imageSize); + + /// paints where no data is on this layer. Useful when it is a transparent layer stacked on top of another one + virtual void paintMaskInactiveLayers(QImage &img, Q_INT32 x, Q_INT32 y, Q_INT32 w, Q_INT32 h); + + /// Returns a thumbnail in requested size. The QImage may have transparent parts. + /// May also return 0 + virtual QImage createThumbnail(Q_INT32 w, Q_INT32 h); + + /// Accept the KisLayerVisitor (for the Visitor design pattern), should call the correct function on the KisLayerVisitor for this layer type + virtual bool accept(KisLayerVisitor &) = 0; + +private: + friend class KisGroupLayer; + + bool matchesFlags(int flags) const; + + int m_id; + int m_index; + Q_UINT8 m_opacity; + bool m_locked; + bool m_visible; + bool m_temporary; + + // XXX: keep a list of dirty rects instead of always aggegrating them + QRect m_dirtyRect; + QString m_name; + KisGroupLayerSP m_parent; + KisImage *m_image; + + // Operation used to composite this layer with the layers _under_ this layer + KisCompositeOp m_compositeOp; +}; + +// For classes that support indirect painting +class KRITACORE_EXPORT KisLayerSupportsIndirectPainting { + // To simulate the indirect painting + KisPaintDeviceSP m_temporaryTarget; + KisCompositeOp m_compositeOp; + Q_UINT8 m_compositeOpacity; +public: + // Indirect painting + void setTemporaryTarget(KisPaintDeviceSP t); + void setTemporaryCompositeOp(const KisCompositeOp& c); + void setTemporaryOpacity(Q_UINT8 o); + KisPaintDeviceSP temporaryTarget(); + KisCompositeOp temporaryCompositeOp() const; + Q_UINT8 temporaryOpacity() const; + + // Or I could make KisLayer a virtual base of KisLayerSupportsIndirectPainting and so, but + // I'm sure virtual diamond inheritance isn't as appreciated as this + virtual KisLayer* layer() = 0; +}; + +#endif // KIS_LAYER_H_ + diff --git a/krita/core/kis_layer_visitor.h b/krita/core/kis_layer_visitor.h new file mode 100644 index 00000000..4326bc54 --- /dev/null +++ b/krita/core/kis_layer_visitor.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2005 Casper Boemann <cbr@boemann.dk> + * + * 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_LAYER_VISITOR_H_ +#define KIS_LAYER_VISITOR_H_ + +#include "kis_global.h" +#include "kis_types.h" + +class KisPaintLayer; +class KisGroupLayer; +class KisPartLayer; +class KisAdjustmentLayer; + +class KisLayerVisitor { +public: + KisLayerVisitor() {}; + virtual ~KisLayerVisitor() {}; + +public: + virtual bool visit(KisPaintLayer *layer) = 0; + virtual bool visit(KisGroupLayer *layer) = 0; + virtual bool visit(KisPartLayer *layer) = 0; + virtual bool visit(KisAdjustmentLayer *layer) = 0; +}; + + +#endif // KIS_LAYER_VISITOR_H_ + diff --git a/krita/core/kis_math_toolbox.cpp b/krita/core/kis_math_toolbox.cpp new file mode 100644 index 00000000..d0f49e71 --- /dev/null +++ b/krita/core/kis_math_toolbox.cpp @@ -0,0 +1,166 @@ +/* + * This file is part of the KDE project + * + * Copyright (c) 2005 Cyrille Berger <cberger@cberger.net> + * + * 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_math_toolbox.h" + +#ifdef HAVE_OPENEXR +#include <half.h> +#endif + +#include "kis_basic_math_toolbox.h" +#include "kis_iterators_pixel.h" + + +KisMathToolbox::KisMathToolbox(KisID id) : m_id(id) +{ +} + +KisMathToolbox::~KisMathToolbox() +{ +} + +KisMathToolboxFactoryRegistry::KisMathToolboxFactoryRegistry() +{ + add(new KisBasicMathToolbox()); +} +KisMathToolboxFactoryRegistry::~KisMathToolboxFactoryRegistry() +{ +} +template<typename T> +double toDouble(Q_UINT8* data, int channelpos ) +{ + return (float)( *((T*)(data + channelpos)) ); +} + +typedef double (*PtrToDouble)(Q_UINT8*, int); + +template<typename T> +void fromDouble(Q_UINT8* data, int channelpos, double v ) +{ + *((T*)(data + channelpos)) = (T)v; +} + +typedef void (*PtrFromDouble)(Q_UINT8*, int, double); + + +void KisMathToolbox::transformToFR(KisPaintDeviceSP src, KisFloatRepresentation* fr, const QRect& rect) +{ + Q_INT32 depth = src->colorSpace()->nColorChannels(); + QMemArray<PtrToDouble> f(depth); + QValueVector<KisChannelInfo *> cis = src->colorSpace()->channels(); + for(Q_INT32 k = 0; k < depth; k++) + { + switch( cis[k]->channelValueType() ) + { + case KisChannelInfo::UINT8: + f[k] = toDouble<Q_UINT8>; + break; + case KisChannelInfo::UINT16: + f[k] = toDouble<Q_UINT16>; + break; +#ifdef HAVE_OPENEXR + case KisChannelInfo::FLOAT16: + f[k] = toDouble<half>; + break; +#endif + case KisChannelInfo::FLOAT32: + f[k] = toDouble<float>; + break; + case KisChannelInfo::INT8: + f[k] = toDouble<Q_INT8>; + break; + case KisChannelInfo::INT16: + f[k] = toDouble<Q_INT16>; + break; + default: + kdWarning() << "Unsupported value type in KisMathToolbox" << endl; + return; + } + } + + for(int i = rect.y(); i < rect.height(); i++) + { + KisHLineIteratorPixel srcIt = src->createHLineIterator(rect.x(), i, rect.width(), false ); + float *dstIt = fr->coeffs + (i-rect.y()) * fr->size * fr->depth; + while( ! srcIt.isDone() ) + { + Q_UINT8* v1 = srcIt.rawData(); + for( int k = 0; k < depth; k++) + { + *dstIt = f[k](v1, cis[k]->pos()); + ++dstIt; + } + ++srcIt; + } + } +} + +void KisMathToolbox::transformFromFR(KisPaintDeviceSP dst, KisFloatRepresentation* fr, const QRect& rect) +{ + Q_INT32 depth = dst->colorSpace()->nColorChannels(); + QMemArray<PtrFromDouble> f(depth); + QValueVector<KisChannelInfo *> cis = dst->colorSpace()->channels(); + for(Q_INT32 k = 0; k < depth; k++) + { + switch( cis[k]->channelValueType() ) + { + case KisChannelInfo::UINT8: + f[k] = fromDouble<Q_UINT8>; + break; + case KisChannelInfo::UINT16: + f[k] = fromDouble<Q_UINT16>; + break; +#ifdef HAVE_OPENEXR + case KisChannelInfo::FLOAT16: + f[k] = fromDouble<half>; + break; +#endif + case KisChannelInfo::FLOAT32: + f[k] = fromDouble<float>; + break; + case KisChannelInfo::INT8: + f[k] = fromDouble<Q_INT8>; + break; + case KisChannelInfo::INT16: + f[k] = fromDouble<Q_INT16>; + break; + default: + kdWarning() << "Unsupported value type in KisMathToolbox" << endl; + return; + } + } + for(int i = rect.y(); i < rect.height(); i++) + { + KisHLineIteratorPixel dstIt = dst->createHLineIterator(rect.x(), i, rect.width(), true ); + float *srcIt = fr->coeffs + (i-rect.y()) * fr->size * fr->depth; + while( ! dstIt.isDone() ) + { + Q_UINT8* v1 = dstIt.rawData(); + for( int k = 0; k < depth; k++) + { + f[k](v1, cis[k]->pos(), *srcIt); + ++srcIt; + } + ++dstIt; + } + } +} + +#include "kis_math_toolbox.moc" diff --git a/krita/core/kis_math_toolbox.h b/krita/core/kis_math_toolbox.h new file mode 100644 index 00000000..0ddc545f --- /dev/null +++ b/krita/core/kis_math_toolbox.h @@ -0,0 +1,123 @@ +/* + * This file is part of the KDE project + * + * Copyright (c) 2005 Cyrille Berger <cberger@cberger.net> + * + * 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_MATH_TOOLBOX_H +#define KIS_MATH_TOOLBOX_H + +#include <qobject.h> + +// typedef unsigned int uint; + +#include <kis_generic_registry.h> +#include "kis_paint_device.h" +#include "kis_types.h" + +#include <new> + +class KisMathToolbox : public QObject { + Q_OBJECT + public: + struct KisFloatRepresentation { + KisFloatRepresentation(uint nsize, uint ndepth) throw(std::bad_alloc ) : coeffs(new float[nsize*nsize*ndepth]) ,size(nsize), depth(ndepth) + { + // XXX: Valgrind shows that these are being used without being initialised. + for (Q_UINT32 i = 0; i < nsize*nsize*ndepth; ++i) { + coeffs[i] = 0; + } + } + ~KisFloatRepresentation() { if(coeffs) delete[] coeffs; } + float* coeffs; + uint size; + uint depth; + }; + typedef KisFloatRepresentation KisWavelet; + public: + KisMathToolbox(KisID id); + ~KisMathToolbox(); + public: + inline KisID id() { return m_id; }; + /** + * This function initialize a wavelet structure + * @param lay the layer that will be used for the transformation + */ + inline KisWavelet* initWavelet(KisPaintDeviceSP lay, const QRect&) throw(std::bad_alloc ); + inline uint fastWaveletTotalSteps(const QRect&); + /** + * This function reconstruct the layer from the information of a wavelet + * @param src layer from which the wavelet will be computed + * @param buff if set to 0, the buffer will be initialized by the function, + * you might want to give a buff to the function if you want to use the same buffer + * in transformToWavelet and in untransformToWavelet, use initWavelet to initialize + * the buffer + */ + virtual KisWavelet* fastWaveletTransformation(KisPaintDeviceSP src, const QRect&, KisWavelet* buff = 0) =0; + /** + * This function reconstruct the layer from the information of a wavelet + * @param dst layer on which the wavelet will be untransform + * @param wav the wavelet + * @param buff if set to 0, the buffer will be initialized by the function, + * you might want to give a buff to the function if you want to use the same buffer + * in transformToWavelet and in untransformToWavelet, use initWavelet to initialize + * the buffer + */ + virtual void fastWaveletUntransformation(KisPaintDeviceSP dst, const QRect&, KisWavelet* wav, KisWavelet* buff = 0) =0; + signals: + void nextStep(); + protected: + /** + * This function transform a paint device into a KisFloatRepresentation, this function is colorspace independant, + * for Wavelet, Pyramid and FFT the data is allways the exact value of the channel stored in a float. + */ + void transformToFR(KisPaintDeviceSP src, KisFloatRepresentation*, const QRect&); + /** + * This function transform a KisFloatRepresentation into a paint device, this function is colorspace independant, + * for Wavelet, Pyramid and FFT the data is allways the exact value of the channel stored in a float. + */ + void transformFromFR(KisPaintDeviceSP dst, KisFloatRepresentation*, const QRect&); + private: + KisID m_id; +}; + +class KisMathToolboxFactoryRegistry : public KisGenericRegistry<KisMathToolbox*> { + public: + KisMathToolboxFactoryRegistry(); + ~KisMathToolboxFactoryRegistry(); +}; + + +inline KisMathToolbox::KisWavelet* KisMathToolbox::initWavelet(KisPaintDeviceSP src, const QRect& rect) throw(std::bad_alloc ) +{ + int size; + int maxrectsize = (rect.height() < rect.width()) ? rect.width() : rect.height(); + for(size = 2; size < maxrectsize; size *= 2) ; + Q_INT32 depth = src->colorSpace()->nColorChannels(); + return new KisWavelet(size, depth); +} + +inline uint KisMathToolbox::fastWaveletTotalSteps(const QRect& rect) +{ + int size, steps; + int maxrectsize = (rect.height() < rect.width()) ? rect.width() : rect.height(); + steps = 0; + for(size = 2; size < maxrectsize; size *= 2) steps += size / 2; ; + return steps; +} + +#endif diff --git a/krita/core/kis_merge_visitor.h b/krita/core/kis_merge_visitor.h new file mode 100644 index 00000000..73331591 --- /dev/null +++ b/krita/core/kis_merge_visitor.h @@ -0,0 +1,358 @@ +/* + * Copyright (c) 2002 Patrick Julien <freak@codepimps.org> + * Copyright (c) 2005 Casper Boemann <cbr@boemann.dk> + * Copyright (c) 2006 Bart Coppens <kde@bartcoppens.be> + * + * 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_MERGE_H_ +#define KIS_MERGE_H_ + +#include <qrect.h> + +#include "kis_types.h" +#include "kis_paint_device.h" +#include "kis_layer_visitor.h" +#include "kis_painter.h" +#include "kis_image.h" +#include "kis_layer.h" +#include "kis_group_layer.h" +#include "kis_adjustment_layer.h" +#include "kis_paint_layer.h" +#include "kis_part_layer_iface.h" +#include "kis_filter.h" +#include "kis_filter_configuration.h" +#include "kis_filter_registry.h" +#include "kis_selection.h" +#include "kis_transaction.h" +#include "kis_iterators_pixel.h" + +class KisMergeVisitor : public KisLayerVisitor { +public: + /** + * Don't even _think_ of creating a merge visitor without a projection; without a projection, + * the adjustmentlayers won't work. + */ + KisMergeVisitor(KisPaintDeviceSP projection, const QRect& rc) : + KisLayerVisitor() + { + Q_ASSERT(projection); + + m_projection = projection; + m_rc = rc; + } + +private: + // Helper for the indirect painting (keep above to inhibit gcc-2.95 ICE) + template<class Target> + KSharedPtr<Target> paintIndirect(KisPaintDeviceSP source, + KSharedPtr<Target> target, + KisLayerSupportsIndirectPainting* layer, + Q_INT32 sx, Q_INT32 sy, Q_INT32 dx, Q_INT32 dy, + Q_INT32 w, Q_INT32 h) { + KisPainter gc2(target.data()); + gc2.bitBlt(dx, dy, COMPOSITE_COPY, source, + OPACITY_OPAQUE, sx, sy, w, h); + gc2.bitBlt(dx, dy, layer->temporaryCompositeOp(), layer->temporaryTarget(), + layer->temporaryOpacity(), sx, sy, w, h); + gc2.end(); + return target; + } + +public: + virtual bool visit(KisPaintLayer *layer) + { + + if (m_projection == 0) { + return false; + } + + kdDebug(41010) << "Visiting on paint layer " << layer->name() << ", visible: " << layer->visible() + << ", temporary: " << layer->temporary() << ", extent: " + << layer->extent() << ", dirty: " << layer->dirtyRect() << ", paint rect: " << m_rc << endl; + if (!layer->visible()) + return true; + + Q_INT32 sx, sy, dx, dy, w, h; + + QRect rc = layer->paintDevice()->extent() & m_rc; + + // Indirect painting? + KisPaintDeviceSP tempTarget = layer->temporaryTarget(); + if (tempTarget) { + rc = (layer->paintDevice()->extent() | tempTarget->extent()) & m_rc; + } + + sx = rc.left(); + sy = rc.top(); + w = rc.width(); + h = rc.height(); + dx = sx; + dy = sy; + + KisPainter gc(m_projection); + KisPaintDeviceSP source = layer->paintDevice(); + + if (!layer->hasMask()) { + if (tempTarget) { + KisPaintDeviceSP temp = new KisPaintDevice(source->colorSpace()); + source = paintIndirect(source, temp, layer, sx, sy, dx, dy, w, h); + } + + gc.bitBlt(dx, dy, layer->compositeOp(), source, layer->opacity(), sx, sy, w, h); + } else { + if (layer->renderMask()) { + // To display the mask, we don't do things with composite op and opacity + // This is like the gimp does it, I guess that's ok? + + // Note that here we'll use m_rc, because even if the extent of the device is + // empty, we want a full mask to be drawn! (we don't change rc, since + // it'd mess with setClean). This is because KisPainter::bitBlt &'s with + // the source device's extent. This is ok in normal circumstances, but + // we changed the default tile. Fixing this properly would mean fixing it there. + sx = m_rc.left(); + sy = m_rc.top(); + w = m_rc.width(); + h = m_rc.height(); + dx = sx; + dy = sy; + + // The problem is that the extent of the layer mask might not be extended + // enough. Check if that is the case + KisPaintDeviceSP mask = layer->getMask(); + QRect mextent = mask->extent(); + if ((mextent & m_rc) != m_rc) { + // Iterate over all pixels in the m_rc area. With just accessing the + // tiles in read-write mode, we ensure that the tiles get created if they + // do not exist. If they do, they'll remain untouched since we don't + // actually write data to it. + // XXX Admission: this is actually kind of a hack :-( + KisRectIteratorPixel it = mask->createRectIterator(sx, sy, w, h, true); + while (!it.isDone()) + ++it; + } + if (tempTarget) { + KisPaintDeviceSP temp = new KisPaintDevice(source->colorSpace()); + mask = paintIndirect(mask, temp, layer, sx, sy, dx, dy, w, h); + } + + gc.bitBlt(dx, dy, COMPOSITE_OVER, mask, OPACITY_OPAQUE, sx, sy, w, h); + } else { + KisSelectionSP mask = layer->getMaskAsSelection(); + // The indirect painting happens on the mask + if (tempTarget && layer->editMask()) { + KisPaintDeviceSP maskSrc = layer->getMask(); + KisPaintDeviceSP temp = new KisPaintDevice(maskSrc->colorSpace()); + temp = paintIndirect(maskSrc, temp, layer, sx, sy, dx, dy, w, h); + // Blegh + KisRectIteratorPixel srcIt = temp->createRectIterator(sx, sy, w, h, false); + KisRectIteratorPixel dstIt = mask->createRectIterator(sx, sy, w, h, true); + + while(!dstIt.isDone()) { + // Same as in convertMaskToSelection + *dstIt.rawData() = *srcIt.rawData(); + ++srcIt; + ++dstIt; + } + } else if (tempTarget) { + // We have a mask, and paint indirect, but not on the mask + KisPaintDeviceSP temp = new KisPaintDevice(source->colorSpace()); + source = paintIndirect(source, temp, layer, sx, sy, dx, dy, w, h); + } + + gc.bltSelection(dx, dy, + layer->compositeOp(), + source, + mask, + layer->opacity(), sx, sy, w, h); + } + } + + layer->setClean( rc ); + return true; + } + + virtual bool visit(KisGroupLayer *layer) + { + + if (m_projection == 0) { + return false; + } + + kdDebug(41010) << "Visiting on group layer " << layer->name() << ", visible: " << layer->visible() << ", extent: " + << layer->extent() << ", dirty: " << layer->dirtyRect() << ", paint rect: " << m_rc << endl; + + if (!layer->visible()) + return true; + + Q_INT32 sx, sy, dx, dy, w, h; + + // This automatically makes sure the projection is up-to-date for the specified rect. + KisPaintDeviceSP dev = layer->projection(m_rc); + QRect rc = dev->extent() & m_rc; + + sx = rc.left(); + sy = rc.top(); + w = rc.width(); + h = rc.height(); + dx = sx; + dy = sy; + + KisPainter gc(m_projection); + gc.bitBlt(dx, dy, layer->compositeOp(), dev, layer->opacity(), sx, sy, w, h); + + return true; + } + + virtual bool visit(KisPartLayer* layer) + { + + kdDebug(41010) << "Visiting on part layer " << layer->name() << ", visible: " << layer->visible() << ", extent: " + << layer->extent() << ", dirty: " << layer->dirtyRect() << ", paint rect: " << m_rc << endl; + + if (m_projection == 0) { + return false; + } + if (!layer->visible()) + return true; + + KisPaintDeviceSP dev(layer->prepareProjection(m_projection, m_rc)); + if (!dev) + return true; + + Q_INT32 sx, sy, dx, dy, w, h; + + QRect rc = dev->extent() & m_rc; + + sx= rc.left(); + sy = rc.top(); + w = rc.width(); + h = rc.height(); + dx = sx; + dy = sy; + + KisPainter gc(m_projection); + gc.bitBlt(dx, dy, layer->compositeOp() , dev, layer->opacity(), sx, sy, w, h); + + layer->setClean(rc); + return true; + } + + virtual bool visit(KisAdjustmentLayer* layer) + { + kdDebug(41010) << "Visiting on adjustment layer " << layer->name() << ", visible: " << layer->visible() << ", extent: " + << layer->extent() << ", dirty: " << layer->dirtyRect() << ", paint rect: " << m_rc << endl; + + if (m_projection == 0) { + return true; + } + + if (!layer->visible()) + return true; + + KisPaintDeviceSP tempTarget = layer->temporaryTarget(); + if (tempTarget) { + m_rc = (layer->extent() | tempTarget->extent()) & m_rc; + } + + if (m_rc.width() == 0 || m_rc.height() == 0) // Don't even try + return true; + + KisFilterConfiguration * cfg = layer->filter(); + if (!cfg) return false; + + + KisFilter * f = KisFilterRegistry::instance()->get( cfg->name() ); + if (!f) return false; + + // Possibly enlarge the rect that changed (like for convolution filters) + // m_rc = f->enlargeRect(m_rc, cfg); + KisSelectionSP selection = layer->selection(); + + // Copy of the projection -- use the copy-on-write trick. XXX NO COPY ON WRITE YET =( + //KisPaintDeviceSP tmp = new KisPaintDevice(*m_projection); + KisPaintDeviceSP tmp = 0; + KisSelectionSP sel = selection; + // If there's a selection, only keep the selected bits + if (selection != 0) { + tmp = new KisPaintDevice(m_projection->colorSpace()); + + KisPainter gc(tmp); + QRect selectedRect = selection->selectedRect(); + selectedRect &= m_rc; + + if (selectedRect.width() == 0 || selectedRect.height() == 0) // Don't even try + return true; + + // Don't forget that we need to take into account the extended sourcing area as well + //selectedRect = f->enlargeRect(selectedRect, cfg); + + //kdDebug() << k_funcinfo << selectedRect << endl; + tmp->setX(selection->getX()); + tmp->setY(selection->getY()); + + // Indirect painting + if (tempTarget) { + sel = new KisSelection(); + sel = paintIndirect(selection.data(), sel, layer, m_rc.left(), m_rc.top(), + m_rc.left(), m_rc.top(), m_rc.width(), m_rc.height()); + } + + gc.bitBlt(selectedRect.x(), selectedRect.y(), COMPOSITE_COPY, m_projection, + selectedRect.x(), selectedRect.y(), + selectedRect.width(), selectedRect.height()); + gc.end(); + } else { + tmp = new KisPaintDevice(*m_projection); + } + + // Some filters will require usage of oldRawData, which is not available without + // a transaction! + KisTransaction* cmd = new KisTransaction("", tmp); + + // Filter the temporary paint device -- remember, these are only the selected bits, + // if there was a selection. + f->process(tmp, tmp, cfg, m_rc); + + delete cmd; + + // Copy the filtered bits onto the projection + KisPainter gc(m_projection); + if (selection) + gc.bltSelection(m_rc.left(), m_rc.top(), + COMPOSITE_OVER, tmp, sel, layer->opacity(), + m_rc.left(), m_rc.top(), m_rc.width(), m_rc.height()); + else + gc.bitBlt(m_rc.left(), m_rc.top(), + COMPOSITE_OVER, tmp, layer->opacity(), + m_rc.left(), m_rc.top(), m_rc.width(), m_rc.height()); + gc.end(); + + // Copy the finished projection onto the cache + gc.begin(layer->cachedPaintDevice()); + gc.bitBlt(m_rc.left(), m_rc.top(), + COMPOSITE_COPY, m_projection, OPACITY_OPAQUE, + m_rc.left(), m_rc.top(), m_rc.width(), m_rc.height()); + layer->setClean(m_rc); + return true; + } + +private: + KisPaintDeviceSP m_projection; + QRect m_rc; +}; + +#endif // KIS_MERGE_H_ + diff --git a/krita/core/kis_meta_registry.cc b/krita/core/kis_meta_registry.cc new file mode 100644 index 00000000..70f21f76 --- /dev/null +++ b/krita/core/kis_meta_registry.cc @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2005 Boudewijn Rempt <boud@valdyas.org> + * + * 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 <qstringlist.h> +#include <qdir.h> +#include <kstandarddirs.h> +#include <kglobal.h> + +#include <config.h> +#include LCMS_HEADER + +#include <kis_colorspace_factory_registry.h> +#include <kis_math_toolbox.h> +#include <kis_meta_registry.h> + +KisMetaRegistry * KisMetaRegistry::m_singleton = 0; + +KisMetaRegistry::KisMetaRegistry() +{ + // Create the colorspaces and load the profiles + + KGlobal::instance()->dirs()->addResourceType("kis_profiles", + KStandardDirs::kde_default("data") + "krita/profiles/"); + + // Add those things here as well, since we are not yet using KisDoc's KisFactory instance (which inits these as well) + KGlobal::instance()->dirs()->addResourceType("kis_profiles", KStandardDirs::kde_default("data") + "krita/profiles/"); + KGlobal::instance()->dirs()->addResourceDir("kis_profiles", "/usr/share/color/icc"); + KGlobal::instance()->dirs()->addResourceDir("kis_profiles", QDir::homeDirPath() + QString("/.icc/")); + KGlobal::instance()->dirs()->addResourceDir("kis_profiles", QDir::homeDirPath() + QString("/.color/icc/")); + + QStringList profileFilenames; + profileFilenames += KGlobal::instance()->dirs()->findAllResources("kis_profiles", "*.icm", true /* recursive */); + profileFilenames += KGlobal::instance()->dirs()->findAllResources("kis_profiles", "*.ICM", true); + profileFilenames += KGlobal::instance()->dirs()->findAllResources("kis_profiles", "*.ICC", true); + profileFilenames += KGlobal::instance()->dirs()->findAllResources("kis_profiles", "*.icc", true); + // Set lcms to return NUll/false etc from failing calls, rather than aborting the app. + cmsErrorAction(LCMS_ERROR_SHOW); + + m_csRegistry = new KisColorSpaceFactoryRegistry(profileFilenames); + m_mtRegistry = new KisMathToolboxFactoryRegistry(); +} + +KisMetaRegistry::~KisMetaRegistry() +{ +} + +KisMetaRegistry * KisMetaRegistry::instance() +{ + if ( KisMetaRegistry::m_singleton == 0 ) { + KisMetaRegistry::m_singleton = new KisMetaRegistry(); + } + return KisMetaRegistry::m_singleton; +} + diff --git a/krita/core/kis_meta_registry.h b/krita/core/kis_meta_registry.h new file mode 100644 index 00000000..42aeee3d --- /dev/null +++ b/krita/core/kis_meta_registry.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2005 Boudewijn Rempt <boud@valdyas.org> + * + * 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_META_REGISTRY_ +#define _KIS_META_REGISTRY_ + +class KisColorSpaceFactoryRegistry; +class KisMathToolboxFactoryRegistry; + +/** + * A single singleton that provides access to several registries. + * + * XXX: Maybe this should go into the SDK + */ +class KisMetaRegistry { + +public: + + virtual ~KisMetaRegistry(); + static KisMetaRegistry* instance(); + + KisColorSpaceFactoryRegistry * csRegistry() { return m_csRegistry; }; + KisMathToolboxFactoryRegistry* mtRegistry() { return m_mtRegistry; }; +private: + + KisMetaRegistry(); + KisMetaRegistry( const KisMetaRegistry& ); + KisMetaRegistry operator=( const KisMetaRegistry& ); + + static KisMetaRegistry * m_singleton; + + KisColorSpaceFactoryRegistry * m_csRegistry; + KisMathToolboxFactoryRegistry* m_mtRegistry; +}; +#endif diff --git a/krita/core/kis_nameserver.cc b/krita/core/kis_nameserver.cc new file mode 100644 index 00000000..bdc59ca2 --- /dev/null +++ b/krita/core/kis_nameserver.cc @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2002 Patrick Julien <freak@codepimps.org> + * + * 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_nameserver.h" + +KisNameServer::KisNameServer(const QString& prefix, Q_INT32 seed) +{ + m_prefix = prefix; + m_generator = seed; +} + +KisNameServer::~KisNameServer() +{ +} + +QString KisNameServer::name() +{ + return m_prefix.arg(m_generator++); +} + +Q_INT32 KisNameServer::currentSeed() const +{ + return m_generator; +} + +Q_INT32 KisNameServer::number() +{ + return m_generator++; +} + +void KisNameServer::rollback() +{ + m_generator--; +} + diff --git a/krita/core/kis_nameserver.h b/krita/core/kis_nameserver.h new file mode 100644 index 00000000..1e4c560d --- /dev/null +++ b/krita/core/kis_nameserver.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2002 Patrick Julien <freak@codepimps.org> + * + * 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_NAMESERVER_H_ +#define KIS_NAMESERVER_H_ + +#include <qstring.h> +#include "kis_global.h" + +class KisNameServer { +public: + KisNameServer(const QString& prefix, Q_INT32 seed = 1); + ~KisNameServer(); + + QString name(); + Q_INT32 number(); + Q_INT32 currentSeed() const; + void rollback(); + +private: + Q_INT32 m_generator; + QString m_prefix; +}; + +#endif // KIS_NAMESERVER_H_ + diff --git a/krita/core/kis_paint_device.cc b/krita/core/kis_paint_device.cc new file mode 100644 index 00000000..4bf15c1d --- /dev/null +++ b/krita/core/kis_paint_device.cc @@ -0,0 +1,1285 @@ +/* + * Copyright (c) 2002 Patrick Julien <freak@codepimps.org> + * Copyright (c) 2004 Boudewijn Rempt <boud@valdyas.org> + * + * 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 <qrect.h> +#include <qwmatrix.h> +#include <qimage.h> +#include <qdatetime.h> +#include <qapplication.h> +#include <qvaluelist.h> +#include <qtimer.h> + +#include <kcommand.h> +#include <klocale.h> +#include <kdebug.h> + +#include <KoStore.h> + +#include "kis_global.h" +#include "kis_types.h" +#include "kis_painter.h" +#include "kis_fill_painter.h" +#include "kis_undo_adapter.h" +#include "kis_iterator.h" +#include "kis_iterators_pixel.h" +#include "kis_iteratorpixeltrait.h" +#include "kis_random_accessor.h" +#include "kis_random_sub_accessor.h" +#include "kis_transaction.h" +#include "kis_profile.h" +#include "kis_color.h" +#include "kis_integer_maths.h" +#include "kis_colorspace_factory_registry.h" +#include "kis_selection.h" +#include "kis_layer.h" +#include "kis_paint_device_iface.h" +#include "kis_paint_device.h" +#include "kis_datamanager.h" +#include "kis_memento.h" +#include "kis_selection.h" + +#include "kis_exif_info.h" + +namespace { + + class KisPaintDeviceCommand : public KNamedCommand { + typedef KNamedCommand super; + + public: + KisPaintDeviceCommand(const QString& name, KisPaintDeviceSP paintDevice); + virtual ~KisPaintDeviceCommand() {} + + virtual void execute() = 0; + virtual void unexecute() = 0; + + protected: + void setUndo(bool undo); + + KisPaintDeviceSP m_paintDevice; + }; + + KisPaintDeviceCommand::KisPaintDeviceCommand(const QString& name, KisPaintDeviceSP paintDevice) : + super(name), m_paintDevice(paintDevice) + { + } + + void KisPaintDeviceCommand::setUndo(bool undo) + { + if (m_paintDevice->undoAdapter()) { + m_paintDevice->undoAdapter()->setUndo(undo); + } + } + + class MoveCommand : public KNamedCommand { + typedef KNamedCommand super; + + public: + MoveCommand(KisPaintDeviceSP device, const QPoint& oldpos, const QPoint& newpos); + virtual ~MoveCommand(); + + virtual void execute(); + virtual void unexecute(); + + private: + void moveTo(const QPoint& pos); + void undoOff(); + void undoOn(); + + private: + KisPaintDeviceSP m_device; + QPoint m_oldPos; + QPoint m_newPos; + }; + + MoveCommand::MoveCommand(KisPaintDeviceSP device, const QPoint& oldpos, const QPoint& newpos) : + super(i18n("Move Layer")) + { + m_device = device; + m_oldPos = oldpos; + m_newPos = newpos; + } + + MoveCommand::~MoveCommand() + { + } + + void MoveCommand::undoOff() + { + if (m_device->undoAdapter()) { + m_device->undoAdapter()->setUndo(false); + } + } + + void MoveCommand::undoOn() + { + if (m_device->undoAdapter()) { + m_device->undoAdapter()->setUndo(true); + } + } + + void MoveCommand::execute() + { + undoOff(); + moveTo(m_newPos); + undoOn(); + } + + void MoveCommand::unexecute() + { + undoOff(); + moveTo(m_oldPos); + undoOn(); + } + + void MoveCommand::moveTo(const QPoint& pos) + { + m_device->move(pos.x(), pos.y()); + } + + class KisConvertLayerTypeCmd : public KNamedCommand { + typedef KNamedCommand super; + + public: + KisConvertLayerTypeCmd(KisUndoAdapter *adapter, KisPaintDeviceSP paintDevice, + KisDataManagerSP beforeData, KisColorSpace * beforeColorSpace, + KisDataManagerSP afterData, KisColorSpace * afterColorSpace + ) : super(i18n("Convert Layer Type")) + { + m_adapter = adapter; + m_paintDevice = paintDevice; + m_beforeData = beforeData; + m_beforeColorSpace = beforeColorSpace; + m_afterData = afterData; + m_afterColorSpace = afterColorSpace; + } + + virtual ~KisConvertLayerTypeCmd() + { + } + + public: + virtual void execute() + { + m_adapter->setUndo(false); + m_paintDevice->setData(m_afterData, m_afterColorSpace); + m_adapter->setUndo(true); + } + + virtual void unexecute() + { + m_adapter->setUndo(false); + m_paintDevice->setData(m_beforeData, m_beforeColorSpace); + m_adapter->setUndo(true); + } + + private: + KisUndoAdapter *m_adapter; + + KisPaintDeviceSP m_paintDevice; + + KisDataManagerSP m_beforeData; + KisColorSpace * m_beforeColorSpace; + + KisDataManagerSP m_afterData; + KisColorSpace * m_afterColorSpace; + }; + +} + +KisPaintDevice::KisPaintDevice(KisColorSpace * colorSpace, const char * name) : + QObject(0, name), KShared(), m_exifInfo(0), m_lock( false ) +{ + if (colorSpace == 0) { + kdWarning(41001) << "Cannot create paint device without colorstrategy!\n"; + return; + } + m_longRunningFilterTimer = 0; + m_dcop = 0; + + m_x = 0; + m_y = 0; + + m_pixelSize = colorSpace->pixelSize(); + m_nChannels = colorSpace->nChannels(); + + Q_UINT8* defPixel = new Q_UINT8 [ m_pixelSize ]; + colorSpace->fromQColor(Qt::black, OPACITY_TRANSPARENT, defPixel); + + m_datamanager = new KisDataManager(m_pixelSize, defPixel); + delete [] defPixel; + + Q_CHECK_PTR(m_datamanager); + m_extentIsValid = true; + + m_parentLayer = 0; + + m_colorSpace = colorSpace; + + m_hasSelection = false; + m_selectionDeselected = false; + m_selection = 0; + +} + +KisPaintDevice::KisPaintDevice(KisLayer *parent, KisColorSpace * colorSpace, const char * name) : + QObject(0, name), KShared(), m_exifInfo(0), m_lock( false ) +{ + + m_longRunningFilterTimer = 0; + m_dcop = 0; + + m_x = 0; + m_y = 0; + + m_hasSelection = false; + m_selectionDeselected = false; + m_selection = 0; + + m_parentLayer = parent; + + if (colorSpace == 0 && parent != 0 && parent->image() != 0) { + m_colorSpace = parent->image()->colorSpace(); + } + else { + m_colorSpace = colorSpace; + } + + Q_ASSERT( m_colorSpace ); + + m_pixelSize = m_colorSpace->pixelSize(); + m_nChannels = m_colorSpace->nChannels(); + + Q_UINT8* defPixel = new Q_UINT8[ m_pixelSize ]; + m_colorSpace->fromQColor(Qt::black, OPACITY_TRANSPARENT, defPixel); + + m_datamanager = new KisDataManager(m_pixelSize, defPixel); + delete [] defPixel; + Q_CHECK_PTR(m_datamanager); + m_extentIsValid = true; + + if ( QString ( name ) == QString( "Layer 1" ) ) { + m_longRunningFilters = m_colorSpace->createBackgroundFilters(); + + if (!m_longRunningFilters.isEmpty()) { + m_longRunningFilterTimer = new QTimer(this); + connect(m_longRunningFilterTimer, SIGNAL(timeout()), this, SLOT(runBackgroundFilters())); + m_longRunningFilterTimer->start(2000); + } + } +} + + +KisPaintDevice::KisPaintDevice(const KisPaintDevice& rhs) : QObject(), KShared(rhs) +{ + if (this != &rhs) { + m_longRunningFilterTimer = 0; + m_parentLayer = 0; + m_dcop = rhs.m_dcop; + if (rhs.m_datamanager) { + m_datamanager = new KisDataManager(*rhs.m_datamanager); + Q_CHECK_PTR(m_datamanager); + } + else { + kdWarning() << "rhs " << rhs.name() << " has no datamanager\n"; + } + m_extentIsValid = rhs.m_extentIsValid; + m_x = rhs.m_x; + m_y = rhs.m_y; + m_colorSpace = rhs.m_colorSpace; + m_hasSelection = rhs.m_hasSelection; + + if ( m_hasSelection ) + m_selection = new KisSelection(*rhs.m_selection); + else + m_selection = 0; + + m_pixelSize = rhs.m_pixelSize; + m_nChannels = rhs.m_nChannels; + if(rhs.m_exifInfo) + { + m_exifInfo = new KisExifInfo(*rhs.m_exifInfo); + } + else { + m_exifInfo = 0; + } + } +} + +KisPaintDevice::~KisPaintDevice() +{ + delete m_dcop; + delete m_longRunningFilterTimer; + QValueList<KisFilter*>::iterator it; + QValueList<KisFilter*>::iterator end = m_longRunningFilters.end(); + for (it = m_longRunningFilters.begin(); it != end; ++it) { + KisFilter * f = (*it); + delete f; + } + m_longRunningFilters.clear(); + //delete m_exifInfo; +} + +DCOPObject *KisPaintDevice::dcopObject() +{ + if (!m_dcop) { + m_dcop = new KisPaintDeviceIface(this); + Q_CHECK_PTR(m_dcop); + } + return m_dcop; +} + +KisLayer *KisPaintDevice::parentLayer() const +{ + return m_parentLayer; +} + +void KisPaintDevice::setParentLayer(KisLayer *parentLayer) +{ + m_parentLayer = parentLayer; +} + +void KisPaintDevice::setDirty(const QRect & rc) +{ + if (m_parentLayer) m_parentLayer->setDirty(rc); +} + +void KisPaintDevice::setDirty() +{ + if (m_parentLayer) m_parentLayer->setDirty(); +} + +KisImage *KisPaintDevice::image() const +{ + if (m_parentLayer) { + return m_parentLayer->image(); + } else { + return 0; + } +} + + +void KisPaintDevice::move(Q_INT32 x, Q_INT32 y) +{ + QRect dirtyRect = extent(); + + m_x = x; + m_y = y; + + dirtyRect |= extent(); + + if(m_selection) + { + m_selection->setX(x); + m_selection->setY(y); + } + + setDirty(dirtyRect); + + emit positionChanged(this); +} + +void KisPaintDevice::move(const QPoint& pt) +{ + move(pt.x(), pt.y()); +} + +KNamedCommand * KisPaintDevice::moveCommand(Q_INT32 x, Q_INT32 y) +{ + KNamedCommand * cmd = new MoveCommand(this, QPoint(m_x, m_y), QPoint(x, y)); + Q_CHECK_PTR(cmd); + cmd->execute(); + return cmd; +} + +void KisPaintDevice::extent(Q_INT32 &x, Q_INT32 &y, Q_INT32 &w, Q_INT32 &h) const +{ + m_datamanager->extent(x, y, w, h); + x += m_x; + y += m_y; +} + +QRect KisPaintDevice::extent() const +{ + Q_INT32 x, y, w, h; + extent(x, y, w, h); + return QRect(x, y, w, h); +} + +bool KisPaintDevice::extentIsValid() const +{ + return m_extentIsValid; +} + +void KisPaintDevice::setExtentIsValid(bool isValid) +{ + m_extentIsValid = isValid; +} + +void KisPaintDevice::exactBounds(Q_INT32 &x, Q_INT32 &y, Q_INT32 &w, Q_INT32 &h) const +{ + QRect r = exactBounds(); + x = r.x(); + y = r.y(); + w = r.width(); + h = r.height(); +} + +QRect KisPaintDevice::exactBoundsOldMethod() const +{ + Q_INT32 x, y, w, h, boundX, boundY, boundW, boundH; + extent(x, y, w, h); + + extent(boundX, boundY, boundW, boundH); + + const Q_UINT8* defaultPixel = m_datamanager->defaultPixel(); + + bool found = false; + + for (Q_INT32 y2 = y; y2 < y + h ; ++y2) { + KisHLineIterator it = const_cast<KisPaintDevice *>(this)->createHLineIterator(x, y2, w, false); + while (!it.isDone() && found == false) { + if (memcmp(it.rawData(), defaultPixel, m_pixelSize) != 0) { + boundY = y2; + found = true; + break; + } + ++it; + } + if (found) break; + } + + found = false; + + for (Q_INT32 y2 = y + h; y2 > y ; --y2) { + KisHLineIterator it = const_cast<KisPaintDevice *>(this)->createHLineIterator(x, y2, w, false); + while (!it.isDone() && found == false) { + if (memcmp(it.rawData(), defaultPixel, m_pixelSize) != 0) { + boundH = y2 - boundY + 1; + found = true; + break; + } + ++it; + } + if (found) break; + } + found = false; + + for (Q_INT32 x2 = x; x2 < x + w ; ++x2) { + KisVLineIterator it = const_cast<KisPaintDevice *>(this)->createVLineIterator(x2, y, h, false); + while (!it.isDone() && found == false) { + if (memcmp(it.rawData(), defaultPixel, m_pixelSize) != 0) { + boundX = x2; + found = true; + break; + } + ++it; + } + if (found) break; + } + + found = false; + + // Look for right edge ) + for (Q_INT32 x2 = x + w; x2 > x ; --x2) { + KisVLineIterator it = const_cast<KisPaintDevice *>(this)->createVLineIterator(x2, y, h, false); + while (!it.isDone() && found == false) { + if (memcmp(it.rawData(), defaultPixel, m_pixelSize) != 0) { + boundW = x2 - boundX + 1; // XXX: I commented this + // +1 out, but why? It + // should be correct, since + // we've found the first + // pixel that should be + // included, and it should + // be added to the width. + found = true; + break; + } + ++it; + } + if (found) break; + } + + return QRect(boundX, boundY, boundW, boundH); +} + +QRect KisPaintDevice::exactBoundsImprovedOldMethod() const +{ + // Solution n°2 + Q_INT32 x, y, w, h, boundX2, boundY2, boundW2, boundH2; + extent(x, y, w, h); + extent(boundX2, boundY2, boundW2, boundH2); + + const Q_UINT8* defaultPixel = m_datamanager->defaultPixel(); + bool found = false; + { + KisHLineIterator it = const_cast<KisPaintDevice *>(this)->createHLineIterator(x, y, w, false); + for (Q_INT32 y2 = y; y2 < y + h ; ++y2) { + while (!it.isDone() && found == false) { + if (memcmp(it.rawData(), defaultPixel, m_pixelSize) != 0) { + boundY2 = y2; + found = true; + break; + } + ++it; + } + if (found) break; + it.nextRow(); + } + } + + found = false; + + for (Q_INT32 y2 = y + h; y2 > y ; --y2) { + KisHLineIterator it = const_cast<KisPaintDevice *>(this)->createHLineIterator(x, y2, w, false); + while (!it.isDone() && found == false) { + if (memcmp(it.rawData(), defaultPixel, m_pixelSize) != 0) { + boundH2 = y2 - boundY2 + 1; + found = true; + break; + } + ++it; + } + if (found) break; + } + found = false; + + { + KisVLineIterator it = const_cast<KisPaintDevice *>(this)->createVLineIterator(x, boundY2, boundH2, false); + for (Q_INT32 x2 = x; x2 < x + w ; ++x2) { + while (!it.isDone() && found == false) { + if (memcmp(it.rawData(), defaultPixel, m_pixelSize) != 0) { + boundX2 = x2; + found = true; + break; + } + ++it; + } + if (found) break; + it.nextCol(); + } + } + + found = false; + + // Look for right edge ) + { + for (Q_INT32 x2 = x + w; x2 > x ; --x2) { + KisVLineIterator it = const_cast<KisPaintDevice *>(this)->createVLineIterator(/*x + w*/ x2, boundY2, boundH2, false); + while (!it.isDone() && found == false) { + if (memcmp(it.rawData(), defaultPixel, m_pixelSize) != 0) { + boundW2 = x2 - boundX2 + 1; // XXX: I commented this + // +1 out, but why? It + // should be correct, since + // we've found the first + // pixel that should be + // included, and it should + // be added to the width. + found = true; + break; + } + ++it; + } + if (found) break; + } + } + return QRect(boundX2, boundY2, boundW2, boundH2); +} + + +QRect KisPaintDevice::exactBounds() const +{ + QRect r2 = exactBoundsImprovedOldMethod(); + return r2; +} + +void KisPaintDevice::crop(Q_INT32 x, Q_INT32 y, Q_INT32 w, Q_INT32 h) +{ + m_datamanager->setExtent(x - m_x, y - m_y, w, h); +} + + +void KisPaintDevice::crop(QRect r) +{ + r.moveBy(-m_x, -m_y); m_datamanager->setExtent(r); +} + +void KisPaintDevice::clear() +{ + m_datamanager->clear(); +} + +void KisPaintDevice::fill(Q_INT32 x, Q_INT32 y, Q_INT32 w, Q_INT32 h, const Q_UINT8 *fillPixel) +{ + m_datamanager->clear(x, y, w, h, fillPixel); +} + +void KisPaintDevice::mirrorX() +{ + QRect r; + if (hasSelection()) { + r = selection()->selectedRect(); + } + else { + r = exactBounds(); + } + + for (Q_INT32 y = r.top(); y <= r.bottom(); ++y) { + KisHLineIteratorPixel srcIt = createHLineIterator(r.x(), y, r.width(), false); + KisHLineIteratorPixel dstIt = createHLineIterator(r.x(), y, r.width(), true); + + dstIt += r.width() - 1; + + while (!srcIt.isDone()) { + if (srcIt.isSelected()) { + memcpy(dstIt.rawData(), srcIt.oldRawData(), m_pixelSize); + } + ++srcIt; + --dstIt; + + } + } + if (m_parentLayer) { + m_parentLayer->setDirty(r); + } +} + +void KisPaintDevice::mirrorY() +{ + /* Read a line from bottom to top and and from top to bottom and write their values to each other */ + QRect r; + if (hasSelection()) { + r = selection()->selectedRect(); + } + else { + r = exactBounds(); + } + + + Q_INT32 y1, y2; + for (y1 = r.top(), y2 = r.bottom(); y1 <= r.bottom(); ++y1, --y2) { + KisHLineIteratorPixel itTop = createHLineIterator(r.x(), y1, r.width(), true); + KisHLineIteratorPixel itBottom = createHLineIterator(r.x(), y2, r.width(), false); + while (!itTop.isDone() && !itBottom.isDone()) { + if (itBottom.isSelected()) { + memcpy(itTop.rawData(), itBottom.oldRawData(), m_pixelSize); + } + ++itBottom; + ++itTop; + } + } + + if (m_parentLayer) { + m_parentLayer->setDirty(r); + } +} + +KisMementoSP KisPaintDevice::getMemento() +{ + return m_datamanager->getMemento(); +} + +void KisPaintDevice::rollback(KisMementoSP memento) { m_datamanager->rollback(memento); } + +void KisPaintDevice::rollforward(KisMementoSP memento) { m_datamanager->rollforward(memento); } + +bool KisPaintDevice::write(KoStore *store) +{ + bool retval = m_datamanager->write(store); + emit ioProgress(100); + + return retval; +} + +bool KisPaintDevice::read(KoStore *store) +{ + bool retval = m_datamanager->read(store); + emit ioProgress(100); + + return retval; +} + +void KisPaintDevice::convertTo(KisColorSpace * dstColorSpace, Q_INT32 renderingIntent) +{ + kdDebug(41004) << "Converting " << name() << " to " << dstColorSpace->id().id() << " from " + << m_colorSpace->id().id() << "\n"; + if ( colorSpace() == dstColorSpace ) + { + return; + } + + KisPaintDevice dst(dstColorSpace); + dst.setX(getX()); + dst.setY(getY()); + + Q_INT32 x, y, w, h; + extent(x, y, w, h); + + for (Q_INT32 row = y; row < y + h; ++row) { + + Q_INT32 column = x; + Q_INT32 columnsRemaining = w; + + while (columnsRemaining > 0) { + + Q_INT32 numContiguousDstColumns = dst.numContiguousColumns(column, row, row); + Q_INT32 numContiguousSrcColumns = numContiguousColumns(column, row, row); + + Q_INT32 columns = QMIN(numContiguousDstColumns, numContiguousSrcColumns); + columns = QMIN(columns, columnsRemaining); + + //const Q_UINT8 *srcData = pixel(column, row); + //Q_UINT8 *dstData = dst.writablePixel(column, row); + KisHLineIteratorPixel srcIt = createHLineIterator(column, row, columns, false); + KisHLineIteratorPixel dstIt = dst.createHLineIterator(column, row, columns, true); + + const Q_UINT8 *srcData = srcIt.rawData(); + Q_UINT8 *dstData = dstIt.rawData(); + + + m_colorSpace->convertPixelsTo(srcData, dstData, dstColorSpace, columns, renderingIntent); + + column += columns; + columnsRemaining -= columns; + } + } + + KisDataManagerSP oldData = m_datamanager; + KisColorSpace *oldColorSpace = m_colorSpace; + + setData(dst.m_datamanager, dstColorSpace); + + if (undoAdapter() && undoAdapter()->undo()) { + undoAdapter()->addCommand(new KisConvertLayerTypeCmd(undoAdapter(), this, oldData, oldColorSpace, m_datamanager, m_colorSpace)); + } +} + +void KisPaintDevice::setProfile(KisProfile * profile) +{ + if (profile == 0) return; + + KisColorSpace * dstSpace = + KisMetaRegistry::instance()->csRegistry()->getColorSpace( colorSpace()->id(), + profile); + if (dstSpace) + m_colorSpace = dstSpace; + +} + +void KisPaintDevice::setData(KisDataManagerSP data, KisColorSpace * colorSpace) +{ + m_datamanager = data; + m_colorSpace = colorSpace; + m_pixelSize = m_colorSpace->pixelSize(); + m_nChannels = m_colorSpace->nChannels(); + + if (m_parentLayer) { + m_parentLayer->setDirty(extent()); + m_parentLayer->notifyPropertyChanged(); + } +} + +KisUndoAdapter *KisPaintDevice::undoAdapter() const +{ + if (m_parentLayer && m_parentLayer->image()) { + return m_parentLayer->image()->undoAdapter(); + } + return 0; +} + +void KisPaintDevice::convertFromQImage(const QImage& image, const QString &srcProfileName, + Q_INT32 offsetX, Q_INT32 offsetY) +{ + QImage img = image; + + // Krita is little-endian inside. + if (img.bitOrder() == QImage::LittleEndian) { + img = img.convertBitOrder(QImage::BigEndian); + } + kdDebug() << k_funcinfo << img.bitOrder()<< endl; + // Krita likes bgra (convertDepth returns *this is the img is alread 32 bits) + img = img.convertDepth( 32 ); +#if 0 + // XXX: Apply import profile + if (colorSpace() == KisMetaRegistry::instance()->csRegistry() ->getColorSpace(KisID("RGBA",""),"")) { + writeBytes(img.bits(), 0, 0, img.width(), img.height()); + } + else { +#endif + Q_UINT8 * dstData = new Q_UINT8[img.width() * img.height() * pixelSize()]; + KisMetaRegistry::instance()->csRegistry() + ->getColorSpace(KisID("RGBA",""),srcProfileName)-> + convertPixelsTo(img.bits(), dstData, colorSpace(), img.width() * img.height()); + writeBytes(dstData, offsetX, offsetY, img.width(), img.height()); +// } +} + +QImage KisPaintDevice::convertToQImage(KisProfile * dstProfile, float exposure) +{ + Q_INT32 x1; + Q_INT32 y1; + Q_INT32 w; + Q_INT32 h; + + x1 = - getX(); + y1 = - getY(); + + if (image()) { + w = image()->width(); + h = image()->height(); + } + else { + extent(x1, y1, w, h); + } + + return convertToQImage(dstProfile, x1, y1, w, h, exposure); +} + +// XXX: is this faster than building the QImage ourselves? It makes +QImage KisPaintDevice::convertToQImage(KisProfile * dstProfile, Q_INT32 x1, Q_INT32 y1, Q_INT32 w, Q_INT32 h, float exposure) +{ + if (w < 0) + return QImage(); + + if (h < 0) + return QImage(); + + Q_UINT8 * data = new Q_UINT8 [w * h * m_pixelSize]; + Q_CHECK_PTR(data); + + // XXX: Is this really faster than converting line by line and building the QImage directly? + // This copies potentially a lot of data. + readBytes(data, x1, y1, w, h); + QImage image = colorSpace()->convertToQImage(data, w, h, dstProfile, INTENT_PERCEPTUAL, exposure); + delete[] data; + + return image; +} + +KisPaintDeviceSP KisPaintDevice::createThumbnailDevice(Q_INT32 w, Q_INT32 h) +{ + KisPaintDeviceSP thumbnail = new KisPaintDevice(colorSpace(), "thumbnail"); + + thumbnail->clear(); + + int srcw, srch; + if( image() ) + { + srcw = image()->width(); + srch = image()->height(); + } + else + { + const QRect e = exactBounds(); + srcw = e.width(); + srch = e.height(); + } + + if (w > srcw) + { + w = srcw; + h = Q_INT32(double(srcw) / w * h); + } + if (h > srch) + { + h = srch; + w = Q_INT32(double(srch) / h * w); + } + + if (srcw > srch) + h = Q_INT32(double(srch) / srcw * w); + else if (srch > srcw) + w = Q_INT32(double(srcw) / srch * h); + + for (Q_INT32 y=0; y < h; ++y) { + Q_INT32 iY = (y * srch ) / h; + for (Q_INT32 x=0; x < w; ++x) { + Q_INT32 iX = (x * srcw ) / w; + thumbnail->setPixel(x, y, colorAt(iX, iY)); + } + } + + return thumbnail; + +} + + +QImage KisPaintDevice::createThumbnail(Q_INT32 w, Q_INT32 h) +{ + int srcw, srch; + if( image() ) + { + srcw = image()->width(); + srch = image()->height(); + } + else + { + const QRect e = extent(); + srcw = e.width(); + srch = e.height(); + } + + if (w > srcw) + { + w = srcw; + h = Q_INT32(double(srcw) / w * h); + } + if (h > srch) + { + h = srch; + w = Q_INT32(double(srch) / h * w); + } + + if (srcw > srch) + h = Q_INT32(double(srch) / srcw * w); + else if (srch > srcw) + w = Q_INT32(double(srcw) / srch * h); + + QColor c; + Q_UINT8 opacity; + QImage img(w,h,32); + + for (Q_INT32 y=0; y < h; ++y) { + Q_INT32 iY = (y * srch ) / h; + for (Q_INT32 x=0; x < w; ++x) { + Q_INT32 iX = (x * srcw ) / w; + pixel(iX, iY, &c, &opacity); + const QRgb rgb = c.rgb(); + img.setPixel(x, y, qRgba(qRed(rgb), qGreen(rgb), qBlue(rgb), opacity)); + } + } + + return img; +} + +KisRectIteratorPixel KisPaintDevice::createRectIterator(Q_INT32 left, Q_INT32 top, Q_INT32 w, Q_INT32 h, bool writable) +{ + if(hasSelection()) + return KisRectIteratorPixel(this, m_datamanager, m_selection->m_datamanager, left, top, w, h, m_x, m_y, writable); + else + return KisRectIteratorPixel(this, m_datamanager, NULL, left, top, w, h, m_x, m_y, writable); +} + +KisHLineIteratorPixel KisPaintDevice::createHLineIterator(Q_INT32 x, Q_INT32 y, Q_INT32 w, bool writable) +{ + if(hasSelection()) + return KisHLineIteratorPixel(this, m_datamanager, m_selection->m_datamanager, x, y, w, m_x, m_y, writable); + else + return KisHLineIteratorPixel(this, m_datamanager, NULL, x, y, w, m_x, m_y, writable); +} + +KisVLineIteratorPixel KisPaintDevice::createVLineIterator(Q_INT32 x, Q_INT32 y, Q_INT32 h, bool writable) +{ + if(hasSelection()) + return KisVLineIteratorPixel(this, m_datamanager, m_selection->m_datamanager, x, y, h, m_x, m_y, writable); + else + return KisVLineIteratorPixel(this, m_datamanager, NULL, x, y, h, m_x, m_y, writable); + +} + +KisRandomAccessorPixel KisPaintDevice::createRandomAccessor(Q_INT32 x, Q_INT32 y, bool writable) { + if(hasSelection()) + return KisRandomAccessorPixel(m_datamanager, m_selection->m_datamanager, x, y, m_x, m_y, writable); + else + return KisRandomAccessorPixel(m_datamanager, NULL, x, y, m_x, m_y, writable); +} + +KisRandomSubAccessorPixel KisPaintDevice::createRandomSubAccessor() +{ + return KisRandomSubAccessorPixel(this); +} + +void KisPaintDevice::emitSelectionChanged() +{ + if (m_parentLayer && m_parentLayer->image()) { + m_parentLayer->image()->slotSelectionChanged(); + } +} + +void KisPaintDevice::emitSelectionChanged(const QRect& r) +{ + if (m_parentLayer && m_parentLayer->image()) { + m_parentLayer->image()->slotSelectionChanged(r); + } +} + +KisSelectionSP KisPaintDevice::selection() +{ + if ( m_selectionDeselected && m_selection ) { + m_selectionDeselected = false; + } + else if (!m_selection) { + m_selection = new KisSelection(this); + Q_CHECK_PTR(m_selection); + m_selection->setX(m_x); + m_selection->setY(m_y); + } + m_hasSelection = true; + + return m_selection; +} + + +bool KisPaintDevice::hasSelection() +{ + return m_hasSelection; +} + +bool KisPaintDevice::selectionDeselected() +{ + return m_selectionDeselected; +} + + +void KisPaintDevice::deselect() +{ + if (m_selection && m_hasSelection) { + m_hasSelection = false; + m_selectionDeselected = true; + } +} + +void KisPaintDevice::reselect() +{ + m_hasSelection = true; + m_selectionDeselected = false; +} + +void KisPaintDevice::addSelection(KisSelectionSP selection) { + + KisPainter painter(this->selection().data()); + QRect r = selection->selectedExactRect(); + painter.bitBlt(r.x(), r.y(), COMPOSITE_OVER, selection.data(), r.x(), r.y(), r.width(), r.height()); + painter.end(); +} + +void KisPaintDevice::subtractSelection(KisSelectionSP selection) { + KisPainter painter(this->selection().data()); + selection->invert(); + + QRect r = selection->selectedExactRect(); + painter.bitBlt(r.x(), r.y(), COMPOSITE_ERASE, selection.data(), r.x(), r.y(), r.width(), r.height()); + + selection->invert(); + painter.end(); +} + +void KisPaintDevice::clearSelection() +{ + if (!hasSelection()) return; + + QRect r = m_selection->selectedExactRect(); + + if (r.isValid()) { + + for (Q_INT32 y = 0; y < r.height(); y++) { + + KisHLineIterator devIt = createHLineIterator(r.x(), r.y() + y, r.width(), true); + KisHLineIterator selectionIt = m_selection->createHLineIterator(r.x(), r.y() + y, r.width(), false); + + while (!devIt.isDone()) { + // XXX: Optimize by using stretches + + m_colorSpace->applyInverseAlphaU8Mask( devIt.rawData(), selectionIt.rawData(), 1); + + ++devIt; + ++selectionIt; + } + } + + if (m_parentLayer) { + m_parentLayer->setDirty(r); + } + } +} + +void KisPaintDevice::applySelectionMask(KisSelectionSP mask) +{ + QRect r = mask->selectedRect(); + crop(r); + + for (Q_INT32 y = r.top(); y <= r.bottom(); ++y) { + + KisHLineIterator pixelIt = createHLineIterator(r.x(), y, r.width(), true); + KisHLineIterator maskIt = mask->createHLineIterator(r.x(), y, r.width(), false); + + while (!pixelIt.isDone()) { + // XXX: Optimize by using stretches + + m_colorSpace->applyAlphaU8Mask( pixelIt.rawData(), maskIt.rawData(), 1); + + ++pixelIt; + ++maskIt; + } + } +} + +KisSelectionSP KisPaintDevice::setSelection( KisSelectionSP selection) +{ + if (selection) { + KisSelectionSP oldSelection = m_selection; + m_selection = selection; + m_hasSelection = true; + return oldSelection; + } + else return 0; +} + +bool KisPaintDevice::pixel(Q_INT32 x, Q_INT32 y, QColor *c, Q_UINT8 *opacity) +{ + KisHLineIteratorPixel iter = createHLineIterator(x, y, 1, false); + + Q_UINT8 *pix = iter.rawData(); + + if (!pix) return false; + + colorSpace()->toQColor(pix, c, opacity); + + return true; +} + + +bool KisPaintDevice::pixel(Q_INT32 x, Q_INT32 y, KisColor * kc) +{ + KisHLineIteratorPixel iter = createHLineIterator(x, y, 1, false); + + Q_UINT8 *pix = iter.rawData(); + + if (!pix) return false; + + kc->setColor(pix, m_colorSpace); + + return true; +} + +KisColor KisPaintDevice::colorAt(Q_INT32 x, Q_INT32 y) +{ + //return KisColor(m_datamanager->pixel(x - m_x, y - m_y), m_colorSpace); + KisHLineIteratorPixel iter = createHLineIterator(x, y, 1, true); + return KisColor(iter.rawData(), m_colorSpace); +} + +bool KisPaintDevice::setPixel(Q_INT32 x, Q_INT32 y, const QColor& c, Q_UINT8 opacity) +{ + KisHLineIteratorPixel iter = createHLineIterator(x, y, 1, true); + + colorSpace()->fromQColor(c, opacity, iter.rawData()); + + return true; +} + +bool KisPaintDevice::setPixel(Q_INT32 x, Q_INT32 y, const KisColor& kc) +{ + Q_UINT8 * pix; + if (kc.colorSpace() != m_colorSpace) { + KisColor kc2 (kc, m_colorSpace); + pix = kc2.data(); + } + else { + pix = kc.data(); + } + + KisHLineIteratorPixel iter = createHLineIterator(x, y, 1, true); + memcpy(iter.rawData(), pix, m_colorSpace->pixelSize()); + + return true; +} + + +Q_INT32 KisPaintDevice::numContiguousColumns(Q_INT32 x, Q_INT32 minY, Q_INT32 maxY) +{ + return m_datamanager->numContiguousColumns(x - m_x, minY - m_y, maxY - m_y); +} + +Q_INT32 KisPaintDevice::numContiguousRows(Q_INT32 y, Q_INT32 minX, Q_INT32 maxX) +{ + return m_datamanager->numContiguousRows(y - m_y, minX - m_x, maxX - m_x); +} + +Q_INT32 KisPaintDevice::rowStride(Q_INT32 x, Q_INT32 y) +{ + return m_datamanager->rowStride(x - m_x, y - m_y); +} + +const Q_UINT8* KisPaintDevice::pixel(Q_INT32 x, Q_INT32 y) +{ + return m_datamanager->pixel(x - m_x, y - m_y); +} + +Q_UINT8* KisPaintDevice::writablePixel(Q_INT32 x, Q_INT32 y) +{ + return m_datamanager->writablePixel(x - m_x, y - m_y); +} + +void KisPaintDevice::setX(Q_INT32 x) +{ + m_x = x; + if(m_selection && m_selection != this) + m_selection->setX(x); +} + +void KisPaintDevice::setY(Q_INT32 y) +{ + m_y = y; + if(m_selection && m_selection != this) + m_selection->setY(y); +} + + +void KisPaintDevice::readBytes(Q_UINT8 * data, Q_INT32 x, Q_INT32 y, Q_INT32 w, Q_INT32 h) +{ + m_datamanager->readBytes(data, x - m_x, y - m_y, w, h); +} + +void KisPaintDevice::writeBytes(const Q_UINT8 * data, Q_INT32 x, Q_INT32 y, Q_INT32 w, Q_INT32 h) +{ + m_datamanager->writeBytes( data, x - m_x, y - m_y, w, h); +} + + +KisDataManagerSP KisPaintDevice::dataManager() const +{ + return m_datamanager; +} + +KisExifInfo* KisPaintDevice::exifInfo() +{ + if(!m_exifInfo) + m_exifInfo = new KisExifInfo(); + return m_exifInfo; +} + +void KisPaintDevice::runBackgroundFilters() +{ + if ( m_lock ) return; + + KisTransaction * cmd = new KisTransaction("Running autofilters", this); + + QRect rc = extent(); + if (!m_longRunningFilters.isEmpty()) { + QValueList<KisFilter*>::iterator it; + QValueList<KisFilter*>::iterator end = m_longRunningFilters.end(); + for (it = m_longRunningFilters.begin(); it != end; ++it) { + (*it)->process(this, this, 0, rc); + } + } + if (cmd && undoAdapter()) undoAdapter()->addCommand(cmd); + + if (m_parentLayer) m_parentLayer->setDirty(rc); +} + +#include "kis_paint_device.moc" diff --git a/krita/core/kis_paint_device.h b/krita/core/kis_paint_device.h new file mode 100644 index 00000000..3b1dcfe7 --- /dev/null +++ b/krita/core/kis_paint_device.h @@ -0,0 +1,596 @@ +/* + * copyright (c) 2002 patrick julien <freak@codepimps.org> + * + * 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_PAINT_DEVICE_IMPL_H_ +#define KIS_PAINT_DEVICE_IMPL_H_ + +#include <qcolor.h> +#include <qobject.h> +#include <qpixmap.h> +#include <qptrlist.h> +#include <qrect.h> +#include <qvaluelist.h> +#include <qstring.h> + +#include "kis_types.h" +#include "kdebug.h" +#include "kis_global.h" +#include "kis_image.h" +#include "kis_colorspace.h" +#include "kis_canvas_controller.h" +#include "kis_color.h" +#include <koffice_export.h> + +class DCOPObject; + +class QImage; +class QSize; +class QPoint; +class QWMatrix; +class QTimer; + +class KNamedCommand; + +class KoStore; + +class KisExifInfo; +class KisHLineIteratorPixel; +class KisImage; +class KisRectIteratorPixel; +class KisVLineIteratorPixel; +class KisRandomAccessorPixel; +class KisRandomSubAccessorPixel; +class KisUndoAdapter; +class KisFilter; +class KisDataManager; +typedef KSharedPtr<KisDataManager> KisDataManagerSP; + +class KisMemento; +typedef KSharedPtr<KisMemento> KisMementoSP; + + +/** + * A paint device contains the actual pixel data and offers methods + * to read and write pixels. A paint device has an integer x,y position + * (i.e., are not positioned on the image with sub-pixel accuracy). + * A KisPaintDevice doesn't have any fixed size, the size change dynamicaly + * when pixels are accessed by an iterator. + */ +class KRITACORE_EXPORT KisPaintDevice + : public QObject + , public KShared +{ + + Q_OBJECT + +public: + + /** + * Create a new paint device with the specified colorspace. + * + * @param colorSpace the colorspace of this paint device + * @param name for debugging purposes + */ + KisPaintDevice(KisColorSpace * colorSpace, const char * name = 0); + + /** + * Create a new paint device with the specified colorspace. The + * parentLayer will be notified of changes to this paint device. + * + * @param parentLayer the layer that contains this paint device. + * @param colorSpace the colorspace of this paint device + * @param name for debugging purposes + */ + KisPaintDevice(KisLayer *parentLayer, KisColorSpace * colorSpace, const char * name = 0); + + KisPaintDevice(const KisPaintDevice& rhs); + virtual ~KisPaintDevice(); + virtual DCOPObject *dcopObject(); + + void lock(bool lock) { m_lock = lock; } + +public: + + /** + * Write the pixels of this paint device into the specified file store. + */ + virtual bool write(KoStore *store); + + /** + * Fill this paint device with the pixels from the specified file store. + */ + virtual bool read(KoStore *store); + +public: + + /** + * Moves the device to these new coordinates (so no incremental move or so) + */ + virtual void move(Q_INT32 x, Q_INT32 y); + + /** + * Convenience method for the above + */ + virtual void move(const QPoint& pt); + + /** + * Move the paint device to the specified location and make it possible to + * undo the move. + */ + virtual KNamedCommand * moveCommand(Q_INT32 x, Q_INT32 y); + + /** + * Returns true of x,y is within the extent of this paint device + */ + bool contains(Q_INT32 x, Q_INT32 y) const; + + /** + * Convenience method for the above + */ + bool contains(const QPoint& pt) const; + + /** + * Retrieve the bounds of the paint device. The size is not exact, + * but may be larger if the underlying datamanager works that way. + * For instance, the tiled datamanager keeps the extent to the nearest + * multiple of 64. + */ + virtual void extent(Q_INT32 &x, Q_INT32 &y, Q_INT32 &w, Q_INT32 &h) const; + virtual QRect extent() const; + + /** + * XXX: This should be a temporay hack, awaiting a proper fix. + * + * Indicates whether the extent really represents the extent. For example, + * the KisBackground checkerboard pattern is generated by filling the + * default tile but it will return an empty extent. + */ + bool extentIsValid() const; + + /// Convience method for the above + void setExtentIsValid(bool isValid); + + /** + * Get the exact bounds of this paint device. This may be very slow, + * especially on larger paint devices because it does a linear scanline search. + */ + virtual void exactBounds(Q_INT32 &x, Q_INT32 &y, Q_INT32 &w, Q_INT32 &h) const; + virtual QRect exactBounds() const; + virtual QRect exactBoundsOldMethod() const; + virtual QRect exactBoundsImprovedOldMethod() const; + + /** + * Cut the paint device down to the specified rect + */ + void crop(Q_INT32 x, Q_INT32 y, Q_INT32 w, Q_INT32 h); + + /// Convience method for the above + void crop(QRect r); + + /** + * Complete erase the current paint device. Its size will become 0. + */ + virtual void clear(); + + /** + * Fill the given rectangle with the given pixel. + */ + void fill(Q_INT32 x, Q_INT32 y, Q_INT32 w, Q_INT32 h, const Q_UINT8 *fillPixel); + + /** + * Read the bytes representing the rectangle described by x, y, w, h into + * data. If data is not big enough, Krita will gladly overwrite the rest + * of your precious memory. + * + * Since this is a copy, you need to make sure you have enough memory. + * + * Reading from areas not previously initialized will read the default + * pixel value into data but not initialize that region. + */ + virtual void readBytes(Q_UINT8 * data, Q_INT32 x, Q_INT32 y, Q_INT32 w, Q_INT32 h); + + /** + * Copy the bytes in data into the rect specified by x, y, w, h. If the + * data is too small or uninitialized, Krita will happily read parts of + * memory you never wanted to be read. + * + * If the data is written to areas of the paint device not previously initialized, + * the paint device will grow. + */ + virtual void writeBytes(const Q_UINT8 * data, Q_INT32 x, Q_INT32 y, Q_INT32 w, Q_INT32 h); + + /** + * Get the number of contiguous columns starting at x, valid for all values + * of y between minY and maxY. + */ + Q_INT32 numContiguousColumns(Q_INT32 x, Q_INT32 minY, Q_INT32 maxY); + + /** + * Get the number of contiguous rows starting at y, valid for all values + * of x between minX and maxX. + */ + Q_INT32 numContiguousRows(Q_INT32 y, Q_INT32 minX, Q_INT32 maxX); + + /** + * Get the row stride at pixel (x, y). This is the number of bytes to add to a + * pointer to pixel (x, y) to access (x, y + 1). + */ + Q_INT32 rowStride(Q_INT32 x, Q_INT32 y); + + /** + * Get a read-only pointer to pixel (x, y). + */ + KDE_DEPRECATED const Q_UINT8* pixel(Q_INT32 x, Q_INT32 y); + + /** + * Get a read-write pointer to pixel (x, y). + */ + KDE_DEPRECATED Q_UINT8* writablePixel(Q_INT32 x, Q_INT32 y); + + /** + * Converts the paint device to a different colorspace + */ + virtual void convertTo(KisColorSpace * dstColorSpace, Q_INT32 renderingIntent = INTENT_PERCEPTUAL); + + /** + * Changes the profile of the colorspace of this paint device to the given + * profile. If the given profile is 0, nothing happens. + */ + virtual void setProfile(KisProfile * profile); + + /** + * Fill this paint device with the data from img; starting at (offsetX, offsetY) + * @param srcProfileName name of the RGB profile to interpret the img as. "" is interpreted as sRGB + */ + virtual void convertFromQImage(const QImage& img, const QString &srcProfileName, Q_INT32 offsetX = 0, Q_INT32 offsetY = 0); + + /** + * Create an RGBA QImage from a rectangle in the paint device. + * + * @param x Left coordinate of the rectangle + * @param y Top coordinate of the rectangle + * @param w Width of the rectangle in pixels + * @param h Height of the rectangle in pixels + * @param dstProfile RGB profile to use in conversion. May be 0, in which + * case it's up to the colour strategy to choose a profile (most + * like sRGB). + * @param exposure The exposure setting used to render a preview of a high dynamic range image. + */ + virtual QImage convertToQImage(KisProfile * dstProfile, Q_INT32 x, Q_INT32 y, Q_INT32 w, Q_INT32 h, float exposure = 0.0f); + + /** + * Create an RGBA QImage from a rectangle in the paint device. The rectangle is defined by the parent image's bounds. + * + * @param dstProfile RGB profile to use in conversion. May be 0, in which + * case it's up to the colour strategy to choose a profile (most + * like sRGB). + * @param exposure The exposure setting used to render a preview of a high dynamic range image. + */ + virtual QImage convertToQImage(KisProfile * dstProfile, float exposure = 0.0f); + + /** + * Creates a paint device thumbnail of the paint device, retaining the aspect ratio. + * The width and height of the returned device won't exceed \p maxw and \p maxw, but they may be smaller. + */ + + KisPaintDeviceSP createThumbnailDevice(Q_INT32 w, Q_INT32 h); + + /** + * Creates a thumbnail of the paint device, retaining the aspect ratio. + * The width and height of the returned QImage won't exceed \p maxw and \p maxw, but they may be smaller. + * The colors are not corrected for display! + */ + virtual QImage createThumbnail(Q_INT32 maxw, Q_INT32 maxh); + + + /** + * Fill c and opacity with the values found at x and y. + * + * The color values will be transformed from the profile of + * this paint device to the display profile. + * + * @return true if the operation was succesful. + */ + bool pixel(Q_INT32 x, Q_INT32 y, QColor *c, Q_UINT8 *opacity); + + + /** + * Fill kc with the values found at x and y. This method differs + * from the above in using KisColor, which can be of any colorspace + * + * The color values will be transformed from the profile of + * this paint device to the display profile. + * + * @return true if the operation was succesful. + */ + bool pixel(Q_INT32 x, Q_INT32 y, KisColor * kc); + + /** + * Return the KisColor of the pixel at x,y. + */ + KisColor colorAt(Q_INT32 x, Q_INT32 y); + + /** + * Set the specified pixel to the specified color. Note that this + * bypasses KisPainter. the PaintDevice is here used as an equivalent + * to QImage, not QPixmap. This means that this is not undoable; also, + * there is no compositing with an existing value at this location. + * + * The color values will be transformed from the display profile to + * the paint device profile. + * + * Note that this will use 8-bit values and may cause a significant + * degradation when used on 16-bit or hdr quality images. + * + * @return true if the operation was succesful + * + */ + bool setPixel(Q_INT32 x, Q_INT32 y, const QColor& c, Q_UINT8 opacity); + + bool setPixel(Q_INT32 x, Q_INT32 y, const KisColor& kc); + + KisColorSpace * colorSpace() const; + + KisDataManagerSP dataManager() const; + + /** + * Replace the pixel data, color strategy, and profile. + */ + void setData(KisDataManagerSP data, KisColorSpace * colorSpace); + + /** + * The X offset of the paint device + */ + Q_INT32 getX() const; + + /** + * The Y offset of the paint device + */ + Q_INT32 getY() const; + + /** + * Return the X offset of the paint device + */ + void setX(Q_INT32 x); + + /** + * Return the Y offset of the paint device + */ + void setY(Q_INT32 y); + + + /** + * Return the number of bytes a pixel takes. + */ + virtual Q_INT32 pixelSize() const; + + /** + * Return the number of channels a pixel takes + */ + virtual Q_INT32 nChannels() const; + + /** + * Return the image that contains this paint device, or 0 if it is not + * part of an image. This is the same as calling parentLayer()->image(). + */ + KisImage *image() const; + + /** + * Returns the KisLayer that contains this paint device, or 0 if this is not + * part of a layer. + */ + KisLayer *parentLayer() const; + + /** + * Set the KisLayer that contains this paint device, or 0 if this is not + * part of a layer. + */ + void setParentLayer(KisLayer *parentLayer); + + /** + * Add the specified rect top the parent layer (if present) + */ + virtual void setDirty(const QRect & rc); + + /** + * Set the parent layer completely dirty, if this paint device has one. + */ + virtual void setDirty(); + + + /** + * Mirror the device along the X axis + */ + void mirrorX(); + /** + * Mirror the device along the Y axis + */ + void mirrorY(); + + KisMementoSP getMemento(); + void rollback(KisMementoSP memento); + void rollforward(KisMementoSP memento); + + /** + * This function return an iterator which points to the first pixel of an rectangle + */ + KisRectIteratorPixel createRectIterator(Q_INT32 left, Q_INT32 top, Q_INT32 w, Q_INT32 h, bool writable); + + /** + * This function return an iterator which points to the first pixel of a horizontal line + */ + KisHLineIteratorPixel createHLineIterator(Q_INT32 x, Q_INT32 y, Q_INT32 w, bool writable); + + /** + * This function return an iterator which points to the first pixel of a vertical line + */ + KisVLineIteratorPixel createVLineIterator(Q_INT32 x, Q_INT32 y, Q_INT32 h, bool writable); + + /** + * This function creates a random accessor which allow to randomly access any pixels on + * the paint device. + * <b>Note:</b> random access is way slower than iterators, allways use iterators whenever + * you can + */ + KisRandomAccessorPixel createRandomAccessor(Q_INT32 x, Q_INT32 y, bool writable); + + /** + * This function create a random accessor which can easily access to sub pixel values. + */ + KisRandomSubAccessorPixel createRandomSubAccessor(); + + /** Get the current selection or create one if this paintdevice hasn't got a selection yet. */ + KisSelectionSP selection(); + + /** Adds the specified selection to the currently active selection for this paintdevice */ + void addSelection(KisSelectionSP selection); + + /** Subtracts the specified selection from the currently active selection for this paindevice */ + void subtractSelection(KisSelectionSP selection); + + /** Whether there is a valid selection for this paintdevice. */ + bool hasSelection(); + + /** Whether the previous selection was deselected. */ + bool selectionDeselected(); + + /** Deselect the selection for this paintdevice. */ + void deselect(); + + /** Reinstates the old selection */ + void reselect(); + + /** Clear the selected pixels from the paint device */ + void clearSelection(); + + /** + * Apply a mask to the image data, i.e. multiply each pixel's opacity by its + * selectedness in the mask. + */ + void applySelectionMask(KisSelectionSP mask); + + /** + * Sets the selection of this paint device to the new selection, + * returns the old selection, if there was an old selection, + * otherwise 0 + */ + KisSelectionSP setSelection(KisSelectionSP selection); + + /** + * Notify the owning image that the current selection has changed. + */ + void emitSelectionChanged(); + + /** + * Notify the owning image that the current selection has changed. + * + * @param r the area for which the selection has changed + */ + void emitSelectionChanged(const QRect& r); + + + KisUndoAdapter *undoAdapter() const; + + /** + * Return the exifInfo associated with this layer. If no exif infos are + * available, the function will create it. + */ + KisExifInfo* exifInfo(); + /** + * This function return true if the layer has exif info associated with it. + */ + bool hasExifInfo() { return m_exifInfo != 0; } +signals: + void positionChanged(KisPaintDeviceSP device); + void ioProgress(Q_INT8 percentage); + void profileChanged(KisProfile * profile); + +private slots: + + void runBackgroundFilters(); + +private: + KisPaintDevice& operator=(const KisPaintDevice&); + +protected: + KisDataManagerSP m_datamanager; + +private: + /* The KisLayer that contains this paint device, or 0 if this is not + * part of a layer. + */ + KisLayer *m_parentLayer; + + bool m_extentIsValid; + + Q_INT32 m_x; + Q_INT32 m_y; + KisColorSpace * m_colorSpace; + // Cached for quick access + Q_INT32 m_pixelSize; + Q_INT32 m_nChannels; + + // Whether the selection is active + bool m_hasSelection; + bool m_selectionDeselected; + + // Contains the actual selection. For now, there can be only + // one selection per layer. XXX: is this a limitation? + KisSelectionSP m_selection; + + DCOPObject * m_dcop; + + KisExifInfo* m_exifInfo; + + QValueList<KisFilter*> m_longRunningFilters; + QTimer * m_longRunningFilterTimer; + + bool m_lock; +}; + +inline Q_INT32 KisPaintDevice::pixelSize() const +{ + Q_ASSERT(m_pixelSize > 0); + return m_pixelSize; +} + +inline Q_INT32 KisPaintDevice::nChannels() const +{ + Q_ASSERT(m_nChannels > 0); + return m_nChannels; +; +} + +inline KisColorSpace * KisPaintDevice::colorSpace() const +{ + Q_ASSERT(m_colorSpace != 0); + return m_colorSpace; +} + + +inline Q_INT32 KisPaintDevice::getX() const +{ + return m_x; +} + +inline Q_INT32 KisPaintDevice::getY() const +{ + return m_y; +} + +#endif // KIS_PAINT_DEVICE_IMPL_H_ + diff --git a/krita/core/kis_paint_device_action.h b/krita/core/kis_paint_device_action.h new file mode 100644 index 00000000..4dbbc365 --- /dev/null +++ b/krita/core/kis_paint_device_action.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2005 Bart Coppens <kde@bartcoppens.be> + * + * 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_PAINTDEV_ACTION_H_ +#define KIS_PAINTDEV_ACTION_H_ + +#include "kis_paint_device.h" +class QString; + +/** + * Defines an action to do with a paint device. It can be force used by the gui on creation + * of a layer, for example. Or just appear in a list of actions to do. + */ +class KisPaintDeviceAction { +public: + virtual ~KisPaintDeviceAction() {} + /** + * Do something with the paint device. This can be anything, like, for example, popping + * up a dialog to choose a texture. The width and height are added because these may + * be needed in some cases. + */ + virtual void act(KisPaintDeviceSP paintDev, Q_INT32 w = 0, Q_INT32 h = 0) const = 0; + /// The name of the action, to be displayed in the GUI + virtual QString name() const = 0; + /// A description of the action, to be displayed in the GUI + virtual QString description() const = 0; +}; + +#endif // KIS_PAINTDEV_ACTION_H_ diff --git a/krita/core/kis_paint_device_iface.cc b/krita/core/kis_paint_device_iface.cc new file mode 100644 index 00000000..ff4c7034 --- /dev/null +++ b/krita/core/kis_paint_device_iface.cc @@ -0,0 +1,74 @@ +/* + * This file is part of the KDE project + * + * Copyright (C) 2005 Boudewijn Rempt <boud@valdyas.org> + * + * 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 <kapplication.h> + +#include <dcopclient.h> + +#include "kis_paint_device_iface.h" +#include "kis_colorspace_iface.h" +#include "kis_colorspace.h" + +#include "kis_paint_device.h" + +KisPaintDeviceIface::KisPaintDeviceIface( KisPaintDevice * parent ) + : DCOPObject("paintdevice") +{ + m_parent = parent; +} + +Q_INT32 KisPaintDeviceIface::pixelSize() const +{ + return m_parent->pixelSize(); +} + +Q_INT32 KisPaintDeviceIface::nChannels() const +{ + return m_parent->nChannels(); +} + +QByteArray KisPaintDeviceIface::readBytes(Q_INT32 x, Q_INT32 y, Q_INT32 w, Q_INT32 h) +{ + QByteArray b (w * h * m_parent->pixelSize()); + + m_parent->readBytes((Q_UINT8*)b.data(), x, y, w, h); + return b; +} + +void KisPaintDeviceIface::writeBytes(QByteArray bytes, Q_INT32 x, Q_INT32 y, Q_INT32 w, Q_INT32 h) +{ + m_parent->writeBytes((Q_UINT8*)bytes.data(), x, y, w, h); +} + +DCOPRef KisPaintDeviceIface::colorSpace() const +{ + KisColorSpace * cs = m_parent->colorSpace(); + if ( !cs ) + return DCOPRef(); + else + return DCOPRef( kapp->dcopClient()->appId(), + cs->dcopObject()->objId(), + "KisColorSpaceIface" ); +} + +void KisPaintDeviceIface::setColorSpace(DCOPRef) +{ + // XXX: Figure out how to get the correct object from + // the dcopref +} diff --git a/krita/core/kis_paint_device_iface.h b/krita/core/kis_paint_device_iface.h new file mode 100644 index 00000000..d712f410 --- /dev/null +++ b/krita/core/kis_paint_device_iface.h @@ -0,0 +1,85 @@ +/* + * This file is part of the KDE project + * + * Copyright (C) 2005 Boudewijn Rempt <boud@valdyas.org> + * + * 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_PAINT_DEVICE_IFACE_H +#define _KIS_PAINT_DEVICE_IFACE_H + +#include <dcopref.h> +#include <dcopobject.h> + +#include <qstring.h> + +class KisPaintDevice; + +class KisPaintDeviceIface : virtual public DCOPObject +{ + K_DCOP +public: + KisPaintDeviceIface( KisPaintDevice * parent ); +k_dcop: + + /** + * Return the number of bytes a pixel takes. + */ + Q_INT32 pixelSize() const; + + /** + * Return the number of channels a pixel takes + */ + Q_INT32 nChannels() const; + + /** + * Read the bytes representing the rectangle described by x, y, w, h into + * data. If data is not big enough, Krita will gladly overwrite the rest + * of your precious memory. + * + * Since this is a copy, you need to make sure you have enough memory. + * + * Reading from areas not previously initialized will read the default + * pixel value into data. + */ + QByteArray readBytes(Q_INT32 x, Q_INT32 y, Q_INT32 w, Q_INT32 h); + + /** + * Copy the bytes in data into the rect specified by x, y, w, h. If there + * data is too small or uninitialized, Krita will happily read parts of + * memory you never wanted to be read. + * + * If the data is written to areas of the paint device not previously initialized, + * the paint device will grow. + */ + void writeBytes(QByteArray bytes, Q_INT32 x, Q_INT32 y, Q_INT32 w, Q_INT32 h); + + /** + * Get the colorspace of this image + */ + DCOPRef colorSpace() const; + + /** + * Set the colorspace of this image + */ + void setColorSpace(DCOPRef colorSpace); + + +private: + + KisPaintDevice *m_parent; +}; + +#endif diff --git a/krita/core/kis_paint_layer.cc b/krita/core/kis_paint_layer.cc new file mode 100644 index 00000000..2567c75c --- /dev/null +++ b/krita/core/kis_paint_layer.cc @@ -0,0 +1,509 @@ +/* + * Copyright (c) 2005 Casper Boemann <cbr@boemann.dk> + * Copyright (c) 2006 Bart Coppens <kde@bartcoppens.be> + * + * 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. + */ + +#include <kdebug.h> +#include <qimage.h> + +#include "kis_debug_areas.h" +#include "kis_image.h" +#include "kis_paint_layer.h" +#include "kis_selection.h" +#include "kis_painter.h" +#include "kis_undo_adapter.h" +#include "kis_iterators_pixel.h" +#include "kis_paint_device.h" +#include "kis_meta_registry.h" +#include "kis_colorspace_factory_registry.h" +#include "kis_datamanager.h" +#include "kis_undo_adapter.h" + +KisPaintLayer::KisPaintLayer(KisImage *img, const QString& name, Q_UINT8 opacity, KisPaintDeviceSP dev) + : super(img, name, opacity) +{ + Q_ASSERT(img); + Q_ASSERT(dev); + m_paintdev = dev; + m_mask = 0; + m_maskAsSelection = 0; + m_paintdev->setParentLayer(this); + m_renderMask = false; + m_editMask = true; +} + + +KisPaintLayer::KisPaintLayer(KisImage *img, const QString& name, Q_UINT8 opacity) + : super(img, name, opacity) +{ + Q_ASSERT(img); + m_paintdev = new KisPaintDevice(this, img->colorSpace(), name.latin1()); + m_mask = 0; + m_maskAsSelection = 0; + m_renderMask = false; + m_editMask = true; +} + +KisPaintLayer::KisPaintLayer(KisImage *img, const QString& name, Q_UINT8 opacity, KisColorSpace * colorSpace) + : super(img, name, opacity) +{ + Q_ASSERT(img); + Q_ASSERT(colorSpace); + m_paintdev = new KisPaintDevice(this, colorSpace, name.latin1()); + m_mask = 0; + m_maskAsSelection = 0; + m_renderMask = false; + m_editMask = true; +} + +KisPaintLayer::KisPaintLayer(const KisPaintLayer& rhs) : + KisLayer(rhs), KisLayerSupportsIndirectPainting(rhs) +{ + m_paintdev = new KisPaintDevice( *rhs.m_paintdev.data() ); + m_paintdev->setParentLayer(this); + if (rhs.hasMask()) { + m_mask = new KisPaintDevice(*rhs.m_mask.data()); + m_mask->setParentLayer(this); + } + m_renderMask = rhs.m_renderMask; + m_editMask = rhs.m_editMask; +} + +KisLayerSP KisPaintLayer::clone() const +{ + return new KisPaintLayer(*this); +} + +KisPaintLayer::~KisPaintLayer() +{ + if (m_paintdev != 0) { + m_paintdev->setParentLayer(0); + } + if (m_mask != 0) { + m_mask->setParentLayer(0); + } +} + +void KisPaintLayer::paintSelection(QImage &img, Q_INT32 x, Q_INT32 y, Q_INT32 w, Q_INT32 h) +{ + if (m_paintdev && m_paintdev->hasSelection()) { + m_paintdev->selection()->paintSelection(img, x, y, w, h); + } else if (m_mask && m_editMask && m_mask->hasSelection()) { + m_mask->selection()->paintSelection(img, x, y, w, h); + } +} + +void KisPaintLayer::paintSelection(QImage &img, const QRect& scaledImageRect, const QSize& scaledImageSize, const QSize& imageSize) +{ + if (m_paintdev && m_paintdev->hasSelection()) { + m_paintdev->selection()->paintSelection(img, scaledImageRect, scaledImageSize, imageSize); + } else if (m_mask && m_editMask && m_mask->hasSelection()) { + m_mask->selection()->paintSelection(img, scaledImageRect, scaledImageSize, imageSize); + } +} + +void KisPaintLayer::paintMaskInactiveLayers(QImage &img, Q_INT32 x, Q_INT32 y, Q_INT32 w, Q_INT32 h) +{ + uchar *j = img.bits(); + + KisColorSpace *cs = m_paintdev->colorSpace(); + + for (Q_INT32 y2 = y; y2 < h + y; ++y2) { + KisHLineIteratorPixel it = m_paintdev->createHLineIterator(x, y2, w, false); + while ( ! it.isDone()) { + Q_UINT8 s = cs->getAlpha(it.rawData()); + if(s==0) + { + Q_UINT8 g = (*(j + 0) + *(j + 1 ) + *(j + 2 )) / 9; + + *(j+0) = 128+g ; + *(j+1) = 165+g; + *(j+2) = 128+g; + } + j+=4; + ++it; + } + } +} + +QImage KisPaintLayer::createThumbnail(Q_INT32 w, Q_INT32 h) +{ + if (m_paintdev) + return m_paintdev->createThumbnail(w, h); + else + return QImage(); +} + + +Q_INT32 KisPaintLayer::x() const { + if (m_paintdev) + return m_paintdev->getX(); + else return 0; +} + +void KisPaintLayer::setX(Q_INT32 x) +{ + if (m_paintdev) + m_paintdev->setX(x); +} + +Q_INT32 KisPaintLayer::y() const { + if (m_paintdev) + return m_paintdev->getY(); + else + return 0; +} + +void KisPaintLayer::setY(Q_INT32 y) { + if (m_paintdev) + m_paintdev->setY(y); +} + +QRect KisPaintLayer::extent() const { + if (m_paintdev) + return m_paintdev->extent(); + else + return QRect(); +} + +QRect KisPaintLayer::exactBounds() const { + if (m_paintdev) + return m_paintdev->exactBounds(); + else + return QRect(); +} + +void KisPaintLayer::removeMask() { + if (!hasMask()) + return; + + m_mask->setParentLayer(0); + m_mask = 0; + m_maskAsSelection = 0; + setDirty(); + + emit sigMaskInfoChanged(); +} + +// ### XXX Do we apply the mask outside the image boundaries too? I'd say no, but I'm not sure +void KisPaintLayer::applyMask() { + if (!hasMask()) + return; + + int x, y, w, h; + m_paintdev->extent(x, y, w, h); + + // A bit slow; but it works + KisPaintDeviceSP temp = new KisPaintDevice(m_paintdev->colorSpace()); + KisPainter gc(temp); + gc.bltSelection(x, y, COMPOSITE_OVER, m_paintdev, m_maskAsSelection, OPACITY_OPAQUE, x, y, w, h); + gc.end(); + gc.begin(m_paintdev); + gc.bitBlt(x, y, COMPOSITE_COPY, temp, OPACITY_OPAQUE, x, y, w, h); + gc.end(); + + removeMask(); +} + +KisPaintDeviceSP KisPaintLayer::createMask() { + if (hasMask()) + return m_mask; + + kdDebug() << k_funcinfo << endl; + // Grey8 nicely fits our needs of being intuitively comparable to other apps' + // mask layer interfaces. It does have an alpha component though, which is a bit + // less appropriate in this context. + m_mask = new KisPaintDevice(KisMetaRegistry::instance()->csRegistry() + ->getColorSpace(KisID("GRAYA"), 0)); + + genericMaskCreationHelper(); + + return m_mask; +} + +// FIXME If from is a paint device is not grey8!! +void KisPaintLayer::createMaskFromPaintDevice(KisPaintDeviceSP from) { + if (hasMask()) + return; // Or overwrite? XXX + + kdDebug() << k_funcinfo << endl; + m_mask = from; // KisPaintDevice(*from); XXX + + genericMaskCreationHelper(); +} + +void KisPaintLayer::createMaskFromSelection(KisSelectionSP from) { + kdDebug() << k_funcinfo << endl; + m_mask = new KisPaintDevice(KisMetaRegistry::instance()->csRegistry() + ->getColorSpace(KisID("GRAYA"), 0)); + m_mask->setParentLayer(this); + + m_maskAsSelection = new KisSelection(); // Anonymous selection is good enough + + // Default pixel is opaque white == don't mask? + Q_UINT8 const defPixel[] = { 255, 255 }; + m_mask->dataManager()->setDefaultPixel(defPixel); + + if (from) { + QRect r(extent()); + + int w = r.width(); + int h = r.height(); + for (int y = r.y(); y < h; y++) { + KisHLineIteratorPixel srcIt = from->createHLineIterator(r.x(), y, w, false); + KisHLineIteratorPixel dstIt = m_mask->createHLineIterator(r.x(), y, w, true); + + while(!dstIt.isDone()) { + // XXX same remark as in convertMaskToSelection + *dstIt.rawData() = *srcIt.rawData(); + ++srcIt; + ++dstIt; + } + } + } + + convertMaskToSelection(extent()); + m_paintdev->deselect(); + + setDirty(); + emit sigMaskInfoChanged(); +} + +KisPaintDeviceSP KisPaintLayer::getMask() { + createMask(); + kdDebug() << k_funcinfo << endl; + return m_mask; +} + +KisSelectionSP KisPaintLayer::getMaskAsSelection() { + createMask(); + kdDebug() << k_funcinfo << endl; + return m_maskAsSelection; +} + +void KisPaintLayer::setEditMask(bool b) { + m_editMask = b; + emit sigMaskInfoChanged(); +} + +void KisPaintLayer::setRenderMask(bool b) { + m_renderMask = b; + + if (hasMask()) + setDirty(); + + emit sigMaskInfoChanged(); +} + +void KisPaintLayer::convertMaskToSelection(const QRect& r) { + KisRectIteratorPixel srcIt = m_mask->createRectIterator(r.x(), r.y(), + r.width(), r.height(), false); + KisRectIteratorPixel dstIt = m_maskAsSelection->createRectIterator(r.x(), r.y(), + r.width(), r.height(), true); + + while(!dstIt.isDone()) { + // src is grey8 (grey + alpha), dst is alpha8. We convert the grey value to + // alpha8 manually and ignore the alpha (that's why we don't convert using default + // functions, and interpret the data raw!) [ XXX ] + *dstIt.rawData() = *srcIt.rawData(); + ++srcIt; + ++dstIt; + } +} + +void KisPaintLayer::genericMaskCreationHelper() { + m_mask->setParentLayer(this); + + m_maskAsSelection = new KisSelection(); // Anonymous selection is good enough + + // Default pixel is opaque white == don't mask? + Q_UINT8 const defPixel[] = { 255, 255 }; + m_mask->dataManager()->setDefaultPixel(defPixel); + + setDirty(); + emit sigMaskInfoChanged(); +} + +void KisPaintLayer::setDirty(bool propagate) { + if (hasMask()) + convertMaskToSelection(extent()); + super::setDirty(propagate); +} + +void KisPaintLayer::setDirty(const QRect & rect, bool propagate) { + if (hasMask()) + convertMaskToSelection(rect); + super::setDirty(rect, propagate); +} + +// Undoable versions code +namespace { + class KisCreateMaskCommand : public KNamedCommand { + typedef KNamedCommand super; + KisPaintLayerSP m_layer; + KisPaintDeviceSP m_mask; + public: + KisCreateMaskCommand(const QString& name, KisPaintLayer* layer) + : super(name), m_layer(layer) {} + virtual void execute() { + kdDebug() << k_funcinfo << endl; + if (!m_mask) + m_mask = m_layer->createMask(); + else + m_layer->createMaskFromPaintDevice(m_mask); + } + virtual void unexecute() { + m_layer->removeMask(); + } + }; + + class KisMaskFromSelectionCommand : public KNamedCommand { + typedef KNamedCommand super; + KisPaintLayerSP m_layer; + KisPaintDeviceSP m_maskBefore; + KisPaintDeviceSP m_maskAfter; + KisSelectionSP m_selection; + public: + KisMaskFromSelectionCommand(const QString& name, KisPaintLayer* layer) + : super(name), m_layer(layer) { + if (m_layer->hasMask()) + m_maskBefore = m_layer->getMask(); + else + m_maskBefore = 0; + m_maskAfter = 0; + if (m_layer->paintDevice()->hasSelection()) + m_selection = m_layer->paintDevice()->selection(); + else + m_selection = 0; + } + virtual void execute() { + if (!m_maskAfter) { + m_layer->createMaskFromSelection(m_selection); + m_maskAfter = m_layer->getMask(); + } else { + m_layer->paintDevice()->deselect(); + m_layer->createMaskFromPaintDevice(m_maskAfter); + } + } + virtual void unexecute() { + m_layer->paintDevice()->setSelection(m_selection); + if (m_maskBefore) + m_layer->createMaskFromPaintDevice(m_maskBefore); + else + m_layer->removeMask(); + } + }; + + class KisMaskToSelectionCommand : public KNamedCommand { + typedef KNamedCommand super; + KisPaintLayerSP m_layer; + KisPaintDeviceSP m_mask; + KisSelectionSP m_selection; + public: + KisMaskToSelectionCommand(const QString& name, KisPaintLayer* layer) + : super(name), m_layer(layer) { + m_mask = m_layer->getMask(); + if (m_layer->paintDevice()->hasSelection()) + m_selection = m_layer->paintDevice()->selection(); + else + m_selection = 0; + } + virtual void execute() { + m_layer->paintDevice()->setSelection(m_layer->getMaskAsSelection()); + m_layer->removeMask(); + } + virtual void unexecute() { + if (m_selection) + m_layer->paintDevice()->setSelection(m_selection); + else + m_layer->paintDevice()->deselect(); + m_layer->createMaskFromPaintDevice(m_mask); + } + }; + + class KisRemoveMaskCommand : public KNamedCommand { + typedef KNamedCommand super; + KisPaintLayerSP m_layer; + KisPaintDeviceSP m_mask; + public: + KisRemoveMaskCommand(const QString& name, KisPaintLayer* layer) + : super(name), m_layer(layer) { + m_mask = m_layer->getMask(); + } + virtual void execute() { + kdDebug() << k_funcinfo << endl; + m_layer->removeMask(); + } + virtual void unexecute() { + // I hope that if the undo stack unwinds, it will end up here in the right + // state again; taking a deep-copy sounds like wasteful to me + m_layer->createMaskFromPaintDevice(m_mask); + } + }; + + class KisApplyMaskCommand : public KNamedCommand { + typedef KNamedCommand super; + KisPaintLayerSP m_layer; + KisPaintDeviceSP m_mask; + KisPaintDeviceSP m_original; + public: + KisApplyMaskCommand(const QString& name, KisPaintLayer* layer) + : super(name), m_layer(layer) { + m_mask = m_layer->getMask(); + m_original = new KisPaintDevice(*m_layer->paintDevice()); + } + virtual void execute() { + m_layer->applyMask(); + } + virtual void unexecute() { + // I hope that if the undo stack unwinds, it will end up here in the right + // state again; taking a deep-copy sounds like wasteful to me + KisPainter gc(m_layer->paintDevice()); + int x, y, w, h; + m_layer->paintDevice()->extent(x, y, w, h); + + gc.bitBlt(x, y, COMPOSITE_COPY, m_original, OPACITY_OPAQUE, x, y, w, h); + gc.end(); + + m_layer->createMaskFromPaintDevice(m_mask); + } + }; +} + +KNamedCommand* KisPaintLayer::createMaskCommand() { + return new KisCreateMaskCommand(i18n("Create Layer Mask"), this); +} + +KNamedCommand* KisPaintLayer::maskFromSelectionCommand() { + return new KisMaskFromSelectionCommand(i18n("Mask From Selection"), this); +} + +KNamedCommand* KisPaintLayer::maskToSelectionCommand() { + return new KisMaskToSelectionCommand(i18n("Mask to Selection"), this); +} + + +KNamedCommand* KisPaintLayer::removeMaskCommand() { + return new KisRemoveMaskCommand(i18n("Remove Layer Mask"), this); +} + +KNamedCommand* KisPaintLayer::applyMaskCommand() { + return new KisApplyMaskCommand(i18n("Apply Layer Mask"), this); +} + + +#include "kis_paint_layer.moc" diff --git a/krita/core/kis_paint_layer.h b/krita/core/kis_paint_layer.h new file mode 100644 index 00000000..2c4b562f --- /dev/null +++ b/krita/core/kis_paint_layer.h @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2005 Casper Boemann <cbr@boemann.dk> + * + * 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_PAINT_LAYER_H_ +#define KIS_PAINT_LAYER_H_ + +#include "kis_types.h" +#include "kis_layer.h" +#include "kis_paint_device.h" +#include "kis_colorspace.h" +/** + * This layer is of a type that can be painted on. + */ +class KisPaintLayer : public KisLayer, public KisLayerSupportsIndirectPainting { + typedef KisLayer super; + + Q_OBJECT + +public: + KisPaintLayer(KisImage *img, const QString& name, Q_UINT8 opacity, KisPaintDeviceSP dev); + KisPaintLayer(KisImage *img, const QString& name, Q_UINT8 opacity); + KisPaintLayer(KisImage *img, const QString& name, Q_UINT8 opacity, KisColorSpace * colorSpace); + KisPaintLayer(const KisPaintLayer& rhs); + virtual ~KisPaintLayer(); + + virtual KisLayerSP clone() const; +public: + + // Called when the layer is made active + virtual void activate() {} + + // Called when another layer is made active + virtual void deactivate() {} + + virtual Q_INT32 x() const; + virtual void setX(Q_INT32 x); + + virtual Q_INT32 y() const; + virtual void setY(Q_INT32 y); + + virtual QRect extent() const; + virtual QRect exactBounds() const; + + virtual void paintSelection(QImage &img, Q_INT32 x, Q_INT32 y, Q_INT32 w, Q_INT32 h); + virtual void paintSelection(QImage &img, const QRect& scaledImageRect, const QSize& scaledImageSize, const QSize& imageSize); + + virtual void paintMaskInactiveLayers(QImage &img, Q_INT32 x, Q_INT32 y, Q_INT32 w, Q_INT32 h); + + virtual QImage createThumbnail(Q_INT32 w, Q_INT32 h); + + virtual bool accept(KisLayerVisitor &v) + { +// kdDebug(41001) << "\tPAINT\t" << name() +// << " dirty: " << dirty() << "\n"; + return v.visit(this); + } + + + inline KisPaintDeviceSP paintDevice() const { return m_paintdev; } + + /// Returns the paintDevice that accompanies this layer (or mask, see editMask) + inline KisPaintDeviceSP paintDeviceOrMask() const { + if (hasMask() && editMask()) + return m_mask; + return m_paintdev; + } + + // Mask Layer + + /// Does this layer have a layer mask? + bool hasMask() const { return m_mask != 0; } + // XXX TODO: Make these undo-able! + /// Create a mask if it does not yet exist, and return it + KisPaintDeviceSP createMask(); + /// Convert the from argument to the mask + void createMaskFromPaintDevice(KisPaintDeviceSP from); + /** + * Convert the from selection to a paint device (should convert the getMaskAsSelection + * result back to the mask). Overwrites the current mask, if any. Also removes the selection + */ + void createMaskFromSelection(KisSelectionSP from); + /// Remove the layer mask + void removeMask(); + /// Apply the layer mask to the paint device, this removes the mask afterwards + void applyMask(); + /// Returns the layer mask's device. Creates one if there is currently none + KisPaintDeviceSP getMask(); + /// Returns the layer mask's device, converted to a selection. Creates one if there is currently none + KisSelectionSP getMaskAsSelection(); + + /// Undoable version of createMask + KNamedCommand* createMaskCommand(); + /// Undoable version of createMaskFromSelection + KNamedCommand* maskFromSelectionCommand(); + /// Undoable, removes the current mask, but converts it to the current selection + KNamedCommand* maskToSelectionCommand(); + /// Undoable version of removeMask + KNamedCommand* removeMaskCommand(); + /// Undoable version of applyMask + KNamedCommand* applyMaskCommand(); + + /// Returns true if the masked part of the mask will be rendered instead of being transparent + bool renderMask() const { return m_renderMask; } + /// Set the renderMask property + void setRenderMask(bool b); + + /** + * When this returns true, the KisPaintDevice returned in paintDevice will actually + * be the layer mask (if there is one). This is so that tools can draw on the mask + * without needing to know its existance. + */ + bool editMask() const { return m_editMask; } + /// Sets the editMask property + void setEditMask(bool b); + + /// Overridden to call the private convertMaskToSelection + virtual void setDirty(bool propagate = true); + /// Same as above + virtual void setDirty(const QRect & rect, bool propagate = true); + + // KisLayerSupportsIndirectPainting + virtual KisLayer* layer() { return this; } +signals: + /// When the mask is created/destroyed or the editmask or rendermask is changed + void sigMaskInfoChanged(); + +private: + void convertMaskToSelection(const QRect& r); + void genericMaskCreationHelper(); + KisPaintDeviceSP m_paintdev; + // Layer mask related: + // XXX It would be nice to merge the next 2 devices... + KisPaintDeviceSP m_mask; // The mask that we can edit and display easily + KisSelectionSP m_maskAsSelection; // The mask as selection, to apply and render easily + bool m_renderMask; + bool m_editMask; +}; + +typedef KSharedPtr<KisPaintLayer> KisPaintLayerSP; + +#endif // KIS_PAINT_LAYER_H_ + diff --git a/krita/core/kis_painter.cc b/krita/core/kis_painter.cc new file mode 100644 index 00000000..8086696f --- /dev/null +++ b/krita/core/kis_painter.cc @@ -0,0 +1,928 @@ +/* + * Copyright (c) 2002 Patrick Julien <freak@codepimps.org> + * Copyright (c) 2004 Boudewijn Rempt <boud@valdyas.org> + * Copyright (c) 2004 Clarence Dang <dang@kde.org> + * Copyright (c) 2004 Adrian Page <adrian@pagenet.plus.com> + * Copyright (c) 2004 Cyrille Berger <cberger@cberger.net> + * + * 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 <stdlib.h> +#include <string.h> +#include <cfloat> +#include <cmath> +#include <climits> +#include <strings.h> + +#include "qbrush.h" +#include "qfontinfo.h" +#include "qfontmetrics.h" +#include "qpen.h" +#include "qregion.h" +#include "qwmatrix.h" +#include <qimage.h> +#include <qmap.h> +#include <qpainter.h> +#include <qpixmap.h> +#include <qpointarray.h> +#include <qrect.h> +#include <qstring.h> + +#include <kdebug.h> +#include <kcommand.h> +#include <klocale.h> + +#include "kis_brush.h" +#include "kis_debug_areas.h" +#include "kis_image.h" +#include "kis_layer.h" +#include "kis_paint_device.h" +#include "kis_painter.h" +#include "kis_pattern.h" +#include "kis_rect.h" +#include "kis_colorspace.h" +#include "kis_transaction.h" +#include "kis_types.h" +#include "kis_vec.h" +#include "kis_iterators_pixel.h" +#include "kis_paintop.h" +#include "kis_selection.h" +#include "kis_fill_painter.h" +#include "kis_color.h" + +// Maximum distance from a Bezier control point to the line through the start +// and end points for the curve to be considered flat. +#define BEZIER_FLATNESS_THRESHOLD 0.5 + +KisPainter::KisPainter() +{ + init(); +} + +KisPainter::KisPainter(KisPaintDeviceSP device) +{ + init(); + Q_ASSERT(device); + begin(device); +} + +void KisPainter::init() +{ + m_transaction = 0; + m_paintOp = 0; + m_filter = 0; + m_brush = 0; + m_pattern= 0; + m_opacity = OPACITY_OPAQUE; + m_compositeOp = COMPOSITE_OVER; + m_dab = 0; + m_fillStyle = FillStyleNone; + m_strokeStyle = StrokeStyleBrush; + m_pressure = PRESSURE_MIN; + m_duplicateHealing = false; + m_duplicateHealingRadius = 10; + m_duplicatePerspectiveCorrection = false; + m_varyBrushSpacingWithPressureWhenDrawingALine = true; +} + +KisPainter::~KisPainter() +{ + m_brush = 0; + delete m_paintOp; + end(); +} + +void KisPainter::begin(KisPaintDeviceSP device) +{ + if (!device) return; + + if (m_transaction) + delete m_transaction; + + m_device = device; + m_colorSpace = device->colorSpace(); + m_pixelSize = device->pixelSize(); +} + +KCommand *KisPainter::end() +{ + return endTransaction(); +} + +void KisPainter::beginTransaction(const QString& customName) +{ + if (m_transaction) + delete m_transaction; + m_transaction = new KisTransaction(customName, m_device); + Q_CHECK_PTR(m_transaction); +} + +void KisPainter::beginTransaction( KisTransaction* command) +{ + if (m_transaction) + delete m_transaction; + m_transaction = command; +} + + +KCommand *KisPainter::endTransaction() +{ + KCommand *command = m_transaction; + m_transaction = 0; + return command; +} + + +QRect KisPainter::dirtyRect() { + QRect r = m_dirtyRect; + m_dirtyRect = QRect(); + return r; +} + +void KisPainter::bitBlt(Q_INT32 dx, Q_INT32 dy, + const KisCompositeOp& op, + KisPaintDeviceSP srcdev, + Q_UINT8 opacity, + Q_INT32 sx, Q_INT32 sy, + Q_INT32 sw, Q_INT32 sh) +{ + if (srcdev == 0) { + return; + } + + QRect srcRect = QRect(sx, sy, sw, sh); + + if (srcdev->extentIsValid() && op != COMPOSITE_COPY) { + srcRect &= srcdev->extent(); + } + + if (srcRect.isEmpty()) { + return; + } + + dx += srcRect.x() - sx; + dy += srcRect.y() - sy; + + sx = srcRect.x(); + sy = srcRect.y(); + sw = srcRect.width(); + sh = srcRect.height(); + + addDirtyRect(QRect(dx, dy, sw, sh)); + + KisColorSpace * srcCs = srcdev->colorSpace(); + + Q_INT32 dstY = dy; + Q_INT32 srcY = sy; + Q_INT32 rowsRemaining = sh; + + while (rowsRemaining > 0) { + + Q_INT32 dstX = dx; + Q_INT32 srcX = sx; + Q_INT32 columnsRemaining = sw; + Q_INT32 numContiguousDstRows = m_device->numContiguousRows(dstY, dstX, dstX + sw - 1); + Q_INT32 numContiguousSrcRows = srcdev->numContiguousRows(srcY, srcX, srcX + sw - 1); + + Q_INT32 rows = QMIN(numContiguousDstRows, numContiguousSrcRows); + rows = QMIN(rows, rowsRemaining); + + while (columnsRemaining > 0) { + + Q_INT32 numContiguousDstColumns = m_device->numContiguousColumns(dstX, dstY, dstY + rows - 1); + Q_INT32 numContiguousSrcColumns = srcdev->numContiguousColumns(srcX, srcY, srcY + rows - 1); + + Q_INT32 columns = QMIN(numContiguousDstColumns, numContiguousSrcColumns); + columns = QMIN(columns, columnsRemaining); + + Q_INT32 srcRowStride = srcdev->rowStride(srcX, srcY); + //const Q_UINT8 *srcData = srcdev->pixel(srcX, srcY); + KisHLineIteratorPixel srcIt = srcdev->createHLineIterator(srcX, srcY, columns, false); + const Q_UINT8 *srcData = srcIt.rawData(); + + //Q_UINT8 *dstData = m_device->writablePixel(dstX, dstY); + Q_INT32 dstRowStride = m_device->rowStride(dstX, dstY); + KisHLineIteratorPixel dstIt = m_device->createHLineIterator(dstX, dstY, columns, true); + Q_UINT8 *dstData = dstIt.rawData(); + + + m_colorSpace->bitBlt(dstData, + dstRowStride, + srcCs, + srcData, + srcRowStride, + 0, + 0, + opacity, + rows, + columns, + op); + + srcX += columns; + dstX += columns; + columnsRemaining -= columns; + } + + srcY += rows; + dstY += rows; + rowsRemaining -= rows; + } +} + +void KisPainter::bltSelection(Q_INT32 dx, Q_INT32 dy, + const KisCompositeOp &op, + KisPaintDeviceSP srcdev, + KisSelectionSP seldev, + Q_UINT8 opacity, + Q_INT32 sx, Q_INT32 sy, + Q_INT32 sw, Q_INT32 sh) +{ + // Better use a probablistic method than a too slow one + if (seldev->isProbablyTotallyUnselected(QRect(dx, dy, sw, sh))) { +/* + kdDebug() << "Blitting outside selection rect\n"; + + kdDebug() << "srcdev: " << srcdev << " (" << srcdev->name() << ")" + << ", seldev: " << seldev << " (" << seldev->name() << ")" + << ". dx, dy " << dx << "," << dy + << ". sx, sy : sw, sy " << sx << "," << sy << " : " << sw << "," << sh << endl; +*/ + return; + } + bltMask(dx,dy,op,srcdev,seldev.data(),opacity,sx,sy,sw,sh); +} + +void KisPainter::bltMask(Q_INT32 dx, Q_INT32 dy, + const KisCompositeOp &op, + KisPaintDeviceSP srcdev, + KisPaintDeviceSP seldev, + Q_UINT8 opacity, + Q_INT32 sx, Q_INT32 sy, + Q_INT32 sw, Q_INT32 sh) + +{ + if (srcdev == 0) return; + + if (seldev == 0) return; + + if (m_device == 0) return; + + + QRect srcRect = QRect(sx, sy, sw, sh); + + if (srcdev->extentIsValid() && op != COMPOSITE_COPY) { + srcRect &= srcdev->extent(); + } + + if (srcRect.isEmpty()) { + return; + } + + dx += srcRect.x() - sx; + dy += srcRect.y() - sy; + + sx = srcRect.x(); + sy = srcRect.y(); + sw = srcRect.width(); + sh = srcRect.height(); + + addDirtyRect(QRect(dx, dy, sw, sh)); + + KisColorSpace * srcCs = srcdev->colorSpace(); + + Q_INT32 dstY = dy; + Q_INT32 srcY = sy; + Q_INT32 rowsRemaining = sh; + + while (rowsRemaining > 0) { + + Q_INT32 dstX = dx; + Q_INT32 srcX = sx; + Q_INT32 columnsRemaining = sw; + Q_INT32 numContiguousDstRows = m_device->numContiguousRows(dstY, dstX, dstX + sw - 1); + Q_INT32 numContiguousSrcRows = srcdev->numContiguousRows(srcY, srcX, srcX + sw - 1); + Q_INT32 numContiguousSelRows = seldev->numContiguousRows(dstY, dstX, dstX + sw - 1); + + Q_INT32 rows = QMIN(numContiguousDstRows, numContiguousSrcRows); + rows = QMIN(numContiguousSelRows, rows); + rows = QMIN(rows, rowsRemaining); + + while (columnsRemaining > 0) { + + Q_INT32 numContiguousDstColumns = m_device->numContiguousColumns(dstX, dstY, dstY + rows - 1); + Q_INT32 numContiguousSrcColumns = srcdev->numContiguousColumns(srcX, srcY, srcY + rows - 1); + Q_INT32 numContiguousSelColumns = seldev->numContiguousColumns(dstX, dstY, dstY + rows - 1); + + Q_INT32 columns = QMIN(numContiguousDstColumns, numContiguousSrcColumns); + columns = QMIN(numContiguousSelColumns, columns); + columns = QMIN(columns, columnsRemaining); + + //Q_UINT8 *dstData = m_device->writablePixel(dstX, dstY); + Q_INT32 dstRowStride = m_device->rowStride(dstX, dstY); + KisHLineIteratorPixel dstIt = m_device->createHLineIterator(dstX, dstY, columns, true); + Q_UINT8 *dstData = dstIt.rawData(); + + //const Q_UINT8 *srcData = srcdev->pixel(srcX, srcY); + Q_INT32 srcRowStride = srcdev->rowStride(srcX, srcY); + KisHLineIteratorPixel srcIt = srcdev->createHLineIterator(srcX, srcY, columns, false); + const Q_UINT8 *srcData = srcIt.rawData(); + + //const Q_UINT8 *selData = seldev->pixel(dstX, dstY); + Q_INT32 selRowStride = seldev->rowStride(dstX, dstY); + KisHLineIteratorPixel selIt = seldev->createHLineIterator(dstX, dstY, columns, false); + const Q_UINT8 *selData = selIt.rawData(); + + m_colorSpace->bitBlt(dstData, + dstRowStride, + srcCs, + srcData, + srcRowStride, + selData, + selRowStride, + opacity, + rows, + columns, + op); + + srcX += columns; + dstX += columns; + columnsRemaining -= columns; + } + + srcY += rows; + dstY += rows; + rowsRemaining -= rows; + } +} + + +void KisPainter::bltSelection(Q_INT32 dx, Q_INT32 dy, + const KisCompositeOp& op, + KisPaintDeviceSP srcdev, + Q_UINT8 opacity, + Q_INT32 sx, Q_INT32 sy, + Q_INT32 sw, Q_INT32 sh) +{ + if (m_device == 0) return; + if (!m_device->hasSelection()) { + bitBlt(dx, dy, op, srcdev, opacity, sx, sy, sw, sh); + } + else + bltSelection(dx,dy,op,srcdev, m_device->selection(),opacity,sx,sy,sw,sh); +} + +double KisPainter::paintLine(const KisPoint & pos1, + const double pressure1, + const double xTilt1, + const double yTilt1, + const KisPoint & pos2, + const double pressure2, + const double xTilt2, + const double yTilt2, + const double inSavedDist) +{ + if (!m_device) return 0; + if (!m_paintOp) return 0; + if (!m_brush) return 0; + + double savedDist = inSavedDist; + KisVector2D end(pos2); + KisVector2D start(pos1); + + KisVector2D dragVec = end - start; + KisVector2D movement = dragVec; + + if (savedDist < 0) { + m_paintOp->paintAt(pos1, KisPaintInformation(pressure1, xTilt1, yTilt1, movement)); + savedDist = 0; + } + + double xSpacing = 0; + double ySpacing = 0; + + if ( m_varyBrushSpacingWithPressureWhenDrawingALine ) { + // XXX: The spacing should vary as the pressure changes along the + // line. + // This is a quick simplification. + xSpacing = m_brush->xSpacing((pressure1 + pressure2) / 2); + ySpacing = m_brush->ySpacing((pressure1 + pressure2) / 2); + } + else { + xSpacing = m_brush->xSpacing( PRESSURE_DEFAULT ); + ySpacing = m_brush->ySpacing( PRESSURE_DEFAULT ); + } + + if (xSpacing < 0.5) { + xSpacing = 0.5; + } + if (ySpacing < 0.5) { + ySpacing = 0.5; + } + + double xScale = 1; + double yScale = 1; + double spacing; + // Scale x or y so that we effectively have a square brush + // and calculate distance in that coordinate space. We reverse this scaling + // before drawing the brush. This produces the correct spacing in both + // x and y directions, even if the brush's aspect ratio is not 1:1. + if (xSpacing > ySpacing) { + yScale = xSpacing / ySpacing; + spacing = xSpacing; + } + else { + xScale = ySpacing / xSpacing; + spacing = ySpacing; + } + + dragVec.setX(dragVec.x() * xScale); + dragVec.setY(dragVec.y() * yScale); + + double newDist = dragVec.length(); + double dist = savedDist + newDist; + double l_savedDist = savedDist; + + if (dist < spacing) { + return dist; + } + + dragVec.normalize(); + KisVector2D step(0, 0); + + while (dist >= spacing) { + if (l_savedDist > 0) { + step += dragVec * (spacing - l_savedDist); + l_savedDist -= spacing; + } + else { + step += dragVec * spacing; + } + + KisPoint p(start.x() + (step.x() / xScale), start.y() + (step.y() / yScale)); + + double distanceMoved = step.length(); + double t = 0; + + if (newDist > DBL_EPSILON) { + t = distanceMoved / newDist; + } + + double pressure = (1 - t) * pressure1 + t * pressure2; + double xTilt = (1 - t) * xTilt1 + t * xTilt2; + double yTilt = (1 - t) * yTilt1 + t * yTilt2; + + m_paintOp->paintAt(p, KisPaintInformation(pressure, xTilt, yTilt, movement)); + dist -= spacing; + } + + if (dist > 0) + return dist; + else + return 0; +} + +void KisPainter::paintPolyline (const vKisPoint &points, + int index, int numPoints) +{ + if (index >= (int) points.count ()) + return; + + if (numPoints < 0) + numPoints = points.count (); + + if (index + numPoints > (int) points.count ()) + numPoints = points.count () - index; + + + for (int i = index; i < index + numPoints - 1; i++) + { + paintLine (points [index], 0/*pressure*/, 0, 0, points [index + 1], + 0/*pressure*/, 0, 0); + } +} + +void KisPainter::getBezierCurvePoints(const KisPoint &pos1, + const KisPoint &control1, + const KisPoint &control2, + const KisPoint &pos2, + vKisPoint& points) +{ + double d1 = pointToLineDistance(control1, pos1, pos2); + double d2 = pointToLineDistance(control2, pos1, pos2); + + if (d1 < BEZIER_FLATNESS_THRESHOLD && d2 < BEZIER_FLATNESS_THRESHOLD) { + points.push_back(pos1); + } else { + // Midpoint subdivision. See Foley & Van Dam Computer Graphics P.508 + KisVector2D p1 = pos1; + KisVector2D p2 = control1; + KisVector2D p3 = control2; + KisVector2D p4 = pos2; + + KisVector2D l2 = (p1 + p2) / 2; + KisVector2D h = (p2 + p3) / 2; + KisVector2D l3 = (l2 + h) / 2; + KisVector2D r3 = (p3 + p4) / 2; + KisVector2D r2 = (h + r3) / 2; + KisVector2D l4 = (l3 + r2) / 2; + KisVector2D r1 = l4; + KisVector2D l1 = p1; + KisVector2D r4 = p4; + + getBezierCurvePoints(l1.toKisPoint(), l2.toKisPoint(), l3.toKisPoint(), l4.toKisPoint(), points); + getBezierCurvePoints(r1.toKisPoint(), r2.toKisPoint(), r3.toKisPoint(), r4.toKisPoint(), points); + } +} + +double KisPainter::paintBezierCurve(const KisPoint &pos1, + const double pressure1, + const double xTilt1, + const double yTilt1, + const KisPoint &control1, + const KisPoint &control2, + const KisPoint &pos2, + const double pressure2, + const double xTilt2, + const double yTilt2, + const double savedDist) +{ + double newDistance; + double d1 = pointToLineDistance(control1, pos1, pos2); + double d2 = pointToLineDistance(control2, pos1, pos2); + + if (d1 < BEZIER_FLATNESS_THRESHOLD && d2 < BEZIER_FLATNESS_THRESHOLD) { + newDistance = paintLine(pos1, pressure1, xTilt1, yTilt1, pos2, pressure2, xTilt2, yTilt2, savedDist); + } else { + // Midpoint subdivision. See Foley & Van Dam Computer Graphics P.508 + KisVector2D p1 = pos1; + KisVector2D p2 = control1; + KisVector2D p3 = control2; + KisVector2D p4 = pos2; + + KisVector2D l2 = (p1 + p2) / 2; + KisVector2D h = (p2 + p3) / 2; + KisVector2D l3 = (l2 + h) / 2; + KisVector2D r3 = (p3 + p4) / 2; + KisVector2D r2 = (h + r3) / 2; + KisVector2D l4 = (l3 + r2) / 2; + KisVector2D r1 = l4; + KisVector2D l1 = p1; + KisVector2D r4 = p4; + + double midPressure = (pressure1 + pressure2) / 2; + double midXTilt = (xTilt1 + xTilt2) / 2; + double midYTilt = (yTilt1 + yTilt2) / 2; + + newDistance = paintBezierCurve(l1.toKisPoint(), pressure1, xTilt1, yTilt1, + l2.toKisPoint(), l3.toKisPoint(), + l4.toKisPoint(), midPressure, midXTilt, midYTilt, + savedDist); + newDistance = paintBezierCurve(r1.toKisPoint(), midPressure, midXTilt, midYTilt, + r2.toKisPoint(), + r3.toKisPoint(), + r4.toKisPoint(), pressure2, xTilt2, yTilt2, newDistance); + } + + return newDistance; +} + +void KisPainter::paintRect (const KisPoint &startPoint, + const KisPoint &endPoint, + const double /*pressure*/, + const double /*xTilt*/, + const double /*yTilt*/) +{ + KoRect normalizedRect = KisRect (startPoint, endPoint).normalize (); + + vKisPoint points; + + points.push_back(normalizedRect.topLeft()); + points.push_back(normalizedRect.bottomLeft()); + points.push_back(normalizedRect.bottomRight()); + points.push_back(normalizedRect.topRight()); + + paintPolygon(points); +} + +void KisPainter::paintEllipse (const KisPoint &startPoint, + const KisPoint &endPoint, + const double /*pressure*/, + const double /*xTilt*/, + const double /*yTilt*/) +{ + KisRect r = KisRect(startPoint, endPoint).normalize(); + + // See http://www.whizkidtech.redprince.net/bezier/circle/ for explanation. + // kappa = (4/3*(sqrt(2)-1)) + const double kappa = 0.5522847498; + const double lx = (r.width() / 2) * kappa; + const double ly = (r.height() / 2) * kappa; + + KisPoint center = r.center(); + + KisPoint p0(r.left(), center.y()); + KisPoint p1(r.left(), center.y() - ly); + KisPoint p2(center.x() - lx, r.top()); + KisPoint p3(center.x(), r.top()); + + vKisPoint points; + + getBezierCurvePoints(p0, p1, p2, p3, points); + + KisPoint p4(center.x() + lx, r.top()); + KisPoint p5(r.right(), center.y() - ly); + KisPoint p6(r.right(), center.y()); + + getBezierCurvePoints(p3, p4, p5, p6, points); + + KisPoint p7(r.right(), center.y() + ly); + KisPoint p8(center.x() + lx, r.bottom()); + KisPoint p9(center.x(), r.bottom()); + + getBezierCurvePoints(p6, p7, p8, p9, points); + + KisPoint p10(center.x() - lx, r.bottom()); + KisPoint p11(r.left(), center.y() + ly); + + getBezierCurvePoints(p9, p10, p11, p0, points); + + paintPolygon(points); +} + +void KisPainter::paintAt(const KisPoint & pos, + const double pressure, + const double xTilt, + const double yTilt) +{ + if (!m_paintOp) return; + m_paintOp->paintAt(pos, KisPaintInformation(pressure, xTilt, yTilt, KisVector2D())); +} + +double KisPainter::pointToLineDistance(const KisPoint& p, const KisPoint& l0, const KisPoint& l1) +{ + double lineLength = sqrt((l1.x() - l0.x()) * (l1.x() - l0.x()) + (l1.y() - l0.y()) * (l1.y() - l0.y())); + double distance = 0; + + if (lineLength > DBL_EPSILON) { + distance = ((l0.y() - l1.y()) * p.x() + (l1.x() - l0.x()) * p.y() + l0.x() * l1.y() - l1.x() * l0.y()) / lineLength; + distance = fabs(distance); + } + + return distance; +} + +/* + * Concave Polygon Scan Conversion + * by Paul Heckbert + * from "Graphics Gems", Academic Press, 1990 + */ + +/* + * concave: scan convert nvert-sided concave non-simple polygon with vertices at + * (point[i].x, point[i].y) for i in [0..nvert-1] within the window win by + * calling spanproc for each visible span of pixels. + * Polygon can be clockwise or counterclockwise. + * Algorithm does uniform point sampling at pixel centers. + * Inside-outside test done by Jordan's rule: a point is considered inside if + * an emanating ray intersects the polygon an odd number of times. + * drawproc should fill in pixels from xl to xr inclusive on scanline y, + * e.g: + * drawproc(y, xl, xr) + * int y, xl, xr; + * { + * int x; + * for (x=xl; x<=xr; x++) + * pixel_write(x, y, pixelvalue); + * } + * + * Paul Heckbert 30 June 81, 18 Dec 89 + */ + +typedef struct { /* a polygon edge */ + double x; /* x coordinate of edge's intersection with current scanline */ + double dx; /* change in x with respect to y */ + int i; /* edge number: edge i goes from pt[i] to pt[i+1] */ +} Edge; + +static int n; /* number of vertices */ +static const KisPoint *pt; /* vertices */ + +static int nact; /* number of active edges */ +static Edge *active; /* active edge list:edges crossing scanline y */ + +/* comparison routines for qsort */ +static int compare_ind(const void *pu, const void *pv) +{ + const int *u = static_cast<const int *>(pu); + const int *v = static_cast<const int *>(pv); + + return pt[*u].y() <= pt[*v].y() ? -1 : 1; +} + +static int compare_active(const void *pu, const void *pv) +{ + const Edge *u = static_cast<const Edge *>(pu); + const Edge *v = static_cast<const Edge *>(pv); + + return u->x <= v->x ? -1 : 1; +} + +static void cdelete(int i) /* remove edge i from active list */ +{ + int j; + + for (j=0; j<nact && active[j].i!=i; j++); + if (j>=nact) return; /* edge not in active list; happens at win->y0*/ + nact--; + bcopy(&active[j+1], &active[j], (nact-j)*sizeof active[0]); +} + +static void cinsert(int i, int y) /* append edge i to end of active list */ +{ + int j; + double dx; + const KisPoint *p, *q; + + j = i<n-1 ? i+1 : 0; + if (pt[i].y() < pt[j].y()) { + p = &pt[i]; q = &pt[j]; + } else { + p = &pt[j]; q = &pt[i]; + } + /* initialize x position at intersection of edge with scanline y */ + active[nact].dx = dx = (q->x()-p->x())/(q->y()-p->y()); + active[nact].x = dx*(y+.5-p->y())+p->x(); + active[nact].i = i; + nact++; +} + +void KisPainter::fillPolygon(const vKisPoint& points, FillStyle fillStyle) +{ + int nvert = points.count(); + int k, y0, y1, y, i, j, xl, xr; + int *ind; /* list of vertex indices, sorted by pt[ind[j]].y */ + + n = nvert; + pt = &(points[0]); + if (n<3) return; + if (fillStyle == FillStyleNone) { + return; + } + + ind = new int[n]; + Q_CHECK_PTR(ind); + active = new Edge[n]; + Q_CHECK_PTR(active); + + /* create y-sorted array of indices ind[k] into vertex list */ + for (k=0; k<n; k++) + ind[k] = k; + qsort(ind, n, sizeof ind[0], compare_ind); /* sort ind by pt[ind[k]].y */ + + nact = 0; /* start with empty active list */ + k = 0; /* ind[k] is next vertex to process */ + y0 = static_cast<int>(ceil(pt[ind[0]].y()-.5)); /* ymin of polygon */ + y1 = static_cast<int>(floor(pt[ind[n-1]].y()-.5)); /* ymax of polygon */ + + int x0 = INT_MAX; + int x1 = INT_MIN; + + for (int i = 0; i < nvert; i++) { + int pointHighX = static_cast<int>(ceil(points[i].x() - 0.5)); + int pointLowX = static_cast<int>(floor(points[i].x() - 0.5)); + + if (pointLowX < x0) { + x0 = pointLowX; + } + if (pointHighX > x1) { + x1 = pointHighX; + } + } + + // Fill the polygon bounding rectangle with the required contents then we'll + // create a mask for the actual polygon coverage. + + KisPaintDeviceSP polygon = new KisPaintDevice(m_device->colorSpace(), "polygon"); + Q_CHECK_PTR(polygon); + + KisFillPainter fillPainter(polygon); + QRect boundingRectangle(x0, y0, x1 - x0 + 1, y1 - y0 + 1); + + // Clip to the image bounds. + if (m_device->image()) { + boundingRectangle &= m_device->image()->bounds(); + } + + switch (fillStyle) { + default: + // Fall through + case FillStyleGradient: + // Currently unsupported, fall through + case FillStyleStrokes: + // Currently unsupported, fall through + kdWarning(DBG_AREA_CORE) << "Unknown or unsupported fill style in fillPolygon\n"; + case FillStyleForegroundColor: + fillPainter.fillRect(boundingRectangle, paintColor(), OPACITY_OPAQUE); + break; + case FillStyleBackgroundColor: + fillPainter.fillRect(boundingRectangle, backgroundColor(), OPACITY_OPAQUE); + break; + case FillStylePattern: + Q_ASSERT(m_pattern != 0); + fillPainter.fillRect(boundingRectangle, m_pattern); + break; + } + + KisSelectionSP polygonMask = new KisSelection(polygon); + + for (y=y0; y<=y1; y++) { /* step through scanlines */ + /* scanline y is at y+.5 in continuous coordinates */ + + /* check vertices between previous scanline and current one, if any */ + for (; k<n && pt[ind[k]].y()<=y+.5; k++) { + /* to simplify, if pt.y=y+.5, pretend it's above */ + /* invariant: y-.5 < pt[i].y <= y+.5 */ + i = ind[k]; + /* + * insert or delete edges before and after vertex i (i-1 to i, + * and i to i+1) from active list if they cross scanline y + */ + j = i>0 ? i-1 : n-1; /* vertex previous to i */ + if (pt[j].y() <= y-.5) /* old edge, remove from active list */ + cdelete(j); + else if (pt[j].y() > y+.5) /* new edge, add to active list */ + cinsert(j, y); + j = i<n-1 ? i+1 : 0; /* vertex next after i */ + if (pt[j].y() <= y-.5) /* old edge, remove from active list */ + cdelete(i); + else if (pt[j].y() > y+.5) /* new edge, add to active list */ + cinsert(i, y); + } + + /* sort active edge list by active[j].x */ + qsort(active, nact, sizeof active[0], compare_active); + + /* draw horizontal segments for scanline y */ + for (j=0; j<nact; j+=2) { /* draw horizontal segments */ + /* span 'tween j & j+1 is inside, span tween j+1 & j+2 is outside */ + xl = static_cast<int>(ceil(active[j].x-.5)); /* left end of span */ + xr = static_cast<int>(floor(active[j+1].x-.5)); /* right end of span */ + + if (xl<=xr) { + KisHLineIterator it = polygonMask->createHLineIterator(xl, y, xr - xl + 1, true); + + while (!it.isDone()) { + // We're using a selection here, that means alpha colorspace, that means one byte. + it.rawData()[0] = MAX_SELECTED; + ++it; + } + } + + active[j].x += active[j].dx; /* increment edge coords */ + active[j+1].x += active[j+1].dx; + } + } + delete [] ind; + delete [] active; + + polygon->applySelectionMask(polygonMask); + + QRect r = polygon->extent(); + + // The strokes for the outline may have already added updated the dirtyrect, but it can't hurt, + // and if we're painting without outlines, then there will be no dirty rect. Let's do it ourselves... + // addDirtyRect( r ); // XXX the bltSelection will add to the dirtyrect + + bltSelection(r.x(), r.y(), compositeOp(), polygon, opacity(), r.x(), r.y(), r.width(), r.height()); +} + +void KisPainter::paintPolygon(const vKisPoint& points) +{ + if (m_fillStyle != FillStyleNone) { + fillPolygon(points, m_fillStyle); + } + + if (m_strokeStyle != StrokeStyleNone) { + if (points.count() > 1) { + double distance = -1; + + for (uint i = 0; i < points.count() - 1; i++) { + distance = paintLine(points[i], PRESSURE_DEFAULT, 0, 0, points[i + 1], PRESSURE_DEFAULT, 0, 0, distance); + } + paintLine(points[points.count() - 1], PRESSURE_DEFAULT, 0, 0, points[0], PRESSURE_DEFAULT, 0, 0, distance); + } + } +} + diff --git a/krita/core/kis_painter.h b/krita/core/kis_painter.h new file mode 100644 index 00000000..fa8de088 --- /dev/null +++ b/krita/core/kis_painter.h @@ -0,0 +1,432 @@ +/* + * Copyright (c) 2002 Patrick Julien <freak@codepimps.org> + * Copyright (c) 2004 Clarence Dang <dang@kde.org> + * + * 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_PAINTER_H_ +#define KIS_PAINTER_H_ + +#include <kcommand.h> + +#include "kis_color.h" +#include "kis_global.h" +#include "kis_types.h" +#include "kis_paint_device.h" +#include "kis_point.h" +#include "kis_filter.h" +#include "kis_progress_subject.h" +#include "kis_paintop.h" +#include "kis_color.h" + +#include <koffice_export.h> + +class QRect; +class KisTransaction; +class KisBrush; +class KisPattern; + +/** + * KisPainter contains the graphics primitives necessary to draw on a + * KisPaintDevice. This is the same kind of abstraction as used in Qt + * itself, where you have QPainter and QPaintDevice. + * + * However, KisPainter works on a tiled image and supports different + * colour models, and that's a lot more complicated. + * + * KisPainter supports transactions that can group various paint operations + * in one undoable step. + * + * For more complex operations, you might want to have a look at the subclasses + * of KisPainter: KisConvolutionPainter, KisFillPainter and KisGradientPainter + */ +class KRITACORE_EXPORT KisPainter : public KisProgressSubject { + typedef KisProgressSubject super; + +public: + /// Construct painter without a device + KisPainter(); + /// Construct a painter, and begin painting on the device + KisPainter(KisPaintDeviceSP device); + virtual ~KisPainter(); + +private: + // Implement KisProgressSubject + virtual void cancel() { m_cancelRequested = true; } + +public: + /** + * Start painting on the specified device. Not undoable. + */ + void begin(KisPaintDeviceSP device); + + /** + * Finish painting on the current device + */ + KCommand *end(); + + /// Begin an undoable paint operation + void beginTransaction(const QString& customName = QString::null); + + /// Finish the undoable paint operation + KCommand *endTransaction(); + + /// begin a transaction with the given command + void beginTransaction( KisTransaction* command); + + /// Return the current transcation + KisTransaction * transaction() { return m_transaction; } + + + /// Returns the current paint device. + KisPaintDeviceSP device() const { return m_device; } + + + // ----------------------------------------------------------------- + // Native paint methods that are undo/redo-able, + // use the color strategies and the composite operations. + + /** + * Blast the specified region from src onto the current paint device. + */ + void bitBlt(Q_INT32 dx, Q_INT32 dy, + const KisCompositeOp& op, + KisPaintDeviceSP src, + Q_INT32 sx, Q_INT32 sy, + Q_INT32 sw, Q_INT32 sh) + { + bitBlt(dx, dy, op, src, OPACITY_OPAQUE, sx, sy, sw, sh); + } + + /** + * Overloaded version of the previous, differs in that it is possible to specify + * a value for opacity + */ + void bitBlt(Q_INT32 dx, Q_INT32 dy, + const KisCompositeOp& op, + KisPaintDeviceSP src, + Q_UINT8 opacity, + Q_INT32 sx, Q_INT32 sy, + Q_INT32 sw, Q_INT32 sh); + + /** + * A version of bitBlt that renders using an external mask, ignoring + * the src device's own selection, if it has one. + */ + void bltMask(Q_INT32 dx, Q_INT32 dy, + const KisCompositeOp &op, + KisPaintDeviceSP src, + KisPaintDeviceSP selMask, + Q_UINT8 opacity, + Q_INT32 sx, Q_INT32 sy, + Q_INT32 sw, Q_INT32 sh); + + /** + * A version of bitBlt that renders using an external selection mask, ignoring + * the src device's own selection, if it has one. + */ + void bltSelection(Q_INT32 dx, Q_INT32 dy, + const KisCompositeOp &op, + KisPaintDeviceSP src, + KisSelectionSP selMask, + Q_UINT8 opacity, + Q_INT32 sx, Q_INT32 sy, + Q_INT32 sw, Q_INT32 sh); + + + /** + * A version of bitBlt that renders using the src device's selection mask, if it has one. + */ + void bltSelection(Q_INT32 dx, Q_INT32 dy, + const KisCompositeOp &op, + KisPaintDeviceSP src, + Q_UINT8 opacity, + Q_INT32 sx, Q_INT32 sy, + Q_INT32 sw, Q_INT32 sh); + + + /** + * The methods below are 'higher' level than the above methods. They need brushes, colors + * etc. set before they can be called. The methods do not directly tell the image to + * update, but you can call dirtyRect() to get the rect that needs to be notified by your + * painting code. + * + * Call will RESET the dirtyRect! + */ + QRect dirtyRect(); + + /** + * Add the r to the current dirty rect, and return the dirtyRect after adding r to it. + */ + QRect addDirtyRect(QRect r) { m_dirtyRect |= r; return m_dirtyRect; } + + + + /** + * Paint a line that connects the dots in points + */ + void paintPolyline(const QValueVector <KisPoint> &points, + int index = 0, int numPoints = -1); + + /** + * Draw a line between pos1 and pos2 using the currently set brush and color. + * If savedDist is less than zero, the brush is painted at pos1 before being + * painted along the line using the spacing setting. + * @return the drag distance, that is the remains of the distance between p1 and p2 not covered + * because the currenlty set brush has a spacing greater than that distance. + */ + double paintLine(const KisPoint &pos1, + const double pressure1, + const double xTilt1, + const double yTilt1, + const KisPoint &pos2, + const double pressure2, + const double xTilt2, + const double yTilt2, + const double savedDist = -1); + + /** + * Draw a Bezier curve between pos1 and pos2 using control points 1 and 2. + * If savedDist is less than zero, the brush is painted at pos1 before being + * painted along the curve using the spacing setting. + * @return the drag distance, that is the remains of the distance between p1 and p2 not covered + * because the currenlty set brush has a spacing greater than that distance. + */ + double paintBezierCurve(const KisPoint &pos1, + const double pressure1, + const double xTilt1, + const double yTilt1, + const KisPoint &control1, + const KisPoint &control2, + const KisPoint &pos2, + const double pressure2, + const double xTilt2, + const double yTilt2, + const double savedDist = -1); + + /** + * Fill the given vector points with the points needed to draw the Bezier curve between + * pos1 and pos2 using control points 1 and 2, excluding the final pos2. + */ + void getBezierCurvePoints(const KisPoint &pos1, + const KisPoint &control1, + const KisPoint &control2, + const KisPoint &pos2, + vKisPoint& points); + + + /** + * Paint the rectangle with given begin and end points + */ + void paintRect(const KisPoint &startPoint, + const KisPoint &endPoint, + const double pressure, + const double xTilt, + const double yTilt); + + + /** + * Paint the ellipse with given begin and end points + */ + void paintEllipse(const KisPoint &startPoint, + const KisPoint &endPoint, + const double pressure, + const double /*xTilt*/, + const double /*yTilt*/); + + /** + * Paint the polygon with the points given in points. It automatically closes the polygon + * by drawing the line from the last point to the first. + */ + void paintPolygon(const vKisPoint& points); + + /** Draw a spot at pos using the currently set paint op, brush and color */ + void paintAt(const KisPoint &pos, + const double pressure, + const double /*xTilt*/, + const double /*yTilt*/); + + + // ------------------------------------------------------------------------ + // Set the parameters for the higher level graphics primitives. + + /// Determines whether the brush spacing should vary when drawing + /// lines with the pressure + void setVaryBrushSpacingWithPressureWhenDrawingALine( bool varyBrushSpacingWithPressureWhenDrawingALine ) + { m_varyBrushSpacingWithPressureWhenDrawingALine = varyBrushSpacingWithPressureWhenDrawingALine; } + bool varyBrushSpacingWithPressureWhenDrawingALine() { return m_varyBrushSpacingWithPressureWhenDrawingALine; } + + /// Set the current brush + void setBrush(KisBrush* brush) { m_brush = brush; } + /// Returns the currently set brush + KisBrush * brush() const { return m_brush; } + + /// Set the current pattern + void setPattern(KisPattern * pattern) { m_pattern = pattern; } + /// Returns the currently set pattern + KisPattern * pattern() const { return m_pattern; } + + /// Set the color that will be used to paint with + void setPaintColor(const KisColor& color) { m_paintColor = color;} + + /// Returns the color that will be used to paint with + KisColor paintColor() const { return m_paintColor; } + + /// Set the current background color + void setBackgroundColor(const KisColor& color) {m_backgroundColor = color; } + /// Returns the current background color + KisColor backgroundColor() const { return m_backgroundColor; } + + /// Set the current fill color + void setFillColor(const KisColor& color) { m_fillColor = color; } + /// Returns the current fill color + KisColor fillColor() const { return m_fillColor; } + + + /// This enum contains the styles with which we can fill things like polygons and ellipses + enum FillStyle { + FillStyleNone, + FillStyleForegroundColor, + FillStyleBackgroundColor, + FillStylePattern, + FillStyleGradient, + FillStyleStrokes + }; + + /// Set the current style with which to fill + void setFillStyle(FillStyle fillStyle) { m_fillStyle = fillStyle; } + /// Returns the current fill style + FillStyle fillStyle() const { return m_fillStyle; } + + /// The style of the brush stroke around polygons and so + enum StrokeStyle { + StrokeStyleNone, + StrokeStyleBrush + }; + + /// Set the current brush stroke style + void setStrokeStyle(StrokeStyle strokeStyle) { m_strokeStyle = strokeStyle; } + /// Returns the current brush stroke style + StrokeStyle strokeStyle() const { return m_strokeStyle; } + + /// Set the opacity which is used in painting (like filling polygons) + void setOpacity(Q_UINT8 opacity) { m_opacity = opacity; } + /// Returns the opacity that is used in painting + Q_UINT8 opacity() const { return m_opacity; } + + /** + * Sets the current composite operation. Everything painted will be composited on + * the destination layer with this composite op. + **/ + void setCompositeOp(const KisCompositeOp& op) { m_compositeOp = op; } + /// Returns the current composite operation + KisCompositeOp compositeOp() const { return m_compositeOp; } + + /// Sets the current KisFilter, used by the paintops that support it (like KisFilterOp) + void setFilter(KisFilterSP filter) { m_filter = filter; } + /// Returns the current KisFilter + KisFilterSP filter() { return m_filter; } + + /** + * The offset for paint operations that use it (like KisDuplicateOp). It will use as source + * the part of the layer that is at its paintedPosition - duplicateOffset + */ + // TODO: this is an hack ! it must be fix, the following functions have nothing to do here + void setDuplicateOffset(const KisPoint& offset) { m_duplicateOffset = offset; } + /// Returns the offset for duplication + KisPoint duplicateOffset(){ return m_duplicateOffset; } + + inline void setDuplicateHealing(bool v) { m_duplicateHealing = v; } + inline bool duplicateHealing() { return m_duplicateHealing; } + + inline void setDuplicateHealingRadius(int r) { m_duplicateHealingRadius = r; } + inline int duplicateHealingRadius() { return m_duplicateHealingRadius; } + + inline void setDuplicatePerspectiveCorrection(bool v) { m_duplicatePerspectiveCorrection = v; } + inline bool duplicatePerspectiveCorrection() { return m_duplicatePerspectiveCorrection; } + + void setDuplicateStart(const KisPoint start) { m_duplicateStart = start;} + KisPoint duplicateStart() { return m_duplicateStart;} + + /// Sets the current pressure for things that like to use this + void setPressure(double pressure) { m_pressure = pressure; } + /// Returns the current pressure + double pressure() { return m_pressure; } + + /** + * Set the current paint operation. This is used for all drawing functions. + * The painter will DELETE the paint op itself!! + * That means no that you should not delete it yourself (or put it on the stack) + */ + void setPaintOp(KisPaintOp * paintOp) { delete m_paintOp; m_paintOp = paintOp; } + /// Returns the current paint operation + KisPaintOp * paintOp() const { return m_paintOp; } + + /// Set a current 'dab'. This usually is a paint device containing a rendered brush + void setDab(KisPaintDeviceSP dab) { m_dab = dab; } + /// Get the currently set dab + KisPaintDeviceSP dab() const { return m_dab; } + + /// Is cancel Requested by the KisProgressSubject for this painter + bool cancelRequested() const { return m_cancelRequested; } + +protected: + /// Initialize, set everything to '0' or defaults + void init(); + KisPainter(const KisPainter&); + KisPainter& operator=(const KisPainter&); + + /// Calculate the distance that point p is from the line made by connecting l0 and l1 + static double pointToLineDistance(const KisPoint& p, const KisPoint& l0, const KisPoint& l1); + + /// Fill the polygon defined by points with the fillStyle + void fillPolygon(const vKisPoint& points, FillStyle fillStyle); + +protected: + KisPaintDeviceSP m_device; + KisTransaction *m_transaction; + + QRect m_dirtyRect; + + KisColor m_paintColor; + KisColor m_backgroundColor; + KisColor m_fillColor; + FillStyle m_fillStyle; + StrokeStyle m_strokeStyle; + KisBrush *m_brush; + KisPattern *m_pattern; + KisPoint m_duplicateOffset; + KisPoint m_duplicateStart; + bool m_duplicateHealing; + int m_duplicateHealingRadius; + bool m_duplicatePerspectiveCorrection; + Q_UINT8 m_opacity; + KisCompositeOp m_compositeOp; + KisFilterSP m_filter; + KisPaintOp * m_paintOp; + double m_pressure; + bool m_cancelRequested; + Q_INT32 m_pixelSize; + KisColorSpace * m_colorSpace; + KisProfile * m_profile; + KisPaintDeviceSP m_dab; + bool m_varyBrushSpacingWithPressureWhenDrawingALine; + +}; + + +#endif // KIS_PAINTER_H_ + diff --git a/krita/core/kis_paintop.cc b/krita/core/kis_paintop.cc new file mode 100644 index 00000000..4030e931 --- /dev/null +++ b/krita/core/kis_paintop.cc @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2002 Patrick Julien <freak@codepimps.org> + * Copyright (c) 2004 Boudewijn Rempt <boud@valdyas.org> + * Copyright (c) 2004 Clarence Dang <dang@kde.org> + * Copyright (c) 2004 Adrian Page <adrian@pagenet.plus.com> + * Copyright (c) 2004 Cyrille Berger <cberger@cberger.net> + * + * 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 "qwidget.h" +#include "kis_painter.h" +#include "kis_layer.h" +#include "kis_types.h" +#include "kis_paintop.h" +#include "kis_alpha_mask.h" +#include "kis_point.h" +#include "kis_colorspace.h" +#include "kis_global.h" +#include "kis_iterators_pixel.h" +#include "kis_color.h" + +KisPaintOp::KisPaintOp(KisPainter * painter) + : m_dab(0) +{ + m_painter = painter; + setSource(painter->device()); +} + +KisPaintOp::~KisPaintOp() +{ +} + +KisPaintDeviceSP KisPaintOp::computeDab(KisAlphaMaskSP mask) { + return computeDab(mask, m_painter->device()->colorSpace()); +} + +KisPaintDeviceSP KisPaintOp::computeDab(KisAlphaMaskSP mask, KisColorSpace *cs) +{ + // XXX: According to the SeaShore source, the Gimp uses a + // temporary layer the size of the layer that is being painted + // on. This layer is cleared between painting actions. Our + // temporary layer, dab, is for every paintAt, composited with + // the target layer. We only use a real temporary layer for things + // like filter tools. + + if(!m_dab || m_dab->colorSpace() != cs) + m_dab = new KisPaintDevice(cs, "dab"); + Q_CHECK_PTR(m_dab); + + KisColor kc = m_painter->paintColor(); + + KisColorSpace * colorSpace = m_dab->colorSpace(); + + Q_INT32 pixelSize = colorSpace->pixelSize(); + + Q_INT32 maskWidth = mask->width(); + Q_INT32 maskHeight = mask->height(); + + // Convert the kiscolor to the right colorspace. + kc.convertTo(colorSpace); + + KisHLineIteratorPixel hiter = m_dab->createHLineIterator(0, 0, maskWidth, true); + for (int y = 0; y < maskHeight; y++) + { + int x=0; + while(! hiter.isDone()) + { + // XXX: Set mask + colorSpace->setAlpha(kc.data(), mask->alphaAt(x++, y), 1); + memcpy(hiter.rawData(), kc.data(), pixelSize); + ++hiter; + } + hiter.nextRow(); + } + + return m_dab; +} + +void KisPaintOp::splitCoordinate(double coordinate, Q_INT32 *whole, double *fraction) +{ + Q_INT32 i = static_cast<Q_INT32>(coordinate); + + if (coordinate < 0) { + // We always want the fractional part to be positive. + // E.g. -1.25 becomes -2 and +0.75 + i--; + } + + double f = coordinate - i; + + *whole = i; + *fraction = f; +} + +void KisPaintOp::setSource(KisPaintDeviceSP p) { + Q_ASSERT(p); + m_source = p; +} + + +KisPaintOpSettings* KisPaintOpFactory::settings(QWidget* /*parent*/, const KisInputDevice& /*inputDevice*/) { return 0; } diff --git a/krita/core/kis_paintop.h b/krita/core/kis_paintop.h new file mode 100644 index 00000000..6a6efddc --- /dev/null +++ b/krita/core/kis_paintop.h @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2002 Patrick Julien <freak@codepimps.org> + * Copyright (c) 2004 Boudewijn Rempt <boud@valdyas.org> + * Copyright (c) 2004 Clarence Dang <dang@kde.org> + * Copyright (c) 2004 Adrian Page <adrian@pagenet.plus.com> + * Copyright (c) 2004 Cyrille Berger <cberger@cberger.net> + * + * 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_PAINTOP_H_ +#define KIS_PAINTOP_H_ + +#include <qstring.h> + +#include <ksharedptr.h> +#include <klocale.h> + +#include "kis_global.h" +#include "kis_types.h" +#include "kis_id.h" +#include "kis_vec.h" +#include "kis_colorspace.h" + +#include <koffice_export.h> + +class KisPoint; +class KisAlphaMask; +class KisPainter; +class KisColorSpace; +class KisInputDevice; +class QWidget; + +/** + * This class keeps information that can be used in the painting process, for example by + * brushes. + **/ +class KRITACORE_EXPORT KisPaintInformation { +public: + KisPaintInformation(double pressure = PRESSURE_DEFAULT, + double xTilt = 0.0, double yTilt = 0.0, + KisVector2D movement = KisVector2D()) + : pressure(pressure), xTilt(xTilt), yTilt(yTilt), movement(movement) {} + double pressure; + double xTilt; + double yTilt; + KisVector2D movement; +}; + +class KRITACORE_EXPORT KisPaintOp : public KShared +{ + +public: + + KisPaintOp(KisPainter * painter); + virtual ~KisPaintOp(); + + virtual void paintAt(const KisPoint &pos, const KisPaintInformation& info) = 0; + void setSource(KisPaintDeviceSP p); + + /** + * Whether this paintop wants to deposit paint even when not moving, i.e. the + * tool needs to activate its timer. + */ + virtual bool incremental() { return false; } + + +protected: + + virtual KisPaintDeviceSP computeDab(KisAlphaMaskSP mask); + virtual KisPaintDeviceSP computeDab(KisAlphaMaskSP mask, KisColorSpace *cs); + + + /** + * Split the coordinate into whole + fraction, where fraction is always >= 0. + */ + virtual void splitCoordinate(double coordinate, Q_INT32 *whole, double *fraction); + + KisPainter * m_painter; + KisPaintDeviceSP m_source; // use this layer as source layer for the operation +private: + KisPaintDeviceSP m_dab; +}; + +class KisPaintOpSettings { + +public: + KisPaintOpSettings(QWidget *parent) { Q_UNUSED(parent); } + virtual ~KisPaintOpSettings() {} + + virtual QWidget *widget() const { return 0; } +}; + +/** + * The paintop factory is responsible for creating paintops of the specified class. + * If there is an optionWidget, the derived paintop itself must support settings, + * and it's up to the factory to do that. + */ +class KisPaintOpFactory : public KShared +{ + +public: + KisPaintOpFactory() {} + virtual ~KisPaintOpFactory() {} + + virtual KisPaintOp * createOp(const KisPaintOpSettings *settings, KisPainter * painter) = 0; + virtual KisID id() { return KisID("abstractpaintop", i18n("Abstract PaintOp")); } + + /** + * The filename of the pixmap we can use to represent this paintop in the ui. + */ + virtual QString pixmap() { return ""; } + + /** + * Whether this paintop is internal to a certain tool or can be used + * in various tools. If false, it won't show up in the toolchest. + * The KisColorSpace argument can be used when certain paintops only support a specific cs + */ + virtual bool userVisible(KisColorSpace * cs = 0) { return cs->id() != KisID("WET", ""); } + + /** + * Create and return an (abstracted) widget with options for this paintop when used with the + * specified input device. Return 0 if there are no settings available for the given + * device. + */ + virtual KisPaintOpSettings* settings(QWidget* parent, const KisInputDevice& inputDevice); + +}; +#endif // KIS_PAINTOP_H_ diff --git a/krita/core/kis_paintop_registry.cc b/krita/core/kis_paintop_registry.cc new file mode 100644 index 00000000..0ba7b5f3 --- /dev/null +++ b/krita/core/kis_paintop_registry.cc @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2004 Boudewijn Rempt <boud@valdyas.org> + * + * 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 <qpixmap.h> +#include <qwidget.h> + +#include <kdebug.h> +#include <kinstance.h> +#include <kglobal.h> +#include <klocale.h> +#include <kstandarddirs.h> +#include <kparts/plugin.h> +#include <kservice.h> +#include <ktrader.h> +#include <kparts/componentfactory.h> + +#include "kis_generic_registry.h" +#include "kis_types.h" +#include "kis_paintop_registry.h" +#include "kis_paintop.h" +#include "kis_id.h" +#include "kis_debug_areas.h" +#include "kis_colorspace.h" + +KisPaintOpRegistry * KisPaintOpRegistry::m_singleton = 0; + +KisPaintOpRegistry::KisPaintOpRegistry() +{ + Q_ASSERT(KisPaintOpRegistry::m_singleton == 0); + KisPaintOpRegistry::m_singleton = this; + + KTrader::OfferList offers = KTrader::self()->query(QString::fromLatin1("Krita/Paintop"), + QString::fromLatin1("(Type == 'Service') and " + "([X-Krita-Version] == 2)")); + + KTrader::OfferList::ConstIterator iter; + + for(iter = offers.begin(); iter != offers.end(); ++iter) + { + KService::Ptr service = *iter; + int errCode = 0; + KParts::Plugin* plugin = + KParts::ComponentFactory::createInstanceFromService<KParts::Plugin> ( service, this, 0, QStringList(), &errCode); + if ( plugin ) + kdDebug(41006) << "found plugin " << service->property("Name").toString() << "\n"; + else { + kdDebug(41006) << "found plugin " << service->property("Name").toString() << ", " << errCode << "\n"; + if( errCode == KParts::ComponentFactory::ErrNoLibrary) + { + kdWarning(41006) << " Error loading plugin was : ErrNoLibrary " << KLibLoader::self()->lastErrorMessage() << endl; + } + } + + } + +} + +KisPaintOpRegistry::~KisPaintOpRegistry() +{ +} + +KisPaintOpRegistry* KisPaintOpRegistry::instance() +{ + if(KisPaintOpRegistry::m_singleton == 0) + { + KisPaintOpRegistry::m_singleton = new KisPaintOpRegistry(); + Q_CHECK_PTR(KisPaintOpRegistry::m_singleton); + } + return KisPaintOpRegistry::m_singleton; +} + +KisPaintOp * KisPaintOpRegistry::paintOp(const KisID & id, const KisPaintOpSettings * settings, KisPainter * painter) const +{ + if (painter == 0) { + kdWarning() << " KisPaintOpRegistry::paintOp painter is null"; + return 0; + } + KisPaintOpFactorySP f = get(id); + if (f) { + return f->createOp(settings, painter); + } + else { + return 0; + } +} + +KisPaintOp * KisPaintOpRegistry::paintOp(const QString & id, const KisPaintOpSettings * settings, KisPainter * painter) const +{ + return paintOp(KisID(id, ""), settings, painter); +} + +KisPaintOpSettings * KisPaintOpRegistry::settings(const KisID& id, QWidget * parent, const KisInputDevice& inputDevice) const +{ + KisPaintOpFactory* f = get(id); + if (f) + return f->settings( parent, inputDevice ); + + return 0; +} + +bool KisPaintOpRegistry::userVisible(const KisID & id, KisColorSpace* cs) const +{ + + KisPaintOpFactorySP f = get(id); + if (!f) { + kdDebug(DBG_AREA_REGISTRY) << "No paintop " << id.id() << "\n"; + return false; + } + return f->userVisible(cs); + +} + +QString KisPaintOpRegistry::pixmap(const KisID & id) const +{ + KisPaintOpFactorySP f = get(id); + + if (!f) { + kdDebug(DBG_AREA_REGISTRY) << "No paintop " << id.id() << "\n"; + return ""; + } + + return f->pixmap(); +} + +#include "kis_paintop_registry.moc" diff --git a/krita/core/kis_paintop_registry.h b/krita/core/kis_paintop_registry.h new file mode 100644 index 00000000..c544a601 --- /dev/null +++ b/krita/core/kis_paintop_registry.h @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2004 Boudewijn Rempt <boud@valdyas.org> + * + * 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_PAINTOP_REGISTRY_H_ +#define KIS_PAINTOP_REGISTRY_H_ + +#include <qobject.h> + +#include "kis_types.h" +#include "kis_generic_registry.h" +#include <koffice_export.h> + +class QWidget; +class QStringList; + +class KisPaintOp; +class KisPaintOpSettings; +class KisPainter; +class KisColorSpace; +class KisInputDevice; + +class KRITACORE_EXPORT KisPaintOpRegistry : public QObject, public KisGenericRegistry<KisPaintOpFactorySP> +{ + + Q_OBJECT + +public: + virtual ~KisPaintOpRegistry(); + + /** + * Return a newly created paintop + */ + KisPaintOp * paintOp(const KisID& id, const KisPaintOpSettings * settings, KisPainter * painter) const; + + /** + * Return a newly created paintopd + */ + KisPaintOp * paintOp(const QString& id, const KisPaintOpSettings * settings, KisPainter * painter) const; + + /** + * Create and return an (abstracted) configuration widget + * for using the specified paintop with the specified input device, + * with the specified parent as widget parent. Returns 0 if there + * are no settings available for the given device. + */ + KisPaintOpSettings * settings(const KisID& id, QWidget * parent, const KisInputDevice& inputDevice) const; + + // Whether we should show this paintop in the toolchest + bool userVisible(const KisID & id, KisColorSpace* cs) const; + + // Get the name of the icon to show in the toolchest + QString pixmap(const KisID & id) const; + + +public: + static KisPaintOpRegistry* instance(); + +private: + KisPaintOpRegistry(); + KisPaintOpRegistry(const KisPaintOpRegistry&); + KisPaintOpRegistry operator=(const KisPaintOpRegistry&); + +private: + static KisPaintOpRegistry *m_singleton; +}; + +#endif // KIS_PAINTOP_REGISTRY_H_ + diff --git a/krita/core/kis_palette.cc b/krita/core/kis_palette.cc new file mode 100644 index 00000000..ad39eed5 --- /dev/null +++ b/krita/core/kis_palette.cc @@ -0,0 +1,306 @@ +/* + * Copyright (c) 2005 Boudewijn Rempt <boud@valdyas.org> + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#ifdef HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif + +#include <netinet/in.h> +#include <limits.h> +#include <stdlib.h> +#include <cfloat> + +#include <qimage.h> +#include <qpoint.h> +#include <qvaluevector.h> +#include <qfile.h> +#include <qtextstream.h> + +#include <kdebug.h> +#include <klocale.h> + +#include "kis_debug_areas.h" +#include "kis_palette.h" +#include "kis_iterators_pixel.h" + + +namespace { + enum enumPaletteType { + FORMAT_UNKNOWN, + FORMAT_GPL, // Gimp palette + FORMAT_PAL, // RIFF palette + FORMAT_ACT // Photoshop binary color palette + }; + +} + + +KisPalette::KisPalette(const QImage * img, Q_INT32 nColors, const QString & name) + : super(QString("")), + m_name(name) +{ + Q_ASSERT(nColors > 0); + Q_ASSERT(!img->isNull()); + + // XXX: Implement + + m_columns = 0; // Set the default value that the GIMP uses... +} + +KisPalette::KisPalette(const KisPaintDeviceSP device, Q_INT32 nColors, const QString & name) + : super(QString("")), + m_name(name) +{ + Q_ASSERT(nColors > 0); + Q_ASSERT(device != 0); + + + // XXX: Implement + m_columns = 0; // Set the default value that the GIMP uses... +} + + +KisPalette::KisPalette(const KisGradient * gradient, Q_INT32 nColors, const QString & name) + : super(QString("")), + m_name(name) +{ + Q_ASSERT(nColors > 0); + Q_ASSERT(gradient != 0); + + double dx, cur_x; + QColor c; + Q_INT32 i; + Q_UINT8 opacity; + dx = 1.0 / (nColors - 1); + + KisPaletteEntry e; + for (i = 0, cur_x = 0; i < nColors; i++, cur_x += dx) { + gradient->colorAt(cur_x, &e.color, &opacity); + e.name = "Untitled"; + add(e); + } + + m_columns = 0; // Set the default value that the GIMP uses... +} + +KisPalette::KisPalette(const QString& filename) + : super(filename) +{ + // Implemented in super class + m_columns = 0; // Set the default value that the GIMP uses... +} + +KisPalette::KisPalette() + : super("") +{ + m_columns = 0; // Set the default value that the GIMP uses... +} + +/// Create an copied palette +KisPalette::KisPalette(const KisPalette& rhs) + : super("") +{ + setFilename(rhs.filename()); + m_ownData = false; + m_img = rhs.m_img; + m_name = rhs.m_name; + m_comment = rhs.m_comment; + m_columns = rhs.m_columns; + m_colors = rhs.m_colors; + setValid(true); +} + +KisPalette::~KisPalette() +{ +} + +bool KisPalette::load() +{ + QFile file(filename()); + file.open(IO_ReadOnly); + m_data = file.readAll(); + file.close(); + return init(); +} + + +bool KisPalette::save() +{ + QFile file(filename()); + if (!file.open(IO_WriteOnly | IO_Truncate)) { + return false; + } + + QTextStream stream(&file); + // Header: Magic\nName: <name>\nColumns: <no idea what this means, but default = 0> + // In any case, we don't use Columns... + stream << "GIMP Palette\nName: " << name() << "\nColumns: " << m_columns << "\n#\n"; + + for (uint i = 0; i < m_colors.size(); i++) { + const KisPaletteEntry& entry = m_colors.at(i); + QColor c = entry.color; + stream << c.red() << " " << c.green() << " " << c.blue() << "\t"; + if (entry.name.isEmpty()) + stream << "Untitled\n"; + else + stream << entry.name << "\n"; + } + + file.close(); + return true; +} + +QImage KisPalette::img() +{ + return m_img; +} + +Q_INT32 KisPalette::nColors() +{ + return m_colors.count(); +} + +bool KisPalette::init() +{ + enumPaletteType format = FORMAT_UNKNOWN; + + QString s = QString::fromUtf8(m_data.data(), m_data.count()); + + if (s.isEmpty() || s.isNull() || s.length() < 50) { + kdWarning(DBG_AREA_FILE) << "Illegal Gimp palette file: " << filename() << "\n"; + return false; + } + + + if (s.startsWith("RIFF") || s.startsWith("PAL data")) + { + format = FORMAT_PAL; + } + else if (s.startsWith("GIMP Palette")) + { + // XXX: No checks for wrong input yet! + Q_UINT32 index = 0; + + QStringList lines = QStringList::split("\n", s); + + if (lines.size() < 3) { + return false; + } + + QString entry, channel, columns; + QStringList c; + Q_INT32 r, g, b; + QColor color; + KisPaletteEntry e; + + format = FORMAT_GPL; + + // Read name + if (!lines[1].startsWith("Name: ") || !lines[0].startsWith("GIMP") ) + { + kdWarning(DBG_AREA_FILE) << "Illegal Gimp palette file: " << filename() << "\n"; + return false; + } + + setName(i18n(lines[1].mid(strlen("Name: ")).stripWhiteSpace().ascii())); + + index = 2; + + // Read columns + if (lines[index].startsWith("Columns: ")) { + columns = lines[index].mid(strlen("Columns: ")).stripWhiteSpace();; + m_columns = columns.toInt(); + index = 3; + } + + for (Q_UINT32 i = index; i < lines.size(); i++) { + if (lines[i].startsWith("#")) { + m_comment += lines[i].mid(1).stripWhiteSpace() + " "; + } + else if (!lines[i].isEmpty()) + { + QStringList a = QStringList::split(" ", lines[i].replace(QChar('\t'), " ")); + + if (a.count() < 3) + { + break; + } + + r = a[0].toInt(); + a.pop_front(); + g = a[0].toInt(); + a.pop_front(); + b = a[0].toInt(); + a.pop_front(); + + if (r < 0 || r > 255 || + g < 0 || g > 255 || + b < 0 || b > 255) + { + break; + } + + color = QColor(r, g, b); + e.color = color; + + QString name = a.join(" "); + e.name = name.isEmpty() ? i18n("Untitled") : name; + + add(e); + } + } + setValid(true); + return true; + } + else if (s.length() == 768) { + kdWarning(DBG_AREA_FILE) << "Photoshop format palette file. Not implemented yet\n"; + format = FORMAT_ACT; + } + return false; +} + + +void KisPalette::add(const KisPaletteEntry & c) +{ + m_colors.push_back(c); +} + +void KisPalette::remove(const KisPaletteEntry & c) +{ + QValueVector<KisPaletteEntry>::iterator it = m_colors.begin(); + QValueVector<KisPaletteEntry>::iterator end = m_colors.end(); + + while (it != end) { + if ((*it) == c) { + m_colors.erase(it); + return; + } + ++it; + } +} + +KisPaletteEntry KisPalette::getColor(Q_UINT32 index) +{ + return m_colors[index]; +} + +#include "kis_palette.moc" diff --git a/krita/core/kis_palette.h b/krita/core/kis_palette.h new file mode 100644 index 00000000..5d7d02c9 --- /dev/null +++ b/krita/core/kis_palette.h @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2005 Boudewijn Rempt <boud@valdyas.org> + * + * 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_PALETTE_ +#define KIS_PALETTE_ + +#include <qimage.h> +#include <qcolor.h> +#include <qvaluevector.h> + +#include <kio/job.h> +#include <kpalette.h> + +#include "kis_types.h" +#include "kis_resource.h" +#include "kis_global.h" +#include "kis_gradient.h" +#include "kis_alpha_mask.h" + +class QPoint; +class QPixmap; +class KisPaintDevice; + +struct KisPaletteEntry { + QColor color; + QString name; + bool operator==(const KisPaletteEntry& rhs) const { + return color == rhs.color && name == rhs.name; + } +}; + +/** + * Open Gimp, Photoshop or RIFF palette files. This is a straight port + * from the Gimp. + */ +class KisPalette : public KisResource { + typedef KisResource super; + + Q_OBJECT + +public: + /** + * Create a palette from the colours in an image + */ + KisPalette(const QImage * img, Q_INT32 nColors, const QString & name); + + /** + * Create a palette from the colours in a paint device + */ + KisPalette(const KisPaintDeviceSP device, Q_INT32 nColors, const QString & name); + + /** + * Create a palette from the colours in a gradient + */ + KisPalette(const KisGradient * gradient, Q_INT32 nColors, const QString & name); + + /** + * Load a palette from a file. This can be a Gimp + * palette, a RIFF palette or a Photoshop palette. + */ + KisPalette(const QString& filename); + + /// Create an empty palette + KisPalette(); + + /// Explicit copy constructor (KisResource copy constructor is private) + KisPalette(const KisPalette& rhs); + + virtual ~KisPalette(); + + virtual bool load(); + virtual bool save(); + virtual QImage img(); + + +public: + + void add(const KisPaletteEntry &); + void remove(const KisPaletteEntry &); + KisPaletteEntry getColor(Q_UINT32 index); + Q_INT32 nColors(); + +private: + bool init(); + +private: + + QByteArray m_data; + bool m_ownData; + QImage m_img; + QString m_name; + QString m_comment; + Q_INT32 m_columns; + QValueVector<KisPaletteEntry> m_colors; + +}; +#endif // KIS_PALETTE_ + diff --git a/krita/core/kis_part_layer_iface.h b/krita/core/kis_part_layer_iface.h new file mode 100644 index 00000000..b324c975 --- /dev/null +++ b/krita/core/kis_part_layer_iface.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2006 Bart Coppens <kde@bartcoppens.be> + * + * 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_PART_LAYER_IFACE_ +#define KIS_PART_LAYER_IFACE_ + +#include <qdom.h> +#include "kis_types.h" + +/** + * An interface for the Part Layer so that we can use it in core/, but can implement it in ui/ + */ +class KisPartLayer : public KisLayer { + typedef KisLayer super; +public: + KisPartLayer(KisImage *img, const QString &name, Q_UINT8 opacity) + : super(img, name, opacity) {} + virtual KisPaintDeviceSP prepareProjection(KisPaintDeviceSP projection, const QRect& r) = 0; + virtual bool saveToXML(QDomDocument doc, QDomElement elem) = 0; +}; + +#endif // KIS_PART_IFACE_LAYER_IFACE_ diff --git a/krita/core/kis_pattern.cc b/krita/core/kis_pattern.cc new file mode 100644 index 00000000..db6ae0a1 --- /dev/null +++ b/krita/core/kis_pattern.cc @@ -0,0 +1,335 @@ +/* + * kis_pattern.cc - part of Krayon + * + * Copyright (c) 2000 Matthias Elter <elter@kde.org> + * 2001 John Califf + * 2004 Boudewijn Rempt <boud@valdyas.org> + * + * 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_pattern.h" + +#include <sys/types.h> +#include <netinet/in.h> + +#include <limits.h> +#include <stdlib.h> + +#include <qpoint.h> +#include <qsize.h> +#include <qimage.h> +#include <qvaluevector.h> +#include <qmap.h> +#include <qfile.h> + +#include <kdebug.h> +#include <klocale.h> + +#include "kis_color.h" +#include "kis_layer.h" +#include "kis_paint_device.h" + +namespace { + struct GimpPatternHeader { + Q_UINT32 header_size; /* header_size = sizeof (PatternHeader) + brush name */ + Q_UINT32 version; /* pattern file version # */ + Q_UINT32 width; /* width of pattern */ + Q_UINT32 height; /* height of pattern */ + Q_UINT32 bytes; /* depth of pattern in bytes : 1, 2, 3 or 4*/ + Q_UINT32 magic_number; /* GIMP brush magic number */ + }; + + // Yes! This is _NOT_ what my pat.txt file says. It's really not 'GIMP', but 'GPAT' + Q_UINT32 const GimpPatternMagic = (('G' << 24) + ('P' << 16) + ('A' << 8) + ('T' << 0)); +} + +KisPattern::KisPattern(const QString& file) : super(file), m_hasFile(true) +{ +} + +KisPattern::KisPattern(KisPaintDevice* image, int x, int y, int w, int h) + : super(""), m_hasFile(false) +{ + // Forcefully convert to RGBA8 + // XXX profile and exposure? + setImage(image->convertToQImage(0, x, y, w, h)); + setName(image->name()); +} + +KisPattern::~KisPattern() +{ +} + +bool KisPattern::load() +{ + if (!m_hasFile) + return true; + + QFile file(filename()); + file.open(IO_ReadOnly); + QByteArray data = file.readAll(); + if (!data.isEmpty()) { + Q_INT32 startPos = m_data.size(); + + m_data.resize(m_data.size() + data.count()); + memcpy(&m_data[startPos], data.data(), data.count()); + } + file.close(); + return init(); +} + +bool KisPattern::save() +{ + QFile file(filename()); + file.open(IO_WriteOnly | IO_Truncate); + + QTextStream stream(&file); + // Header: header_size (24+name length),version,width,height,colourdepth of brush,magic,name + // depth: 1 = greyscale, 2 = greyscale + A, 3 = RGB, 4 = RGBA + // magic = "GPAT", as a single uint32, the docs are wrong here! + // name is UTF-8 (\0-terminated! The docs say nothing about this!) + // _All_ data in network order, it seems! (not mentioned in gimp-2.2.8/devel-docs/pat.txt!!) + // We only save RGBA at the moment + // Version is 1 for now... + + GimpPatternHeader ph; + QCString utf8Name = name().utf8(); + char const* name = utf8Name.data(); + int nameLength = qstrlen(name); + + ph.header_size = htonl(sizeof(GimpPatternHeader) + nameLength + 1); // trailing 0 + ph.version = htonl(1); + ph.width = htonl(width()); + ph.height = htonl(height()); + ph.bytes = htonl(4); + ph.magic_number = htonl(GimpPatternMagic); + + QByteArray bytes; + bytes.setRawData(reinterpret_cast<char*>(&ph), sizeof(GimpPatternHeader)); + int wrote = file.writeBlock(bytes); + bytes.resetRawData(reinterpret_cast<char*>(&ph), sizeof(GimpPatternHeader)); + + if (wrote == -1) + return false; + + wrote = file.writeBlock(name, nameLength + 1); // Trailing 0 apparantly! + if (wrote == -1) + return false; + + int k = 0; + bytes.resize(width() * height() * 4); + for (Q_INT32 y = 0; y < height(); y++) { + for (Q_INT32 x = 0; x < width(); x++) { + // RGBA only + QRgb pixel = m_img.pixel(x,y); + bytes[k++] = static_cast<char>(qRed(pixel)); + bytes[k++] = static_cast<char>(qGreen(pixel)); + bytes[k++] = static_cast<char>(qBlue(pixel)); + bytes[k++] = static_cast<char>(qAlpha(pixel)); + } + } + + wrote = file.writeBlock(bytes); + if (wrote == -1) + return false; + + file.close(); + + return true; +} + +QImage KisPattern::img() +{ + return m_img; +} + +bool KisPattern::init() +{ + // load Gimp patterns + GimpPatternHeader bh; + Q_INT32 k; + QValueVector<char> name; + + if (sizeof(GimpPatternHeader) > m_data.size()) { + return false; + } + + memcpy(&bh, &m_data[0], sizeof(GimpPatternHeader)); + bh.header_size = ntohl(bh.header_size); + bh.version = ntohl(bh.version); + bh.width = ntohl(bh.width); + bh.height = ntohl(bh.height); + bh.bytes = ntohl(bh.bytes); + bh.magic_number = ntohl(bh.magic_number); + + if (bh.header_size > m_data.size() || bh.header_size == 0) { + return false; + } + + name.resize(bh.header_size - sizeof(GimpPatternHeader)); + memcpy(&name[0], &m_data[sizeof(GimpPatternHeader)], name.size()); + + if (name[name.size() - 1]) { + return false; + } + + setName(i18n(&name[0])); + + if (bh.width == 0 || bh.height == 0 || !m_img.create(bh.width, bh.height, 32)) { + return false; + } + + k = bh.header_size; + + if (bh.bytes == 1) { + // Grayscale + Q_INT32 val; + + for (Q_UINT32 y = 0; y < bh.height; y++) { + for (Q_UINT32 x = 0; x < bh.width; x++, k++) { + if (static_cast<Q_UINT32>(k) > m_data.size()) { + kdDebug(DBG_AREA_FILE) << "failed in gray\n"; + return false; + } + + val = m_data[k]; + m_img.setPixel(x, y, qRgb(val, val, val)); + m_img.setAlphaBuffer(false); + } + } + } else if (bh.bytes == 2) { + // Grayscale + A + Q_INT32 val; + Q_INT32 alpha; + for (Q_UINT32 y = 0; y < bh.height; y++) { + for (Q_UINT32 x = 0; x < bh.width; x++, k++) { + if (static_cast<Q_UINT32>(k + 2) > m_data.size()) { + kdDebug(DBG_AREA_FILE) << "failed in grayA\n"; + return false; + } + + val = m_data[k]; + alpha = m_data[k++]; + m_img.setPixel(x, y, qRgba(val, val, val, alpha)); + m_img.setAlphaBuffer(true); + } + } + } else if (bh.bytes == 3) { + // RGB without alpha + for (Q_UINT32 y = 0; y < bh.height; y++) { + for (Q_UINT32 x = 0; x < bh.width; x++) { + if (static_cast<Q_UINT32>(k + 3) > m_data.size()) { + kdDebug(DBG_AREA_FILE) << "failed in RGB\n"; + return false; + } + + m_img.setPixel(x, y, qRgb(m_data[k], + m_data[k + 1], + m_data[k + 2])); + k += 3; + m_img.setAlphaBuffer(false); + } + } + } else if (bh.bytes == 4) { + // Has alpha + for (Q_UINT32 y = 0; y < bh.height; y++) { + for (Q_UINT32 x = 0; x < bh.width; x++) { + if (static_cast<Q_UINT32>(k + 4) > m_data.size()) { + kdDebug(DBG_AREA_FILE) << "failed in RGBA\n"; + return false; + } + + m_img.setPixel(x, y, qRgba(m_data[k], + m_data[k + 1], + m_data[k + 2], + m_data[k + 3])); + k += 4; + m_img.setAlphaBuffer(true); + } + } + } else { + return false; + } + + if (m_img.isNull()) { + return false; + } + + setWidth(m_img.width()); + setHeight(m_img.height()); + + setValid(true); + + return true; +} + +KisPaintDeviceSP KisPattern::image(KisColorSpace * colorSpace) { + // Check if there's already a pattern prepared for this colorspace + QMap<QString, KisPaintDeviceSP>::const_iterator it = m_colorspaces.find(colorSpace->id().id()); + if (it != m_colorspaces.end()) + return (*it); + + // If not, create one + KisPaintDeviceSP layer = new KisPaintDevice(colorSpace, "pattern"); + + Q_CHECK_PTR(layer); + + layer->convertFromQImage(m_img,""); + + m_colorspaces[colorSpace->id().id()] = layer; + return layer; +} + +Q_INT32 KisPattern::width() const +{ + return m_width; +} + +void KisPattern::setWidth(Q_INT32 w) +{ + m_width = w; +} + +Q_INT32 KisPattern::height() const +{ + return m_height; +} + +void KisPattern::setHeight(Q_INT32 h) +{ + m_height = h; +} + +void KisPattern::setImage(const QImage& img) +{ + m_hasFile = false; + m_img = img; + m_img.detach(); + + setWidth(img.width()); + setHeight(img.height()); + + setValid(true); +} + +KisPattern* KisPattern::clone() const +{ + KisPattern* pattern = new KisPattern(""); + pattern->setImage(m_img); + pattern->setName(name()); + return pattern; +} + +#include "kis_pattern.moc" diff --git a/krita/core/kis_pattern.h b/krita/core/kis_pattern.h new file mode 100644 index 00000000..4b5868f3 --- /dev/null +++ b/krita/core/kis_pattern.h @@ -0,0 +1,79 @@ +/* + * kis_pattern.h - part of Krayon + * + * Copyright (c) 2000 Matthias Elter <elter@kde.org> + * + * 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_pattern_h__ +#define __kis_pattern_h__ + +#include <kio/job.h> + +#include "kis_debug_areas.h" +#include "kis_resource.h" +#include "kis_types.h" + +class QPoint; +class QImage; +class KisColorSpace; +class KisPaintDevice; + +class KisPattern : public KisResource { + typedef KisResource super; + Q_OBJECT + +public: + KisPattern(const QString& file); + KisPattern(KisPaintDevice* image, int x, int y, int w, int h); + virtual ~KisPattern(); + + virtual bool load(); + virtual bool save(); + virtual QImage img(); + + /** + * returns a KisPaintDeviceSP made with colorSpace as the ColorSpace strategy + * for use in the fill painter. + **/ + KisPaintDeviceSP image(KisColorSpace * colorSpace); + + Q_INT32 width() const; + Q_INT32 height() const; + + void setImage(const QImage& img); + + KisPattern* clone() const; + +protected: + void setWidth(Q_INT32 w); + void setHeight(Q_INT32 h); + +private: + bool init(); + +private: + QByteArray m_data; + QImage m_img; + QMap<QString, KisPaintDeviceSP> m_colorspaces; + bool m_hasFile; + + Q_INT32 m_width; + Q_INT32 m_height; +}; + +#endif + diff --git a/krita/core/kis_perspective_grid.cpp b/krita/core/kis_perspective_grid.cpp new file mode 100644 index 00000000..d9b0e800 --- /dev/null +++ b/krita/core/kis_perspective_grid.cpp @@ -0,0 +1,100 @@ +/* + * This file is part of Krita + * + * Copyright (c) 2006 Cyrille Berger <cberger@cberger.net> + * + * 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_perspective_grid.h" + +int KisSubPerspectiveGrid::s_lastIndex = 0; + +KisSubPerspectiveGrid::KisSubPerspectiveGrid(KisPerspectiveGridNodeSP topLeft, KisPerspectiveGridNodeSP topRight, KisPerspectiveGridNodeSP bottomRight, KisPerspectiveGridNodeSP bottomLeft) : m_topLeft(topLeft), m_topRight(topRight), m_bottomRight(bottomRight), m_bottomLeft(bottomLeft), m_subdivisions(5), m_leftGrid(0), m_rightGrid(0), m_topGrid(0), m_bottomGrid(0), m_index(++s_lastIndex) +{ + +} + +bool KisSubPerspectiveGrid::contains(const KisPoint p) const +{ + return true; + KisPerspectiveMath::LineEquation d1 = KisPerspectiveMath::computeLineEquation( topLeft(), topRight() ); + kdDebug() << p.y() << " " << (p.x() * d1.a + d1.b) << endl; + if( p.y() >= p.x() * d1.a + d1.b) + { + d1 = KisPerspectiveMath::computeLineEquation( topRight(), bottomRight() ); + kdDebug() << p.y() << " " << (p.x() * d1.a + d1.b) << endl; + if( p.y() >= p.x() * d1.a + d1.b) + { + d1 = KisPerspectiveMath::computeLineEquation( bottomRight(), bottomLeft() ); + kdDebug() << p.y() << " " << (p.x() * d1.a + d1.b) << endl; + if( p.y() <= p.x() * d1.a + d1.b) + { + d1 = KisPerspectiveMath::computeLineEquation( bottomLeft(), topLeft() ); + kdDebug() << p.y() << " " << (p.x() * d1.a + d1.b) << endl; + if( p.y() <= p.x() * d1.a + d1.b) + { + return true; + } + } + } + } + return false; +} + + +KisPerspectiveGrid::KisPerspectiveGrid() +{ +} + + +KisPerspectiveGrid::~KisPerspectiveGrid() +{ + clearSubGrids( ); +} + +bool KisPerspectiveGrid::addNewSubGrid( KisSubPerspectiveGrid* ng ) +{ + if(hasSubGrids() && !ng->topGrid() && !ng->bottomGrid() && !ng->leftGrid() && !ng->rightGrid() ) + { + kdError() << "sub grids need a neighbourgh if they are not the first grid to be added" << endl; + return false; + } + m_subGrids.push_back(ng); + return true; +} + + +void KisPerspectiveGrid::clearSubGrids( ) +{ + for( QValueList<KisSubPerspectiveGrid*>::const_iterator it = begin(); it != end(); ++it) + { + delete *it; + } + m_subGrids.clear(); +} + +KisSubPerspectiveGrid* KisPerspectiveGrid::gridAt(KisPoint p) +{ + for( QValueList<KisSubPerspectiveGrid*>::const_iterator it = begin(); it != end(); ++it) + { + if( (*it)->contains(p) ) + { + return *it; + } + } + return 0; +} + diff --git a/krita/core/kis_perspective_grid.h b/krita/core/kis_perspective_grid.h new file mode 100644 index 00000000..76021a6c --- /dev/null +++ b/krita/core/kis_perspective_grid.h @@ -0,0 +1,107 @@ +/* + * This file is part of Krita + * + * Copyright (c) 2006 Cyrille Berger <cberger@cberger.net> + * + * 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_PERSPECTIVE_GRID_H +#define KIS_PERSPECTIVE_GRID_H + +#include <qvaluelist.h> + +#include <kis_perspective_math.h> +#include <kis_point.h> +#include <ksharedptr.h> + +class KisPerspectiveGridNode : public KisPoint, public KShared { + public: + inline KisPerspectiveGridNode(double x, double y) : KisPoint(x,y) { } + inline KisPerspectiveGridNode(KisPoint p) : KisPoint(p) { } +}; +typedef KSharedPtr<KisPerspectiveGridNode> KisPerspectiveGridNodeSP; + +class KisSubPerspectiveGrid { + public: + KisSubPerspectiveGrid(KisPerspectiveGridNodeSP topLeft, KisPerspectiveGridNodeSP topRight, KisPerspectiveGridNodeSP bottomRight, KisPerspectiveGridNodeSP bottomLeft); + + inline KisPoint topBottomVanishingPoint() { return computeVanishingPoint( topLeft(), topRight(), bottomLeft(), bottomRight() ); }; + inline KisPoint leftRightVanishingPoint() { return computeVanishingPoint( topLeft(), bottomLeft(), topRight(), bottomRight() ); }; + + inline KisSubPerspectiveGrid* leftGrid() { return m_leftGrid; } + inline void setLeftGrid(KisSubPerspectiveGrid* g) { Q_ASSERT(m_leftGrid==0); m_leftGrid = g; } + inline KisSubPerspectiveGrid* rightGrid() { return m_rightGrid; } + inline void setRightGrid(KisSubPerspectiveGrid* g) { Q_ASSERT(m_rightGrid==0); m_rightGrid = g; } + inline KisSubPerspectiveGrid* topGrid() { return m_topGrid; } + inline void setTopGrid(KisSubPerspectiveGrid* g) { Q_ASSERT(m_topGrid==0); m_topGrid = g; } + inline KisSubPerspectiveGrid* bottomGrid() { return m_bottomGrid; } + inline void setBottomGrid(KisSubPerspectiveGrid* g) { Q_ASSERT(m_bottomGrid==0); m_bottomGrid = g; } + inline const KisPerspectiveGridNodeSP topLeft() const { return m_topLeft; } + inline KisPerspectiveGridNodeSP topLeft() { return m_topLeft; } + inline const KisPerspectiveGridNodeSP topRight() const { return m_topRight; } + inline KisPerspectiveGridNodeSP topRight() { return m_topRight; } + inline const KisPerspectiveGridNodeSP bottomLeft() const { return m_bottomLeft; } + inline KisPerspectiveGridNodeSP bottomLeft() { return m_bottomLeft; } + inline const KisPerspectiveGridNodeSP bottomRight() const { return m_bottomRight; } + inline KisPerspectiveGridNodeSP bottomRight() { return m_bottomRight; } + inline int subdivisions() const { return m_subdivisions; } + /** + * Return the index of the subgrid, the value is automaticaly set when the KisSubPerspectiveGrid, it is usefull for + * drawing the perspective grid, to avoid drawing twice the same border, or points + */ + inline int index() const { return m_index; } + + /** + * @return true if the point p is contain by the grid + */ + bool contains(const KisPoint p) const; + private: + inline KisPoint computeVanishingPoint(KisPerspectiveGridNodeSP p11, KisPerspectiveGridNodeSP p12, KisPerspectiveGridNodeSP p21, KisPerspectiveGridNodeSP p22) + { + KisPerspectiveMath::LineEquation d1 = KisPerspectiveMath::computeLineEquation( p11, p12 ); + KisPerspectiveMath::LineEquation d2 = KisPerspectiveMath::computeLineEquation( p21, p22 ); + return KisPerspectiveMath::computeIntersection(d1,d2); + } + private: + KisPerspectiveGridNodeSP m_topLeft, m_topRight, m_bottomLeft, m_bottomRight; + KisSubPerspectiveGrid *m_leftGrid, *m_rightGrid, *m_topGrid, *m_bottomGrid; + int m_subdivisions; + int m_index; + static int s_lastIndex; +}; + +class KisPerspectiveGrid { + public: + KisPerspectiveGrid(); + ~KisPerspectiveGrid(); + /** + * @return false if the grid wasn't added, note that subgrids must be attached to an other grid, except if it's the first grid + */ + bool addNewSubGrid( KisSubPerspectiveGrid* ng ); + inline QValueList<KisSubPerspectiveGrid*>::const_iterator begin() const { return m_subGrids.begin(); } + inline QValueList<KisSubPerspectiveGrid*>::const_iterator end() const { return m_subGrids.end(); } + inline bool hasSubGrids() const { return !m_subGrids.isEmpty(); } + void clearSubGrids(); + inline int countSubGrids() const { return m_subGrids.size(); } + /** + * @return the first grid hit by the point p + */ + KisSubPerspectiveGrid* gridAt(KisPoint p); + private: + QValueList<KisSubPerspectiveGrid*> m_subGrids; +}; + +#endif diff --git a/krita/core/kis_perspective_math.cpp b/krita/core/kis_perspective_math.cpp new file mode 100644 index 00000000..eadbab8b --- /dev/null +++ b/krita/core/kis_perspective_math.cpp @@ -0,0 +1,546 @@ +/* + * This file is part of Krita + * + * Copyright (c) 2006 Cyrille Berger <cberger@cberger.net> + * + * 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_perspective_math.h" + +#include <qrect.h> + +#if 1 + +#include <iostream> +#include <stdlib.h> +#include <math.h> +//#define NDEBUG // uncomment to remove checking of assert() +#include <assert.h> +#define DEFAULT_ALLOC 2 + +namespace math { // TODO: use eigen + +template <class ElType> class matrix; + +template <class ElType> + class vector +{ + public: + friend class matrix<ElType>; + ElType * data; + int len; + int length()const; + vector(); + vector(int n); + ~vector(){ delete [] data;} + //Copy operator + vector(const vector<ElType>& v) ; + //assignment operator + vector<ElType>& operator =(const vector<ElType> &original); + ElType& operator[](int i)const ; + void zero(); + vector<ElType> operator+(const vector<ElType>& v); + vector<ElType> operator-(const vector<ElType>&v); + void rprint()const; //print entries on a single line + void resize(int n); + int operator==(const vector<ElType>& v)const; + friend vector<ElType> operator* (ElType c,vector<ElType>& v ); + friend vector<ElType> operator*(vector<ElType>& v,ElType c ); + friend std::ostream& operator<<(std::ostream& s,vector<ElType>& v); +}; +template <class ElType> + void vector<ElType>::zero() +{ + for(int i=0;i<len;i++) data[i]=(ElType)0; +} +template <class ElType> + int vector<ElType>::length()const +{ + return len; +} +template <class ElType> + ElType& vector<ElType>::operator[](int i)const +{ + assert(i>=0 && i < len); + return data[i]; +} + +template <class ElType> + vector<ElType>::vector() +{ + data=new ElType[ DEFAULT_ALLOC]; + assert(data!=0); + len= DEFAULT_ALLOC; +} +template <class ElType> + vector<ElType>::vector(int n) +{ + data = new ElType[len=n]; + assert(data!=0); +} +template <class ElType> + vector<ElType>::vector(const vector<ElType>& v) +{ + data=new ElType[len=v.len]; + assert(data!=0); + for(int i=0;i<len;i++) data[i]=v.data[i]; +} +template <class ElType> + vector<ElType>& vector<ElType>::operator =(const vector<ElType> &original) +{ + if(this != &original) + { + delete [] data; + data= new ElType[len=original.len]; + assert(data!=0); + for(int i=0;i<len;i++) data[i]=original.data[i]; + } + return *this; +} +template <class ElType> + vector<ElType> vector<ElType>::operator+(const vector<ElType>& v) +{ + vector<ElType> sum(len); + for(int i=0;i<len;i++) sum[i] = data[i]+v.data[i]; + return sum; + +} +template <class ElType> + vector<ElType> vector<ElType>::operator-(const vector<ElType>& v) +{ + vector<ElType> sum(len); + for(int i=0;i<len;i++) sum[i] = data[i]-v.data[i]; + return sum; +} +template <class ElType> + void vector<ElType>::rprint()const //print entries on a single line +{ + int i; + std::cout << "VECTOR: "; + std::cout << "("; + for(i=0;i<len-1;i++) std::cout << data[i] << ","; + std::cout << data[len-1] << ")" << std::endl; + return; +} +template <class ElType> + void vector<ElType>::resize(int n) +{ + delete[]data; + data = new ElType[len=n]; + assert(data !=0); +} +template <class ElType> + int vector<ElType>::operator==(const vector<ElType>& v)const +{ + if(len != v.len) return 0; + for(int i=0;i<len;i++) if(data[i]!=v.data[i]) return 0; + return 1; +} +template <class ElType> + vector<ElType> operator*(ElType c,vector<ElType>& v ) +{ + vector<ElType> ans(v.len); + for(int i=0;i<v.len;i++) ans[i]=c*v[i]; + return ans; +} +template <class ElType> + vector<ElType> operator*(vector<ElType>& v,ElType c ) +{ + vector<ElType> ans(v.len); + for(int i=0;i<v.len;i++) ans[i]=c*v[i]; + return ans; +} +template <class ElType> + std::ostream& operator<<(std::ostream& s,vector<ElType>& v) +{ + s << "("; + for(int i=0;i<v.len-1;i++) s << v.data[i] << ", "; + s << v.data[v.len-1]<<")"<<endl; + return s; +} + +template <class ElType> + class matrix +{ + public: + vector<ElType> *m; + int rows,cols; + matrix(); + matrix( int r, int c); + matrix(const matrix<ElType> &s); + ~matrix(); + matrix& operator =(const matrix<ElType>& s); + vector<ElType>& operator[](const int i); + vector<ElType> operator*(const vector<ElType>&); + friend matrix<ElType> operator*(const ElType&, const matrix<ElType>&); + friend matrix<ElType> operator*(const matrix<ElType>&, const ElType&); + matrix<ElType> operator*(const matrix<ElType>& a); + matrix<ElType> operator+(const matrix<ElType>& a); + matrix<ElType> operator-(const matrix<ElType>& a); + matrix<ElType> transpose(); + //matrix<ElType> inverse(); + friend std::ostream& operator<<(std::ostream& s,matrix<ElType>& m); + friend void ludcmp(matrix<ElType>& a,vector<int>& indx,double &d); + friend void lubksb(matrix<ElType>&a,vector<int>& indx,vector<ElType>&b); +}; +template <class ElType> + matrix<ElType>::matrix() +{ + m = new vector<ElType>[DEFAULT_ALLOC]; + assert(m !=0); + rows=cols=DEFAULT_ALLOC; + for(int i=0;i<rows;i++) + { + vector<ElType> v; + m[i]= v; + } +} + +template <class ElType> + matrix<ElType>::matrix(int r, int c) +{ + m= new vector<ElType>[r]; + assert(m != 0); + rows=r; + cols=c; + for(int i=0;i<r;i++) + { + vector<ElType> v(cols); + m[i]=v; + } +} +template <class ElType> + matrix<ElType>::matrix(const matrix<ElType> &s) +{ + int i; + rows=s.rows; + m = new vector<ElType>[rows]; + assert(m!=0); + cols =s.cols; + for(i=0;i<rows;i++) + { + m[i]=s.m[i]; + } +} +template <class ElType> + matrix<ElType>::~matrix() +{ + delete [] m; +} + +template <class ElType> + matrix<ElType>& matrix<ElType>::operator =(const matrix<ElType> &s) +{ + if(this != &s) + { + delete []m; + rows= s.rows; + cols=s.cols; + m = new vector<ElType>[rows]; + assert(m !=0); + for(int i=0;i<rows;i++) m[i]=s.m[i]; + } + return *this; +} +template <class ElType> + vector<ElType>& matrix<ElType>::operator[](const int i) +{ + assert(i>=0 && i < rows); + return m[i]; +} +template <class ElType> + vector<ElType> matrix<ElType>::operator*(const vector<ElType>& v) +{ + int i,j; + assert(cols == v.len); + vector<ElType> ans(rows); + for(i=0;i<rows;i++) + { + ans.data[i]=0.0; + for(j=0;j<cols;j++) ans.data[i] += m[i][j]*v.data[j]; + } + return ans; +} +template <class ElType> + matrix<ElType> operator*(const ElType& x,const matrix<ElType>& s) +{ + matrix<ElType> ans(s.rows,s.cols); + for(int i=0;i<ans.rows;i++) + { + ans.m[i]= x*s.m[i]; + } + return ans; +} +template <class ElType> + matrix<ElType> matrix<ElType>::transpose() +{ + matrix<ElType> ans(cols,rows); + for(int i=0;i<rows;i++) + { + for(int j=0;j<cols;j++) ans[j][i]=m[i][j]; + } + return ans; +} +template <class ElType> + matrix<ElType> operator*(const matrix<ElType>& s,const ElType& x) +{ + matrix<ElType> ans(s.rows,s.cols); + for(int i=0;i<ans.rows;i++) + { + ans.m[i]= x*s.m[i]; + } + return ans; +} +template <class ElType> + matrix<ElType> matrix<ElType> ::operator*(const matrix<ElType>& a) +{ + + assert(cols == a.rows); + + matrix<ElType> ans(rows,a.cols); + for(int i=0;i<rows;i++) + { + for(int j=0;j<a.cols;j++) + { + ans.m[i][j]=0.0; + for(int k=0;k<cols;k++) + { + ans.m[i][j] += m[i][k]*a.m[k][j]; + } + } + } + return ans; +} +template <class ElType> + matrix<ElType> matrix<ElType> ::operator+(const matrix<ElType> & a) +{ + int i,j; + + assert(rows== a.rows); + assert(cols== a.cols); + + matrix<ElType> ans(a.rows,a.cols); + for(i=0;i<a.rows;i++) + { + for(j=0;j<a.cols;j++) + { + ans.m[i][j] = m[i][j] + a.m[i][j]; //faster than assigning vectors? + } + } + return ans; +} +template <class ElType> + matrix<ElType> matrix<ElType>::operator-(const matrix<ElType>& a) +{ + int i,j; + assert(rows == a.rows); + assert(cols == a.cols); + matrix ans(rows,cols); + for(i=0;i<rows;i++) + { + for(j=0;j<cols;j++) + ans.m[i][j] = m[i][j] - a.m[i][j]; + } + return ans; +} +template <class ElType> + std::ostream& operator<<(std::ostream& s,matrix<ElType>& m) +{ + for(int i=0; i<m.rows;i++) s << m[i]; + return s; +} + +#define TINY 1.0e-20; +//we assume fabs(ElType) is defined +//assignment of doubles to ElType is defined +template <class ElType> +void ludcmp(matrix<ElType>& a, vector<int>& indx,double& d) +{ + int i,imax,j,k; + ElType big,dum,sum,temp; + int n=a.rows; + vector<ElType> vv(n); + assert(a.rows == a.cols); + d=1.0; + for (i=0;i<n;i++) + { + big=0.0; +// kdDebug() << "new search" << endl; + for (j=0;j<n;j++) { if ((temp=fabs(a[i][j])) > big) big=temp; +/* kdDebug() << temp << " " << fabs(a[i][j]) << " "<< big <<endl; */} + if (big == 0.0) { std::cerr << "Singular matrix in routine LUDCMP" << std::endl; big = TINY;} + vv[i]=1.0/big; + } + for (j=0;j<n;j++) + { + for (i=0;i<j;i++) + { + sum=a[i][j]; + for (k=0;k<i;k++) sum -= a[i][k]*a[k][j]; + a[i][j]=sum; + } + big=0.0; + for (i=j;i<n;i++) + { + sum=a[i][j]; + for (k=0;k<j;k++) sum -= a[i][k]*a[k][j]; + a[i][j]=sum; + if ( (dum=vv[i]*fabs(sum)) >= big) + { + big=dum; + imax=i; + } + } + if (j != imax) + { + for (k=0;k<n;k++) + { + dum=a[imax][k]; + a[imax][k]=a[j][k]; + a[j][k]=dum; + } + d = -(d); + vv[imax]=vv[j]; + } + indx[j]=imax; + if (a[j][j] == 0.0) a[j][j]=TINY; + if (j != n-1) { + dum=1.0/(a[j][j]); + for (i=j+1;i<n;i++) a[i][j] *= dum; + } + } +} +#undef TINY +template <class ElType> +void lubksb(matrix<ElType>& a,vector<int>& indx,vector<ElType>& b) +{ + int i,ip,j; + ElType sum; + int n=a.rows; + for (i=0;i<n;i++) + { + ip=indx[i]; + sum=b[ip]; + b[ip]=b[i]; + for (j=0;j<=i-1;j++) sum -= a[i][j]*b[j]; + b[i]=sum; + } + for (i=n-1;i>=0;i--) + { + sum=b[i]; + for (j=i+1;j<n;j++) sum -= a[i][j]*b[j]; + b[i]=sum/a[i][i]; + } +} + + + +} +#endif + +double* KisPerspectiveMath::computeMatrixTransfo( const KisPoint& topLeft1, const KisPoint& topRight1, const KisPoint& bottomLeft1, const KisPoint& bottomRight1 , const KisPoint& topLeft2, const KisPoint& topRight2, const KisPoint& bottomLeft2, const KisPoint& bottomRight2) +{ + double* matrix = new double[9]; + + math::matrix<double> a(10,10); + math::vector<double> b(10); + math::vector<int> indx(10); + double d = 0.; + for(int i = 0; i <= 9; i++) + { + for(int j = 0; j <= 9; j++) + { + a[i][j] = 0.; + } + b[i] = 0.; + indx[i] = 0; + } + + // topLeft + a[0][0] = topLeft1.x(); + a[0][1] = topLeft1.y(); + a[0][2] = 1; + a[0][6] = -topLeft2.x() * topLeft1.x(); + a[0][7] = -topLeft2.x() * topLeft1.y(); + a[0][8] = -topLeft2.x(); + a[1][3] = topLeft1.x(); + a[1][4] = topLeft1.y(); + a[1][5] = 1; + a[1][6] = -topLeft2.y() * topLeft1.x(); + a[1][7] = -topLeft2.y() * topLeft1.y(); + a[1][8] = -topLeft2.y(); + // topRight + a[2][0] = topRight1.x(); + a[2][1] = topRight1.y(); + a[2][2] = 1; + a[2][6] = -topRight2.x() * topRight1.x(); + a[2][7] = -topRight2.x() * topRight1.y(); + a[2][8] = -topRight2.x(); + a[3][3] = topRight1.x(); + a[3][4] = topRight1.y(); + a[3][5] = 1; + a[3][6] = -topRight2.y() * topRight1.x(); + a[3][7] = -topRight2.y() * topRight1.y(); + a[3][8] = -topRight2.y(); + // bottomLeft1 + a[4][0] = bottomLeft1.x(); + a[4][1] = bottomLeft1.y(); + a[4][2] = 1; + a[4][6] = -bottomLeft2.x() * bottomLeft1.x(); + a[4][7] = -bottomLeft2.x() * bottomLeft1.y(); + a[4][8] = -bottomLeft2.x(); + a[5][3] = bottomLeft1.x(); + a[5][4] = bottomLeft1.y(); + a[5][5] = 1; + a[5][6] = -bottomLeft2.y() * bottomLeft1.x(); + a[5][7] = -bottomLeft2.y() * bottomLeft1.y(); + a[5][8] = -bottomLeft2.y(); + // bottomRight + a[6][0] = bottomRight1.x(); + a[6][1] = bottomRight1.y(); + a[6][2] = 1; + a[6][6] = -bottomRight2.x() * bottomRight1.x(); + a[6][7] = -bottomRight2.x() * bottomRight1.y(); + a[6][8] = -bottomRight2.x(); + a[7][3] = bottomRight1.x(); + a[7][4] = bottomRight1.y(); + a[7][5] = 1; + a[7][6] = -bottomRight2.y() * bottomRight1.x(); + a[7][7] = -bottomRight2.y() * bottomRight1.y(); + a[7][8] = -bottomRight2.y(); + a[8][8] = 1; + b[8] = 1; +// kdDebug() << " a := { { " << a[0][0] << " , " << a[0][1] << " , " << a[0][2] << " , " << a[0][3] << " , " << a[0][4] << " , " << a[0][5] << " , " << a[0][6] << " , " << a[0][7] << " , " << a[0][8] << " } , { " << a[1][0] << " , " << a[1][1] << " , " << a[1][2] << " , " << a[1][3] << " , " << a[1][4] << " , " << a[1][5] << " , " << a[1][6] << " , " << a[1][7] << " , " << a[1][8] << " } , { " << a[2][0] << " , " << a[2][1] << " , " << a[2][2] << " , " << a[2][3] << " , " << a[2][4] << " , " << a[2][5] << " , " << a[2][6] << " , " << a[2][7] << " , " << a[2][8] << " } , { " << a[3][0] << " , " << a[3][1] << " , " << a[3][2] << " , " << a[3][3] << " , " << a[3][4] << " , " << a[3][5] << " , " << a[3][6] << " , " << a[3][7] << " , " << a[3][8] << " } , { " << a[4][0] << " , " << a[4][1] << " , " << a[4][2] << " , " << a[4][3] << " , " << a[4][4] << " , " << a[4][5] << " , " << a[4][6] << " , " << a[4][7] << " , " << a[4][8] << " } , { " << a[5][0] << " , " << a[5][1] << " , " << a[5][2] << " , " << a[5][3] << " , " << a[5][4] << " , " << a[5][5] << " , " << a[5][6] << " , " << a[5][7] << " , " << a[5][8] << " } , { " << a[6][0] << " , " << a[6][1] << " , " << a[6][2] << " , " << a[6][3] << " , " << a[6][4] << " , " << a[6][5] << " , " << a[6][6] << " , " << a[6][7] << " , " << a[6][8] << " } , { "<< a[7][0] << " , " << a[7][1] << " , " << a[7][2] << " , " << a[7][3] << " , " << a[7][4] << " , " << a[7][5] << " , " << a[7][6] << " , " << a[7][7] << " , " << a[7][8] << " } , { "<< a[8][0] << " , " << a[8][1] << " , " << a[8][2] << " , " << a[8][3] << " , " << a[8][4] << " , " << a[8][5] << " , " << a[8][6] << " , " << a[8][7] << " , " << a[8][8] << " } }; " << endl; + math::ludcmp<double>(a,indx,d); + math::lubksb<double>(a,indx,b); + + for(int i = 0; i < 9; i++) + { + matrix[i] = b[i]; + } + return matrix; +} + +double* KisPerspectiveMath::computeMatrixTransfoToPerspective(const KisPoint& topLeft, const KisPoint& topRight, const KisPoint& bottomLeft, const KisPoint& bottomRight, const QRect& r) +{ + return KisPerspectiveMath::computeMatrixTransfo(topLeft, topRight, bottomLeft, bottomRight, r.topLeft(), r.topRight(), r.bottomLeft(), r.bottomRight()); +} + +double* KisPerspectiveMath::computeMatrixTransfoFromPerspective(const QRect& r, const KisPoint& topLeft, const KisPoint& topRight, const KisPoint& bottomLeft, const KisPoint& bottomRight) +{ + return KisPerspectiveMath::computeMatrixTransfo(r.topLeft(), r.topRight(), r.bottomLeft(), r.bottomRight(), topLeft, topRight, bottomLeft, bottomRight); +} + diff --git a/krita/core/kis_perspective_math.h b/krita/core/kis_perspective_math.h new file mode 100644 index 00000000..047e571e --- /dev/null +++ b/krita/core/kis_perspective_math.h @@ -0,0 +1,70 @@ +/* + * This file is part of Krita + * + * Copyright (c) 2006 Cyrille Berger <cberger@cberger.net> + * + * 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; version 2 of the License. + * + * 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_PERSPECTVE_MATH_H_ +#define _KIS_PERSPECTVE_MATH_H_ + +#include "kis_point.h" + +class QRect; + +class KisPerspectiveMath { + private: + KisPerspectiveMath() { } + public: + static double* computeMatrixTransfo( const KisPoint& topLeft1, const KisPoint& topRight1, const KisPoint& bottomLeft1, const KisPoint& bottomRight1 , const KisPoint& topLeft2, const KisPoint& topRight2, const KisPoint& bottomLeft2, const KisPoint& bottomRight2); + static double* computeMatrixTransfoToPerspective(const KisPoint& topLeft, const KisPoint& topRight, const KisPoint& bottomLeft, const KisPoint& bottomRight, const QRect& r); + static double* computeMatrixTransfoFromPerspective(const QRect& r, const KisPoint& topLeft, const KisPoint& topRight, const KisPoint& bottomLeft, const KisPoint& bottomRight); + struct LineEquation { + // y = a*x + b + double a, b; + }; + /// TODO: get ride of this in 2.0 + inline static KisPoint matProd(const double (&m)[3][3], const KisPoint& p) + { + double s = ( p.x() * m[2][0] + p.y() * m[2][1] + 1.0); + s = (s == 0.) ? 1. : 1./s; + return KisPoint( (p.x() * m[0][0] + p.y() * m[0][1] + m[0][2] ) * s, + (p.x() * m[1][0] + p.y() * m[1][1] + m[1][2] ) * s ); + } + static inline LineEquation computeLineEquation(const KisPoint* p1, const KisPoint* p2) + { + LineEquation eq; + double x1 = p1->x(); double x2 = p2->x(); + if( fabs(x1 - x2) < 0.000001 ) + { + x1 += 0.0001; // Introduce a small perturbation + } + eq.a = (p2->y() - p1->y()) / (double)( x2 - x1 ); + eq.b = -eq.a * x1 + p1->y(); + return eq; + } + static inline KisPoint computeIntersection(const LineEquation& d1, const LineEquation& d2) + { + double a1 = d1.a; double a2 = d2.a; + if( fabs(a1 - a2) < 0.000001 ) + { + a1 += 0.0001; // Introduce a small perturbation + } + double x = (d1.b - d2.b) / (a2 - a1); + return KisPoint(x, a2 * x + d2.b); + } +}; + +#endif diff --git a/krita/core/kis_perspectivetransform_worker.cpp b/krita/core/kis_perspectivetransform_worker.cpp new file mode 100644 index 00000000..bb98b3bf --- /dev/null +++ b/krita/core/kis_perspectivetransform_worker.cpp @@ -0,0 +1,121 @@ +/* + * This file is part of Krita + * + * Copyright (c) 2006 Cyrille Berger <cberger@cberger.net> + * + * 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; version 2 of the License. + * + * 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_perspectivetransform_worker.h" + +#include "kis_iterators_pixel.h" +#include "kis_paint_device.h" +#include "kis_perspective_math.h" +#include "kis_progress_display_interface.h" +#include "kis_random_sub_accessor.h" +#include "kis_selection.h" + +KisPerspectiveTransformWorker::KisPerspectiveTransformWorker(KisPaintDeviceSP dev, const KisPoint& topLeft, const KisPoint& topRight, const KisPoint& bottomLeft, const KisPoint& bottomRight, KisProgressDisplayInterface *progress) + : KisProgressSubject(), m_dev(dev), m_cancelRequested(false), m_progress(progress) + +{ + QRect m_r; + if(m_dev->hasSelection()) + m_r = m_dev->selection()->selectedExactRect(); + else + m_r = m_dev->exactBounds(); +/* if(m_dev->hasSelection()) + m_dev->selection()->clear();*/ + kdDebug() << "r = " << m_r << endl; + + double* b = KisPerspectiveMath::computeMatrixTransfoToPerspective(topLeft, topRight, bottomLeft, bottomRight, m_r); + for(int i = 0; i < 3; i++) + { + for(int j = 0; j < 3; j++) + { + kdDebug() << "sol[" << 3*i+j << "]=" << b[3*i+j] << endl; + m_matrix[i][j] = b[3*i+j]; + } + } + delete b; +} + + +KisPerspectiveTransformWorker::~KisPerspectiveTransformWorker() +{ +} + +double norm2(const KisPoint& p) +{ + return sqrt(p.x() * p.x() + p.y() * p.y() ); +} + +void KisPerspectiveTransformWorker::run() +{ + kdDebug() << "r = " << m_r << endl; + + //TODO: understand why my caching of the rect didn't work... + if(m_dev->hasSelection()) + { + m_r = m_dev->selection()->selectedExactRect(); + } + else + { + m_r = m_dev->exactBounds(); + } +// KisColorSpace * cs = m_dev->colorSpace(); + + kdDebug() << "r = " << m_r << endl; + KisRectIteratorPixel dstIt = m_dev->createRectIterator(m_r.x(), m_r.y(), m_r.width(), m_r.height(), true); + KisPaintDeviceSP srcdev = new KisPaintDevice(*m_dev.data()); + { // ensure that the random sub accessor is deleted first + KisRandomSubAccessorPixel srcAcc = srcdev->createRandomSubAccessor(); + // Initialise progress + if(m_progress) + m_progress->setSubject(this, true, true); + m_lastProgressReport = 0; + m_progressStep = 0; + m_progressTotalSteps = m_r.width() * m_r.height(); + //Action + while(!dstIt.isDone()) + { + if(dstIt.isSelected()) + { + KisPoint p; + double sf = ( dstIt.x() * m_matrix[2][0] + dstIt.y() * m_matrix[2][1] + 1.0); + sf = (sf == 0.) ? 1. : 1./sf; + p.setX( ( dstIt.x() * m_matrix[0][0] + dstIt.y() * m_matrix[0][1] + m_matrix[0][2] ) * sf ); + p.setY( ( dstIt.x() * m_matrix[1][0] + dstIt.y() * m_matrix[1][1] + m_matrix[1][2] ) * sf ); + + srcAcc.moveTo( p ); + srcAcc.sampledOldRawData( dstIt.rawData() ); + + // TODO: Should set alpha = alpha*(1-selectedness) +// cs->setAlpha( dstIt.rawData(), 255, 1); + } else { +// cs->setAlpha( dstIt.rawData(), 0, 1); + } + m_progressStep ++; + if(m_lastProgressReport != (m_progressStep * 100) / m_progressTotalSteps) + { + m_lastProgressReport = (m_progressStep * 100) / m_progressTotalSteps; + emit notifyProgress(m_lastProgressReport); + } + if (m_cancelRequested) { + break; + } + ++dstIt; + } + } +} diff --git a/krita/core/kis_perspectivetransform_worker.h b/krita/core/kis_perspectivetransform_worker.h new file mode 100644 index 00000000..2f53625f --- /dev/null +++ b/krita/core/kis_perspectivetransform_worker.h @@ -0,0 +1,52 @@ +/* + * This file is part of Krita + * + * Copyright (c) 2006 Cyrille Berger <cberger@cberger.net> + * + * 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; version 2 of the License. + * + * 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_PERSPECTIVETRANSFORM_WORKER_H +#define KIS_PERSPECTIVETRANSFORM_WORKER_H + +#include "kis_types.h" +#include "kis_progress_subject.h" + +class KisPoint; +class KisProgressDisplayInterface; + +class KisPerspectiveTransformWorker : public KisProgressSubject +{ + public: + KisPerspectiveTransformWorker(KisPaintDeviceSP dev, const KisPoint& topLeft, const KisPoint& topRight, const KisPoint& bottomLeft, const KisPoint& bottomRight, KisProgressDisplayInterface *progress); + + ~KisPerspectiveTransformWorker(); + + void run(); + bool isCanceled() { return m_cancelRequested; }; + private: + virtual void cancel() { m_cancelRequested = true; } + private: + Q_INT32 m_progressTotalSteps; + Q_INT32 m_lastProgressReport; + Q_INT32 m_progressStep; + double m_xcenter, m_ycenter, m_p, m_q; + KisPaintDeviceSP m_dev; + bool m_cancelRequested; + KisProgressDisplayInterface *m_progress; + double m_matrix[3][3]; + QRect m_r; +}; + +#endif diff --git a/krita/core/kis_point.h b/krita/core/kis_point.h new file mode 100644 index 00000000..ee5dba78 --- /dev/null +++ b/krita/core/kis_point.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2004 Adrian Page <adrian@pagenet.plus.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_POINT_H_ +#define KIS_POINT_H_ + +#include <qvaluevector.h> +#include <KoPoint.h> + +/** + * A double-based point class that can return it's coordinates + * approximated to integers. + */ +class KisPoint : public KoPoint { + typedef KoPoint super; +public: + KisPoint() {} + KisPoint(double x, double y) : super(x, y) {} + KisPoint(const QPoint& pt) : super(pt) {} + KisPoint(const KoPoint& pt) : super(pt) {} + + int floorX() const { return static_cast<int>(x()); } + int floorY() const { return static_cast<int>(y()); } + int roundX() const { return qRound(x()); } + int roundY() const { return qRound(y()); } + + QPoint floorQPoint() const { return QPoint(static_cast<int>(x()), static_cast<int>(y())); } + QPoint roundQPoint() const { return QPoint(qRound(x()), qRound(y())); } +}; + +typedef QValueVector<KisPoint> vKisPoint; + +#endif // KIS_POINT_H_ + diff --git a/krita/core/kis_random_accessor.cpp b/krita/core/kis_random_accessor.cpp new file mode 100644 index 00000000..3d538453 --- /dev/null +++ b/krita/core/kis_random_accessor.cpp @@ -0,0 +1,58 @@ +/* + * This file is part of the Krita project + * + * Copyright (c) 2006 Cyrille Berger <cberger@cberger.net> + * + * 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; version 2 of the License. + * + * 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_random_accessor.h" + +#include "kis_tiled_random_accessor.h" + +KisRandomAccessor::KisRandomAccessor(KisTiledDataManager *ktm, Q_INT32 x, Q_INT32 y, Q_INT32 offsetx, Q_INT32 offsety, bool writable) : m_offsetx(offsetx), m_offsety(offsety) +{ + m_accessor = new KisTiledRandomAccessor(ktm, x, y, writable); +} + +KisRandomAccessor::KisRandomAccessor(const KisRandomAccessor& rhs) { + m_accessor = rhs.m_accessor; +} + +KisRandomAccessor::~KisRandomAccessor() +{ + +} + +void KisRandomAccessor::moveTo(Q_INT32 x, Q_INT32 y) +{ + m_accessor->moveTo(x - m_offsetx, y - m_offsety); +} + +Q_UINT8* KisRandomAccessor::rawData() const +{ + return m_accessor->rawData(); +} + +const Q_UINT8* KisRandomAccessor::oldRawData() const +{ + return m_accessor->oldRawData(); +} + +KisRandomAccessorPixel::KisRandomAccessorPixel(KisTiledDataManager *ktm, KisTiledDataManager *ktmselect, Q_INT32 x, Q_INT32 y, Q_INT32 offsetx, Q_INT32 offsety, bool writable) : + KisRandomAccessor( ktm, x, y, offsetx, offsety, writable), + KisRandomAccessorPixelTrait( this, (ktmselect) ? new KisRandomAccessor(ktm, x, y, offsetx, offsety, false) : 0 ) +{ + +} diff --git a/krita/core/kis_random_accessor.h b/krita/core/kis_random_accessor.h new file mode 100644 index 00000000..dddd94b6 --- /dev/null +++ b/krita/core/kis_random_accessor.h @@ -0,0 +1,95 @@ +/* + * This file is part of the Krita project + * + * Copyright (c) 2006 Cyrille Berger <cberger@cberger.net> + * + * 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; version 2 of the License. + * + * 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_RANDOM_ACCESSOR_H +#define KIS_RANDOM_ACCESSOR_H + +#include <ksharedptr.h> + +#include <kis_global.h> + +class KisTiledRandomAccessor; +typedef KSharedPtr<KisTiledRandomAccessor> KisTiledRandomAccessorSP; + +class KisTiledDataManager; + +class KisRandomAccessor{ + public: + KisRandomAccessor(KisTiledDataManager *ktm, Q_INT32 x, Q_INT32 y, Q_INT32 offsetx, Q_INT32 offsety, bool writable); + KisRandomAccessor(const KisRandomAccessor& rhs); + ~KisRandomAccessor(); + public: + /// Move to a given x,y position, fetch tiles and data + void moveTo(Q_INT32 x, Q_INT32 y); + Q_UINT8* rawData() const; + const Q_UINT8* oldRawData() const; + private: + KisTiledRandomAccessorSP m_accessor; + Q_INT32 m_offsetx, m_offsety; +}; + +class KisRandomAccessorPixelTrait { + public: + inline KisRandomAccessorPixelTrait(KisRandomAccessor* underlyingAccessor, KisRandomAccessor* selectionAccessor) : m_underlyingAccessor(underlyingAccessor), m_selectionAccessor(selectionAccessor) + { + } + ~KisRandomAccessorPixelTrait() { + if(m_selectionAccessor) + delete m_selectionAccessor; + } + inline bool isSelected() const + { + return (m_selectionAccessor) ? *(m_selectionAccessor->rawData()) > SELECTION_THRESHOLD : true; + }; + inline Q_UINT8 operator[](int index) const + { return m_underlyingAccessor->rawData()[index]; }; + /** + * Returns the degree of selectedness of the pixel. + */ + inline Q_UINT8 selectedness() const + { + return (m_selectionAccessor) ? *(m_selectionAccessor->rawData()) : MAX_SELECTED; + }; + + /** + * Returns the selectionmask from the current point; this is guaranteed + * to have the same number of consecutive pixels that the iterator has + * at a given point. It return a 0 if there is no selection. + */ + inline Q_UINT8 * selectionMask() const + { + return ( m_selectionAccessor ) ? m_selectionAccessor->rawData() : 0; + } + + inline void moveTo(Q_INT32 x, Q_INT32 y) { if(m_selectionAccessor) m_selectionAccessor->moveTo(x,y); } + + private: + KisRandomAccessor* m_underlyingAccessor; + KisRandomAccessor* m_selectionAccessor; +}; + +class KisRandomAccessorPixel : public KisRandomAccessor, public KisRandomAccessorPixelTrait { + public: + KisRandomAccessorPixel(KisTiledDataManager *ktm, KisTiledDataManager *ktmselect, Q_INT32 x, Q_INT32 y, Q_INT32 offsetx, Q_INT32 offsety, bool writable); + public: + inline void moveTo(Q_INT32 x, Q_INT32 y) { KisRandomAccessor::moveTo(x,y); KisRandomAccessorPixelTrait::moveTo(x,y); } +}; + + +#endif diff --git a/krita/core/kis_random_sub_accessor.cpp b/krita/core/kis_random_sub_accessor.cpp new file mode 100644 index 00000000..3b63f340 --- /dev/null +++ b/krita/core/kis_random_sub_accessor.cpp @@ -0,0 +1,84 @@ +/* + * This file is part of the KDE project + * + * Copyright (c) 2006 Cyrille Berger <cberger@cberger.net> + * + * 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; version 2 of the License. + * + * 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_random_sub_accessor.h" + +#include "kis_paint_device.h" + +KisRandomSubAccessorPixel::KisRandomSubAccessorPixel(KisPaintDeviceSP device) : + m_device(device), m_currentPoint( 0, 0 ), m_randomAccessor(device->createRandomAccessor(0,0, false)) +{ +} + + +KisRandomSubAccessorPixel::~KisRandomSubAccessorPixel() +{ +} + + +void KisRandomSubAccessorPixel::sampledOldRawData(Q_UINT8* dst) +{ + const Q_UINT8* pixels[4]; + Q_UINT8 weights[4]; + int x = (int)floor(m_currentPoint.x()); + int y = (int)floor(m_currentPoint.y()); + double hsub = m_currentPoint.x() - x; + if(hsub < 0.0 ) hsub = 1.0 + hsub; + double vsub = m_currentPoint.y() - y; + if(vsub < 0.0 ) vsub = 1.0 + vsub; + weights[0] = (int)qRound( ( 1.0 - hsub) * ( 1.0 - vsub) * 255 ); + m_randomAccessor.moveTo(x, y); + pixels[0] = m_randomAccessor.oldRawData(); + weights[1] = (int)qRound( ( 1.0 - vsub) * hsub * 255 ); + m_randomAccessor.moveTo(x+1, y); + pixels[1] = m_randomAccessor.oldRawData(); + weights[2] = (int)qRound( vsub * ( 1.0 - hsub) * 255 ); + m_randomAccessor.moveTo(x, y+1); + pixels[2] = m_randomAccessor.oldRawData(); + weights[3] = (int)qRound( hsub * vsub * 255 ); + m_randomAccessor.moveTo(x+1, y+1); + pixels[3] = m_randomAccessor.oldRawData(); + m_device->colorSpace()->mixColors(pixels, weights, 4, dst); +} + +void KisRandomSubAccessorPixel::sampledRawData(Q_UINT8* dst) +{ + const Q_UINT8* pixels[4]; + Q_UINT8 weights[4]; + int x = (int)floor(m_currentPoint.x()); + int y = (int)floor(m_currentPoint.y()); + double hsub = m_currentPoint.x() - x; + if(hsub < 0.0 ) hsub = 1.0 + hsub; + double vsub = m_currentPoint.y() - y; + if(vsub < 0.0 ) vsub = 1.0 + vsub; + weights[0] = (int)qRound( ( 1.0 - hsub) * ( 1.0 - vsub) * 255 ); + m_randomAccessor.moveTo(x, y); + pixels[0] = m_randomAccessor.rawData(); + weights[1] = (int)qRound( ( 1.0 - vsub) * hsub * 255 ); + m_randomAccessor.moveTo(x+1, y); + pixels[1] = m_randomAccessor.rawData(); + weights[2] = (int)qRound( vsub * ( 1.0 - hsub) * 255 ); + m_randomAccessor.moveTo(x, y+1); + pixels[2] = m_randomAccessor.rawData(); + weights[3] = (int)qRound( hsub * vsub * 255 ); + m_randomAccessor.moveTo(x+1, y+1); + pixels[3] = m_randomAccessor.rawData(); + m_device->colorSpace()->mixColors(pixels, weights, 4, dst); +} + diff --git a/krita/core/kis_random_sub_accessor.h b/krita/core/kis_random_sub_accessor.h new file mode 100644 index 00000000..7beb7945 --- /dev/null +++ b/krita/core/kis_random_sub_accessor.h @@ -0,0 +1,45 @@ +/* + * This file is part of the KDE project + * + * Copyright (c) 2006 Cyrille Berger <cberger@cberger.net> + * + * 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; version 2 of the License. + * + * 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_CURVE_ITERATOR_H +#define KIS_CURVE_ITERATOR_H + +#include "kis_point.h" +#include "kis_random_accessor.h" +#include "kis_types.h" + +class KisRandomSubAccessorPixel{ + public: + KisRandomSubAccessorPixel(KisPaintDeviceSP device); + ~KisRandomSubAccessorPixel(); + /** + * Copy the sampled old value to destination + */ + void sampledOldRawData(Q_UINT8* dst); + void sampledRawData(Q_UINT8* dst); + inline void moveTo(double x, double y) { m_currentPoint.setX(x); m_currentPoint.setY(y); } + inline void moveTo(const KisPoint& p ) { m_currentPoint = p; } + private: + KisPaintDeviceSP m_device; + int m_position, m_end; + KisPoint m_currentPoint; + KisRandomAccessorPixel m_randomAccessor; +}; + +#endif diff --git a/krita/core/kis_rect.cc b/krita/core/kis_rect.cc new file mode 100644 index 00000000..892a5e32 --- /dev/null +++ b/krita/core/kis_rect.cc @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2004 Adrian Page <adrian@pagenet.plus.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 <cmath> +#include <cfloat> + +#include "kis_rect.h" + +QRect KisRect::qRect() const +{ + return QRect(static_cast<int>(floor(left())), static_cast<int>(floor(top())), static_cast<int>(ceil(right()) - floor(left())), static_cast<int>(ceil(bottom()) - floor(top()))); +} + diff --git a/krita/core/kis_rect.h b/krita/core/kis_rect.h new file mode 100644 index 00000000..268b64eb --- /dev/null +++ b/krita/core/kis_rect.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2004 Adrian Page <adrian@pagenet.plus.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_RECT_H_ +#define KIS_RECT_H_ + +#include <qrect.h> +#include <KoRect.h> +#include "kis_point.h" + +/** + * A double-based rect class that can return a QRect that encloses the KisRect. + */ +class KisRect : public KoRect +{ + typedef KoRect super; +public: + KisRect() {} + KisRect(double x, double y, double w, double h) : super(x, y, w, h) {} + KisRect(const KisPoint& topLeft, const KisPoint& bottomRight) : super(topLeft, bottomRight) {} + KisRect(const QRect& qr) : super(qr.x(), qr.y(), qr.width(), qr.height()) {} + KisRect(const KoRect& r) : super(r) {} + + /** + * Return the QRect that encloses this KisRect. + */ + QRect qRect() const; + +private: + // Use qRect() which uses ceil() and floor() to return a rectangle + // 'enclosing' the rectangle, whereas toQRect rounds the points. + QRect toQRect() const; +}; + +#endif // KIS_RECT_H_ + diff --git a/krita/core/kis_resource.cc b/krita/core/kis_resource.cc new file mode 100644 index 00000000..256ffae5 --- /dev/null +++ b/krita/core/kis_resource.cc @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2003 Patrick Julien <freak@codepimps.org> + * + * 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_resource.h" +#include "kis_global.h" + +KisResource::KisResource(const QString& filename) +{ + m_filename = filename; + m_valid = false; +} + +KisResource::~KisResource() +{ +} + +QString KisResource::filename() const +{ + return m_filename; +} + +void KisResource::setFilename(const QString& filename) +{ + m_filename = filename; +} + +QString KisResource::name() const +{ + return m_name; +} + +void KisResource::setName(const QString& name) +{ + m_name = name; +} + +bool KisResource::valid() const +{ + return m_valid; +} + +void KisResource::setValid(bool valid) +{ + m_valid = valid; +} + +#include "kis_resource.moc" + diff --git a/krita/core/kis_resource.h b/krita/core/kis_resource.h new file mode 100644 index 00000000..d798d3cd --- /dev/null +++ b/krita/core/kis_resource.h @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2003 Patrick Julien <freak@codepimps.org> + * + * 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_RESOURCE_H_ +#define KIS_RESOURCE_H_ + +#include <qimage.h> +#include <qobject.h> +#include <qstring.h> + + +/** + * The KisResource class provides a representation of Krita image resources. This + * includes, but not limited to, brushes and patterns. + * + * This replaces the KisKrayon facility that used to be present in Krayon. + */ +class KisResource : public QObject { + typedef QObject super; + Q_OBJECT + +public: + + /** + * Creates a new KisResource object using @p filename. No file is opened + * in the constructor, you have to call load. + * + * @param filename the file name to save and load from. + */ + KisResource(const QString& filename); + virtual ~KisResource(); + +public: + /** + * Load this resource. + */ + virtual bool load() = 0; + + /** + * Save this resource asynchronously. The signal saveComplete is emitted when + * the resource has been saved. + */ + virtual bool save() = 0; + + /** + * Returns a QImage representing this resource. This image could be null. + */ + virtual QImage img() = 0; + +public: + QString filename() const; + void setFilename(const QString& filename); + QString name() const; + void setName(const QString& name); + bool valid() const; + void setValid(bool valid); + +private: + KisResource(const KisResource&); + KisResource& operator=(const KisResource&); + +private: + QString m_name; + QString m_filename; + bool m_valid; +}; + +#endif // KIS_RESOURCE_H_ + diff --git a/krita/core/kis_rotate_visitor.cc b/krita/core/kis_rotate_visitor.cc new file mode 100644 index 00000000..4e703633 --- /dev/null +++ b/krita/core/kis_rotate_visitor.cc @@ -0,0 +1,406 @@ +/* + * Copyright (c) 2004 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 <math.h> +#include <qapplication.h> +#include <qwmatrix.h> +#include <qrect.h> + +#include <kdebug.h> +#include <klocale.h> + +#include "kis_paint_device.h" +#include "kis_rotate_visitor.h" +#include "kis_progress_display_interface.h" +#include "kis_iterators_pixel.h" +#include "kis_selection.h" +#include "kis_painter.h" + +void KisRotateVisitor::rotate(double angle, bool rotateAboutImageCentre, KisProgressDisplayInterface *progress) +{ + KisPoint centreOfRotation; + + if (rotateAboutImageCentre) { + centreOfRotation = KisPoint(m_dev->image()->width() / 2.0, m_dev->image()->height() / 2.0); + } else { + QRect r = m_dev->exactBounds(); + centreOfRotation = KisPoint(r.x() + (r.width() / 2.0), r.y() + (r.height() / 2.0)); + } + + m_progress = progress; + + KisPaintDeviceSP rotated = rotate(m_dev, angle, centreOfRotation); + + if (!m_dev->hasSelection()) { + // Clear everything + m_dev->clear(); + } else { + // Clear selected pixels + m_dev->clearSelection(); + } + + KisPainter p(m_dev); + QRect r = rotated->extent(); + + // OVER ipv COPY + p.bitBlt(r.x(), r.y(), COMPOSITE_OVER, rotated, OPACITY_OPAQUE, r.x(), r.y(), r.width(), r.height()); + p.end(); +} + +void KisRotateVisitor::shear(double angleX, double angleY, KisProgressDisplayInterface *progress) +{ + const double pi=3.1415926535897932385; + double thetaX = angleX * pi / 180; + double shearX = tan(thetaX); + double thetaY = angleY * pi / 180; + double shearY = tan(thetaY); + + QRect r = m_dev->exactBounds(); + + const int xShearSteps = r.height(); + const int yShearSteps = r.width(); + + m_progress = progress; + initProgress(xShearSteps + yShearSteps); + + + KisPaintDeviceSP sheared; + + if (m_dev->hasSelection()) { + sheared = new KisPaintDevice(m_dev->colorSpace(), "sheared"); + KisPainter p1(sheared); + p1.bltSelection(r.x(), r.y(), COMPOSITE_OVER, m_dev, OPACITY_OPAQUE, r.x(), r.y(), r.width(), r.height()); + p1.end(); + sheared = xShear(sheared, shearX); + } + else { + sheared = xShear(m_dev, shearX); + } + + sheared = yShear(sheared, shearY); + + if (!m_dev->hasSelection()) { + m_dev->clear(); + } else { + // Clear selected pixels + m_dev->clearSelection(); + } + + KisPainter p2(m_dev); + r = sheared->extent(); + + p2.bitBlt(r.x(), r.y(), COMPOSITE_OVER, sheared, OPACITY_OPAQUE, r.x(), r.y(), r.width(), r.height()); + p2.end(); + + setProgressDone(); +} + +KisPaintDeviceSP KisRotateVisitor::rotateRight90(KisPaintDeviceSP src) +{ + KisPaintDeviceSP dst = new KisPaintDevice(src->colorSpace(), "rotateright90"); + dst->setX(src->getX()); + dst->setY(src->getY()); + + Q_INT32 pixelSize = src->pixelSize(); + QRect r = src->exactBounds(); + Q_INT32 x = 0; + + for (Q_INT32 y = r.bottom(); y >= r.top(); --y) { + KisHLineIteratorPixel hit = src->createHLineIterator(r.x(), y, r.width(), false); + KisVLineIterator vit = dst->createVLineIterator(-y, r.x(), r.width(), true); + + while (!hit.isDone()) { + if (hit.isSelected()) { + memcpy(vit.rawData(), hit.rawData(), pixelSize); + } + ++hit; + ++vit; + } + ++x; + incrementProgress(); + } + + return dst; +} + +KisPaintDeviceSP KisRotateVisitor::rotateLeft90(KisPaintDeviceSP src) +{ + KisPaintDeviceSP dst = new KisPaintDevice(src->colorSpace(), "rotateleft90"); + + Q_INT32 pixelSize = src->pixelSize(); + QRect r = src->exactBounds(); + Q_INT32 x = 0; + + for (Q_INT32 y = r.top(); y <= r.bottom(); ++y) { + // Read the horizontal line from back to front, write onto the vertical column + KisHLineIteratorPixel hit = src->createHLineIterator(r.x(), y, r.width(), false); + KisVLineIterator vit = dst->createVLineIterator(y, -r.x() - r.width(), r.width(), true); + + hit += r.width() - 1; + while (!vit.isDone()) { + if (hit.isSelected()) { + memcpy(vit.rawData(), hit.rawData(), pixelSize); + } + --hit; + ++vit; + } + ++x; + incrementProgress(); + } + + return dst; +} + +KisPaintDeviceSP KisRotateVisitor::rotate180(KisPaintDeviceSP src) +{ + KisPaintDeviceSP dst = new KisPaintDevice(src->colorSpace(), "rotate180"); + dst->setX(src->getX()); + dst->setY(src->getY()); + + Q_INT32 pixelSize = src->pixelSize(); + QRect r = src->exactBounds(); + + for (Q_INT32 y = r.top(); y <= r.bottom(); ++y) { + KisHLineIteratorPixel srcIt = src->createHLineIterator(r.x(), y, r.width(), false); + KisHLineIterator dstIt = dst->createHLineIterator( -r.x() - r.width(), -y, r.width(), true); + + srcIt += r.width() - 1; + while (!dstIt.isDone()) { + if (srcIt.isSelected()) { + memcpy(dstIt.rawData(), srcIt.rawData(), pixelSize); + } + --srcIt; + ++dstIt; + } + incrementProgress(); + } + + return dst; +} + +KisPaintDeviceSP KisRotateVisitor::rotate(KisPaintDeviceSP src, double angle, KisPoint centreOfRotation) +{ + const double pi = 3.1415926535897932385; + + if (angle >= 315 && angle < 360) { + angle = angle - 360; + } else if (angle > -360 && angle < -45) { + angle = angle + 360; + } + + QRect r = src->exactBounds(); + + const int xShearSteps = r.height(); + const int yShearSteps = r.width(); + const int fixedRotateSteps = r.height(); + + KisPaintDeviceSP dst; + + if (angle == 90) { + initProgress(fixedRotateSteps); + dst = rotateRight90(src); + } else if (angle == 180) { + initProgress(fixedRotateSteps); + dst = rotate180(src); + } else if (angle == 270) { + initProgress(fixedRotateSteps); + dst = rotateLeft90(src); + } else { + double theta; + + if (angle >= -45 && angle < 45) { + + theta = angle * pi / 180; + dst = src; + initProgress(yShearSteps + (2 * xShearSteps)); + } + else if (angle >= 45 && angle < 135) { + + initProgress(fixedRotateSteps + yShearSteps + (2 * xShearSteps)); + dst = rotateRight90(src); + theta = (angle - 90) * pi / 180; + } + else if (angle >= 135 && angle < 225) { + + initProgress(fixedRotateSteps + yShearSteps + (2 * xShearSteps)); + dst = rotate180(src); + theta = (angle - 180) * pi / 180; + + } else { + + initProgress(fixedRotateSteps + yShearSteps + (2 * xShearSteps)); + dst = rotateLeft90(src); + theta = (angle - 270) * pi / 180; + } + + double shearX = tan(theta / 2); + double shearY = sin(theta); + + //first perform a shear along the x-axis by tan(theta/2) + dst = xShear(dst, shearX); + //next perform a shear along the y-axis by sin(theta) + dst = yShear(dst, shearY); + //again perform a shear along the x-axis by tan(theta/2) + dst = xShear(dst, shearX); + } + + double sinAngle = sin(angle * pi / 180); + double cosAngle = cos(angle * pi / 180); + + KisPoint rotatedCentreOfRotation( + centreOfRotation.x() * cosAngle - centreOfRotation.y() * sinAngle, + centreOfRotation.x() * sinAngle + centreOfRotation.y() * cosAngle); + + dst->setX((Q_INT32)(dst->getX() + centreOfRotation.x() - rotatedCentreOfRotation.x())); + dst->setY((Q_INT32)(dst->getY() + centreOfRotation.y() - rotatedCentreOfRotation.y())); + + setProgressDone(); + + return dst; +} + +KisPaintDeviceSP KisRotateVisitor::xShear(KisPaintDeviceSP src, double shearX) +{ + KisPaintDeviceSP dst = new KisPaintDevice(src->colorSpace(), "xShear"); + dst->setX(src->getX()); + dst->setY(src->getY()); + + QRect r = src->exactBounds(); + + double displacement; + Q_INT32 displacementInt; + double weight; + + for (Q_INT32 y = r.top(); y <= r.bottom(); y++) { + + //calculate displacement + displacement = -y * shearX; + + displacementInt = (Q_INT32)(floor(displacement)); + weight = displacement - displacementInt; + + Q_UINT8 pixelWeights[2]; + + pixelWeights[0] = static_cast<Q_UINT8>(weight * 255 + 0.5); + pixelWeights[1] = 255 - pixelWeights[0]; + + KisHLineIteratorPixel srcIt = src->createHLineIterator(r.x(), y, r.width() + 1, false); + KisHLineIteratorPixel leftSrcIt = src->createHLineIterator(r.x() - 1, y, r.width() + 1, false); + KisHLineIteratorPixel dstIt = dst->createHLineIterator(r.x() + displacementInt, y, r.width() + 1, true); + + while (!srcIt.isDone()) { + + const Q_UINT8 *pixelPtrs[2]; + + pixelPtrs[0] = leftSrcIt.rawData(); + pixelPtrs[1] = srcIt.rawData(); + + src->colorSpace()->mixColors(pixelPtrs, pixelWeights, 2, dstIt.rawData()); + + ++srcIt; + ++leftSrcIt; + ++dstIt; + } + incrementProgress(); + } + + return dst; +} + +KisPaintDeviceSP KisRotateVisitor::yShear(KisPaintDeviceSP src, double shearY) +{ + KisPaintDeviceSP dst = new KisPaintDevice(src->colorSpace(), "yShear"); + dst->setX(src->getX()); + dst->setY(src->getY()); + + QRect r = src->exactBounds(); + + double displacement; + Q_INT32 displacementInt; + double weight; + + for (Q_INT32 x = r.left(); x <= r.right(); x++) { + + //calculate displacement + displacement = x * shearY; + + displacementInt = (Q_INT32)(floor(displacement)); + weight = displacement - displacementInt; + + Q_UINT8 pixelWeights[2]; + + pixelWeights[0] = static_cast<Q_UINT8>(weight * 255 + 0.5); + pixelWeights[1] = 255 - pixelWeights[0]; + + KisVLineIteratorPixel srcIt = src->createVLineIterator(x, r.y(), r.height() + 1, false); + KisVLineIteratorPixel leftSrcIt = src->createVLineIterator(x, r.y() - 1, r.height() + 1, false); + KisVLineIteratorPixel dstIt = dst->createVLineIterator(x, r.y() + displacementInt, r.height() + 1, true); + + while (!srcIt.isDone()) { + + const Q_UINT8 *pixelPtrs[2]; + + pixelPtrs[0] = leftSrcIt.rawData(); + pixelPtrs[1] = srcIt.rawData(); + + src->colorSpace()->mixColors(pixelPtrs, pixelWeights, 2, dstIt.rawData()); + + ++srcIt; + ++leftSrcIt; + ++dstIt; + } + incrementProgress(); + } + + return dst; +} + +void KisRotateVisitor::initProgress(Q_INT32 totalSteps) +{ + if (!m_progress) return; + + m_progressTotalSteps = totalSteps; + m_progressStep = 0; + m_lastProgressPerCent = 0; + + + m_progress->setSubject(this, true, false); + emit notifyProgress(0); + +} + +void KisRotateVisitor::incrementProgress() +{ + if (!m_progress) return; + + m_progressStep++; + Q_INT32 progressPerCent = (m_progressStep * 100) / m_progressTotalSteps; + + if (progressPerCent != m_lastProgressPerCent) { + m_lastProgressPerCent = progressPerCent; + emit notifyProgress(progressPerCent); + } +} + +void KisRotateVisitor::setProgressDone() +{ + if (!m_progress) return; + + emit notifyProgressDone(); +} + + diff --git a/krita/core/kis_rotate_visitor.h b/krita/core/kis_rotate_visitor.h new file mode 100644 index 00000000..19db35f4 --- /dev/null +++ b/krita/core/kis_rotate_visitor.h @@ -0,0 +1,80 @@ +/* + * copyright (c) 2004 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_ROTATE_VISITOR_H_ +#define KIS_ROTATE_VISITOR_H_ + +#include "kis_types.h" +#include "kis_progress_subject.h" + +class QRect; +class KisPaintDevice; +class KisProgressDisplayInterface; + +class KisRotateVisitor : public KisProgressSubject { + typedef KisProgressSubject super; + + /* Structs for the image rescaling routine */ + +public: + KisRotateVisitor(); + ~KisRotateVisitor(); + + void visitKisPaintDevice(KisPaintDevice* dev); + + void rotate(double angle, bool rotateAboutImageCentre, KisProgressDisplayInterface *progress); + void shear(double angleX, double angleY, KisProgressDisplayInterface *progress); + +private: + KisPaintDeviceSP m_dev; + + // Implement KisProgressSubject + bool m_cancelRequested; + virtual void cancel() { m_cancelRequested = true; } + + void initProgress(Q_INT32 totalSteps); + void incrementProgress(); + void setProgressDone(); + + KisProgressDisplayInterface *m_progress; + Q_INT32 m_progressStep; + Q_INT32 m_progressTotalSteps; + Q_INT32 m_lastProgressPerCent; + + KisPaintDeviceSP rotateRight90(KisPaintDeviceSP src); + KisPaintDeviceSP rotateLeft90(KisPaintDeviceSP src); + KisPaintDeviceSP rotate180(KisPaintDeviceSP src); + KisPaintDeviceSP rotate(KisPaintDeviceSP src, double angle, KisPoint centreOfRotation); + + KisPaintDeviceSP xShear(KisPaintDeviceSP src, double shearX); + KisPaintDeviceSP yShear(KisPaintDeviceSP src, double shearY); + +}; + +inline KisRotateVisitor::KisRotateVisitor() +{ +} + +inline KisRotateVisitor::~KisRotateVisitor() +{ +} + +inline void KisRotateVisitor::visitKisPaintDevice(KisPaintDevice* dev) +{ + m_dev = dev; +} +#endif // KIS_ROTATE_VISITOR_H_ diff --git a/krita/core/kis_scale_visitor.cc b/krita/core/kis_scale_visitor.cc new file mode 100644 index 00000000..424db7c6 --- /dev/null +++ b/krita/core/kis_scale_visitor.cc @@ -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 */ diff --git a/krita/core/kis_scale_visitor.h b/krita/core/kis_scale_visitor.h new file mode 100644 index 00000000..e20ba2e0 --- /dev/null +++ b/krita/core/kis_scale_visitor.h @@ -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_ diff --git a/krita/core/kis_selected_transaction.cc b/krita/core/kis_selected_transaction.cc new file mode 100644 index 00000000..78430a84 --- /dev/null +++ b/krita/core/kis_selected_transaction.cc @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2002 Patrick Julien <freak@codepimps.org> + * + * 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_types.h" +#include "kis_global.h" +#include "kis_selected_transaction.h" +#include "kis_selection.h" + +KisSelectedTransaction::KisSelectedTransaction(const QString& name, KisPaintDeviceSP device) : + KisTransaction(name, device), + m_device(device), + m_hadSelection(device->hasSelection()) +{ + m_selTransaction = new KisTransaction(name, device->selection().data()); + if(! m_hadSelection) { + m_device->deselect(); // let us not be the cause of select + } +} + +KisSelectedTransaction::~KisSelectedTransaction() +{ + delete m_selTransaction; +} + +void KisSelectedTransaction::execute() +{ + super::execute(); + m_selTransaction->execute(); + if(m_redoHasSelection) + m_device->selection(); + else + m_device->deselect(); + m_device->emitSelectionChanged(); +} + +void KisSelectedTransaction::unexecute() +{ + m_redoHasSelection = m_device->hasSelection(); + + super::unexecute(); + m_selTransaction->unexecute(); + if(m_hadSelection) + m_device->selection(); + else + m_device->deselect(); + m_device->emitSelectionChanged(); +} + +void KisSelectedTransaction::unexecuteNoUpdate() +{ + m_redoHasSelection = m_device->hasSelection(); + + super::unexecuteNoUpdate(); + m_selTransaction->unexecuteNoUpdate(); + if(m_hadSelection) + m_device->selection(); + else + m_device->deselect(); +} diff --git a/krita/core/kis_selected_transaction.h b/krita/core/kis_selected_transaction.h new file mode 100644 index 00000000..7d1182c0 --- /dev/null +++ b/krita/core/kis_selected_transaction.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2005 Casper Boemann <cbr@boemann.dk> + * + * 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_SELECTED_TRANSACTION_H_ +#define KIS_SELECTED_TRANSACTION_H_ + +#include <map> +#include <qglobal.h> +#include <qstring.h> + +#include "kis_transaction.h" + +#include "koffice_export.h" + +class KRITACORE_EXPORT KisSelectedTransaction : public KisTransaction { + typedef KisTransaction super; +public: + KisSelectedTransaction(const QString& name, KisPaintDeviceSP device); + virtual ~KisSelectedTransaction(); + +public: + virtual void execute(); + virtual void unexecute(); + virtual void unexecuteNoUpdate(); + +public: + +private: + KisPaintDeviceSP m_device; + KisTransaction *m_selTransaction; + bool m_hadSelection; + bool m_redoHasSelection; +}; + +#endif // KIS_SELECTED_TRANSACTION_H_ diff --git a/krita/core/kis_selection.cc b/krita/core/kis_selection.cc new file mode 100644 index 00000000..9964a355 --- /dev/null +++ b/krita/core/kis_selection.cc @@ -0,0 +1,582 @@ +/* + * Copyright (c) 2004 Boudewijn Rempt <boud@valdyas.org> + * + * 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. + */ + +#include <qimage.h> + +#include <kdebug.h> +#include <klocale.h> +#include <qcolor.h> + +#include "kis_layer.h" +#include "kis_debug_areas.h" +#include "kis_types.h" +#include "kis_colorspace_factory_registry.h" +#include "kis_fill_painter.h" +#include "kis_iterators_pixel.h" +#include "kis_integer_maths.h" +#include "kis_image.h" +#include "kis_datamanager.h" +#include "kis_fill_painter.h" +#include "kis_selection.h" + +KisSelection::KisSelection(KisPaintDeviceSP dev) + : super(dev->parentLayer() + , KisMetaRegistry::instance()->csRegistry()->getAlpha8() + , (QString("selection for ") + dev->name()).latin1()) + , m_parentPaintDevice(dev) + , m_doCacheExactRect(false) + , m_dirty(false) +{ + Q_ASSERT(dev); +} + +KisSelection::KisSelection() + : super(KisMetaRegistry::instance()->csRegistry()->getAlpha8(), "anonymous selection") + , m_parentPaintDevice(0), m_dirty(false) +{ +} + +KisSelection::KisSelection(const KisSelection& rhs) + : super(rhs), m_parentPaintDevice(rhs.m_parentPaintDevice), m_doCacheExactRect(rhs.m_doCacheExactRect), + m_cachedExactRect(rhs.m_cachedExactRect), m_dirty(rhs.m_dirty) +{ +} + +KisSelection::~KisSelection() +{ +} + +Q_UINT8 KisSelection::selected(Q_INT32 x, Q_INT32 y) +{ + KisHLineIteratorPixel iter = createHLineIterator(x, y, 1, false); + + Q_UINT8 *pix = iter.rawData(); + + return *pix; +} + +void KisSelection::setSelected(Q_INT32 x, Q_INT32 y, Q_UINT8 s) +{ + KisHLineIteratorPixel iter = createHLineIterator(x, y, 1, true); + + Q_UINT8 *pix = iter.rawData(); + + *pix = s; +} + +QImage KisSelection::maskImage() +{ + // If part of a KisAdjustmentLayer, there may be no parent device. + QImage img; + QRect bounds; + if (m_parentPaintDevice) { + + bounds = m_parentPaintDevice->exactBounds(); + bounds = bounds.intersect( m_parentPaintDevice->image()->bounds() ); + img = QImage(bounds.width(), bounds.height(), 32); + } + else { + bounds = QRect( 0, 0, image()->width(), image()->height()); + img = QImage(bounds.width(), bounds.height(), 32); + } + + KisHLineIteratorPixel it = createHLineIterator(bounds.x(), bounds.y(), bounds.width(), false); + for (int y2 = bounds.y(); y2 < bounds.height() - bounds.y(); ++y2) { + int x2 = 0; + while (!it.isDone()) { + Q_UINT8 s = MAX_SELECTED - *(it.rawData()); + Q_INT32 c = qRgb(s, s, s); + img.setPixel(x2, y2, c); + ++x2; + ++it; + } + it.nextRow(); + } + return img; +} +void KisSelection::select(QRect r) +{ + KisFillPainter painter(this); + KisColorSpace * cs = KisMetaRegistry::instance()->csRegistry()->getRGB8(); + painter.fillRect(r, KisColor(Qt::white, cs), MAX_SELECTED); + Q_INT32 x, y, w, h; + extent(x, y, w, h); +} + +void KisSelection::clear(QRect r) +{ + KisFillPainter painter(this); + KisColorSpace * cs = KisMetaRegistry::instance()->csRegistry()->getRGB8(); + painter.fillRect(r, KisColor(Qt::white, cs), MIN_SELECTED); +} + +void KisSelection::clear() +{ + Q_UINT8 defPixel = MIN_SELECTED; + m_datamanager->setDefaultPixel(&defPixel); + m_datamanager->clear(); +} + +void KisSelection::invert() +{ + Q_INT32 x,y,w,h; + + extent(x, y, w, h); + KisRectIterator it = createRectIterator(x, y, w, h, true); + while ( ! it.isDone() ) + { + // CBR this is wrong only first byte is inverted + // BSAR: But we have always only one byte in this color model :-). + *(it.rawData()) = MAX_SELECTED - *(it.rawData()); + ++it; + } + Q_UINT8 defPixel = MAX_SELECTED - *(m_datamanager->defaultPixel()); + m_datamanager->setDefaultPixel(&defPixel); +} + +bool KisSelection::isTotallyUnselected(QRect r) +{ + if(*(m_datamanager->defaultPixel()) != MIN_SELECTED) + return false; + QRect sr = selectedExactRect(); + return ! r.intersects(sr); +} + +bool KisSelection::isProbablyTotallyUnselected(QRect r) +{ + if(*(m_datamanager->defaultPixel()) != MIN_SELECTED) + return false; + QRect sr = selectedRect(); + return ! r.intersects(sr); +} + + +QRect KisSelection::selectedRect() const +{ + if(*(m_datamanager->defaultPixel()) == MIN_SELECTED || !m_parentPaintDevice) + return extent(); + else + return extent().unite(m_parentPaintDevice->extent()); +} + +QRect KisSelection::selectedExactRect() const +{ + if(m_doCacheExactRect) + return m_cachedExactRect; + else if(*(m_datamanager->defaultPixel()) == MIN_SELECTED || !m_parentPaintDevice) + return exactBounds(); + else + return exactBounds().unite(m_parentPaintDevice->exactBounds()); +} + +void KisSelection::stopCachingExactRect() +{ + kdDebug() << "stop caching the exact rect" << endl; + m_doCacheExactRect = false; +} + + +void KisSelection::startCachingExactRect() +{ + kdDebug() << "start caching the exact rect" << endl; + if(*(m_datamanager->defaultPixel()) == MIN_SELECTED || !m_parentPaintDevice) + m_cachedExactRect = exactBounds(); + else + m_cachedExactRect = exactBounds().unite(m_parentPaintDevice->exactBounds()); + m_doCacheExactRect = true; +} + +void KisSelection::paintUniformSelectionRegion(QImage img, const QRect& imageRect, const QRegion& uniformRegion) +{ + Q_ASSERT(img.size() == imageRect.size()); + Q_ASSERT(imageRect.contains(uniformRegion.boundingRect())); + + if (img.isNull() || img.size() != imageRect.size() || !imageRect.contains(uniformRegion.boundingRect())) { + return; + } + + if (*m_datamanager->defaultPixel() == MIN_SELECTED) { + + QRegion region = uniformRegion & QRegion(imageRect); + + if (!region.isEmpty()) { + QMemArray<QRect> rects = region.rects(); + + for (unsigned int i = 0; i < rects.count(); i++) { + QRect r = rects[i]; + + for (Q_INT32 y = 0; y < r.height(); ++y) { + + QRgb *imagePixel = reinterpret_cast<QRgb *>(img.scanLine(r.y() - imageRect.y() + y)); + imagePixel += r.x() - imageRect.x(); + + Q_INT32 numPixels = r.width(); + + while (numPixels > 0) { + + QRgb srcPixel = *imagePixel; + Q_UINT8 srcGrey = (qRed(srcPixel) + qGreen(srcPixel) + qBlue(srcPixel)) / 9; + Q_UINT8 srcAlpha = qAlpha(srcPixel); + + srcGrey = UINT8_MULT(srcGrey, srcAlpha); + Q_UINT8 dstAlpha = QMAX(srcAlpha, 192); + + QRgb dstPixel = qRgba(128 + srcGrey, 128 + srcGrey, 165 + srcGrey, dstAlpha); + *imagePixel = dstPixel; + + ++imagePixel; + --numPixels; + } + } + } + } + } +} + +void KisSelection::paintSelection(QImage img, Q_INT32 imageRectX, Q_INT32 imageRectY, Q_INT32 imageRectWidth, Q_INT32 imageRectHeight) +{ + Q_ASSERT(img.size() == QSize(imageRectWidth, imageRectHeight)); + + if (img.isNull() || img.size() != QSize(imageRectWidth, imageRectHeight)) { + return; + } + + QRect imageRect(imageRectX, imageRectY, imageRectWidth, imageRectHeight); + QRect selectionExtent = extent(); + + selectionExtent.setLeft(selectionExtent.left() - 1); + selectionExtent.setTop(selectionExtent.top() - 1); + selectionExtent.setWidth(selectionExtent.width() + 2); + selectionExtent.setHeight(selectionExtent.height() + 2); + + QRegion uniformRegion = QRegion(imageRect); + uniformRegion -= QRegion(selectionExtent); + + if (!uniformRegion.isEmpty()) { + paintUniformSelectionRegion(img, imageRect, uniformRegion); + } + + QRect nonuniformRect = imageRect & selectionExtent; + + if (!nonuniformRect.isEmpty()) { + + const Q_INT32 imageRectOffsetX = nonuniformRect.x() - imageRectX; + const Q_INT32 imageRectOffsetY = nonuniformRect.y() - imageRectY; + + imageRectX = nonuniformRect.x(); + imageRectY = nonuniformRect.y(); + imageRectWidth = nonuniformRect.width(); + imageRectHeight = nonuniformRect.height(); + + const Q_INT32 NUM_SELECTION_ROWS = 3; + + Q_UINT8 *selectionRow[NUM_SELECTION_ROWS]; + + Q_INT32 aboveRowIndex = 0; + Q_INT32 centreRowIndex = 1; + Q_INT32 belowRowIndex = 2; + + selectionRow[aboveRowIndex] = new Q_UINT8[imageRectWidth + 2]; + selectionRow[centreRowIndex] = new Q_UINT8[imageRectWidth + 2]; + selectionRow[belowRowIndex] = new Q_UINT8[imageRectWidth + 2]; + + readBytes(selectionRow[centreRowIndex], imageRectX - 1, imageRectY - 1, imageRectWidth + 2, 1); + readBytes(selectionRow[belowRowIndex], imageRectX - 1, imageRectY, imageRectWidth + 2, 1); + + for (Q_INT32 y = 0; y < imageRectHeight; ++y) { + + Q_INT32 oldAboveRowIndex = aboveRowIndex; + aboveRowIndex = centreRowIndex; + centreRowIndex = belowRowIndex; + belowRowIndex = oldAboveRowIndex; + + readBytes(selectionRow[belowRowIndex], imageRectX - 1, imageRectY + y + 1, imageRectWidth + 2, 1); + + const Q_UINT8 *aboveRow = selectionRow[aboveRowIndex] + 1; + const Q_UINT8 *centreRow = selectionRow[centreRowIndex] + 1; + const Q_UINT8 *belowRow = selectionRow[belowRowIndex] + 1; + + QRgb *imagePixel = reinterpret_cast<QRgb *>(img.scanLine(imageRectOffsetY + y)); + imagePixel += imageRectOffsetX; + + for (Q_INT32 x = 0; x < imageRectWidth; ++x) { + + Q_UINT8 centre = *centreRow; + + if (centre != MAX_SELECTED) { + + // this is where we come if the pixels should be blue or bluish + + QRgb srcPixel = *imagePixel; + Q_UINT8 srcGrey = (qRed(srcPixel) + qGreen(srcPixel) + qBlue(srcPixel)) / 9; + Q_UINT8 srcAlpha = qAlpha(srcPixel); + + // Colour influence is proportional to alphaPixel. + srcGrey = UINT8_MULT(srcGrey, srcAlpha); + + QRgb dstPixel; + + if (centre == MIN_SELECTED) { + //this is where we come if the pixels should be blue (or red outline) + + Q_UINT8 left = *(centreRow - 1); + Q_UINT8 right = *(centreRow + 1); + Q_UINT8 above = *aboveRow; + Q_UINT8 below = *belowRow; + + // Stop unselected transparent areas from appearing the same + // as selected transparent areas. + Q_UINT8 dstAlpha = QMAX(srcAlpha, 192); + + // now for a simple outline based on 4-connectivity + if (left != MIN_SELECTED || right != MIN_SELECTED || above != MIN_SELECTED || below != MIN_SELECTED) { + dstPixel = qRgba(255, 0, 0, dstAlpha); + } else { + dstPixel = qRgba(128 + srcGrey, 128 + srcGrey, 165 + srcGrey, dstAlpha); + } + } else { + dstPixel = qRgba(UINT8_BLEND(qRed(srcPixel), srcGrey + 128, centre), + UINT8_BLEND(qGreen(srcPixel), srcGrey + 128, centre), + UINT8_BLEND(qBlue(srcPixel), srcGrey + 165, centre), + srcAlpha); + } + + *imagePixel = dstPixel; + } + + aboveRow++; + centreRow++; + belowRow++; + imagePixel++; + } + } + + delete [] selectionRow[aboveRowIndex]; + delete [] selectionRow[centreRowIndex]; + delete [] selectionRow[belowRowIndex]; + } +} + +void KisSelection::paintSelection(QImage img, const QRect& scaledImageRect, const QSize& scaledImageSize, const QSize& imageSize) +{ + if (img.isNull() || scaledImageRect.isEmpty() || scaledImageSize.isEmpty() || imageSize.isEmpty()) { + return; + } + + Q_ASSERT(img.size() == scaledImageRect.size()); + + if (img.size() != scaledImageRect.size()) { + return; + } + + Q_INT32 imageWidth = imageSize.width(); + Q_INT32 imageHeight = imageSize.height(); + + QRect selectionExtent = extent(); + + selectionExtent.setLeft(selectionExtent.left() - 1); + selectionExtent.setTop(selectionExtent.top() - 1); + selectionExtent.setWidth(selectionExtent.width() + 2); + selectionExtent.setHeight(selectionExtent.height() + 2); + + double xScale = static_cast<double>(scaledImageSize.width()) / imageWidth; + double yScale = static_cast<double>(scaledImageSize.height()) / imageHeight; + + QRect scaledSelectionExtent; + + scaledSelectionExtent.setLeft(static_cast<int>(selectionExtent.left() * xScale)); + scaledSelectionExtent.setRight(static_cast<int>(ceil((selectionExtent.right() + 1) * xScale)) - 1); + scaledSelectionExtent.setTop(static_cast<int>(selectionExtent.top() * yScale)); + scaledSelectionExtent.setBottom(static_cast<int>(ceil((selectionExtent.bottom() + 1) * yScale)) - 1); + + QRegion uniformRegion = QRegion(scaledImageRect); + uniformRegion -= QRegion(scaledSelectionExtent); + + if (!uniformRegion.isEmpty()) { + paintUniformSelectionRegion(img, scaledImageRect, uniformRegion); + } + + QRect nonuniformRect = scaledImageRect & scaledSelectionExtent; + + if (!nonuniformRect.isEmpty()) { + + const Q_INT32 scaledImageRectXOffset = nonuniformRect.x() - scaledImageRect.x(); + const Q_INT32 scaledImageRectYOffset = nonuniformRect.y() - scaledImageRect.y(); + + const Q_INT32 scaledImageRectX = nonuniformRect.x(); + const Q_INT32 scaledImageRectY = nonuniformRect.y(); + const Q_INT32 scaledImageRectWidth = nonuniformRect.width(); + const Q_INT32 scaledImageRectHeight = nonuniformRect.height(); + + const Q_INT32 imageRowLeft = static_cast<Q_INT32>(scaledImageRectX / xScale); + const Q_INT32 imageRowRight = static_cast<Q_INT32>((ceil((scaledImageRectX + scaledImageRectWidth - 1 + 1) / xScale)) - 1); + + const Q_INT32 imageRowWidth = imageRowRight - imageRowLeft + 1; + const Q_INT32 imageRowStride = imageRowWidth + 2; + + const Q_INT32 NUM_SELECTION_ROWS = 3; + + Q_INT32 aboveRowIndex = 0; + Q_INT32 centreRowIndex = 1; + Q_INT32 belowRowIndex = 2; + + Q_INT32 aboveRowSrcY = -3; + Q_INT32 centreRowSrcY = -3; + Q_INT32 belowRowSrcY = -3; + + Q_UINT8 *selectionRows = new Q_UINT8[imageRowStride * NUM_SELECTION_ROWS]; + Q_UINT8 *selectionRow[NUM_SELECTION_ROWS]; + + selectionRow[0] = selectionRows + 1; + selectionRow[1] = selectionRow[0] + imageRowStride; + selectionRow[2] = selectionRow[0] + (2 * imageRowStride); + + for (Q_INT32 y = 0; y < scaledImageRectHeight; ++y) { + + Q_INT32 scaledY = scaledImageRectY + y; + Q_INT32 srcY = (scaledY * imageHeight) / scaledImageSize.height(); + + Q_UINT8 *aboveRow; + Q_UINT8 *centreRow; + Q_UINT8 *belowRow; + + if (srcY - 1 == aboveRowSrcY) { + aboveRow = selectionRow[aboveRowIndex]; + centreRow = selectionRow[centreRowIndex]; + belowRow = selectionRow[belowRowIndex]; + } else if (srcY - 1 == centreRowSrcY) { + + Q_INT32 oldAboveRowIndex = aboveRowIndex; + + aboveRowIndex = centreRowIndex; + centreRowIndex = belowRowIndex; + belowRowIndex = oldAboveRowIndex; + + aboveRow = selectionRow[aboveRowIndex]; + centreRow = selectionRow[centreRowIndex]; + belowRow = selectionRow[belowRowIndex]; + + readBytes(belowRow - 1, imageRowLeft - 1, srcY + 1, imageRowStride, 1); + + } else if (srcY - 1 == belowRowSrcY) { + + Q_INT32 oldAboveRowIndex = aboveRowIndex; + Q_INT32 oldCentreRowIndex = centreRowIndex; + + aboveRowIndex = belowRowIndex; + centreRowIndex = oldAboveRowIndex; + belowRowIndex = oldCentreRowIndex; + + aboveRow = selectionRow[aboveRowIndex]; + centreRow = selectionRow[centreRowIndex]; + belowRow = selectionRow[belowRowIndex]; + + if (belowRowIndex == centreRowIndex + 1) { + readBytes(centreRow - 1, imageRowLeft - 1, srcY, imageRowStride, 2); + } else { + readBytes(centreRow - 1, imageRowLeft - 1, srcY, imageRowStride, 1); + readBytes(belowRow - 1, imageRowLeft - 1, srcY + 1, imageRowStride, 1); + } + + } else { + + aboveRowIndex = 0; + centreRowIndex = 1; + belowRowIndex = 2; + + aboveRow = selectionRow[aboveRowIndex]; + centreRow = selectionRow[centreRowIndex]; + belowRow = selectionRow[belowRowIndex]; + + readBytes(selectionRows, imageRowLeft - 1, srcY - 1, imageRowStride, NUM_SELECTION_ROWS); + } + + aboveRowSrcY = srcY - 1; + centreRowSrcY = aboveRowSrcY + 1; + belowRowSrcY = centreRowSrcY + 1; + + QRgb *imagePixel = reinterpret_cast<QRgb *>(img.scanLine(scaledImageRectYOffset + y)); + imagePixel += scaledImageRectXOffset; + + for (Q_INT32 x = 0; x < scaledImageRectWidth; ++x) { + + Q_INT32 scaledX = scaledImageRectX + x; + Q_INT32 srcX = (scaledX * imageWidth) / scaledImageSize.width(); + + Q_UINT8 centre = *(centreRow + srcX - imageRowLeft); + + if (centre != MAX_SELECTED) { + + // this is where we come if the pixels should be blue or bluish + + QRgb srcPixel = *imagePixel; + Q_UINT8 srcGrey = (qRed(srcPixel) + qGreen(srcPixel) + qBlue(srcPixel)) / 9; + Q_UINT8 srcAlpha = qAlpha(srcPixel); + + // Colour influence is proportional to alphaPixel. + srcGrey = UINT8_MULT(srcGrey, srcAlpha); + + QRgb dstPixel; + + if (centre == MIN_SELECTED) { + //this is where we come if the pixels should be blue (or red outline) + + Q_UINT8 left = *(centreRow + (srcX - imageRowLeft) - 1); + Q_UINT8 right = *(centreRow + (srcX - imageRowLeft) + 1); + Q_UINT8 above = *(aboveRow + (srcX - imageRowLeft)); + Q_UINT8 below = *(belowRow + (srcX - imageRowLeft)); + + // Stop unselected transparent areas from appearing the same + // as selected transparent areas. + Q_UINT8 dstAlpha = QMAX(srcAlpha, 192); + + // now for a simple outline based on 4-connectivity + if (left != MIN_SELECTED || right != MIN_SELECTED || above != MIN_SELECTED || below != MIN_SELECTED) { + dstPixel = qRgba(255, 0, 0, dstAlpha); + } else { + dstPixel = qRgba(128 + srcGrey, 128 + srcGrey, 165 + srcGrey, dstAlpha); + } + } else { + dstPixel = qRgba(UINT8_BLEND(qRed(srcPixel), srcGrey + 128, centre), + UINT8_BLEND(qGreen(srcPixel), srcGrey + 128, centre), + UINT8_BLEND(qBlue(srcPixel), srcGrey + 165, centre), + srcAlpha); + } + + *imagePixel = dstPixel; + } + + imagePixel++; + } + } + + delete [] selectionRows; + } +} + +void KisSelection::setDirty(const QRect& rc) +{ + if (m_dirty) + super::setDirty(rc); +} + +void KisSelection::setDirty() +{ + if (m_dirty) + super::setDirty(); +} diff --git a/krita/core/kis_selection.h b/krita/core/kis_selection.h new file mode 100644 index 00000000..959be492 --- /dev/null +++ b/krita/core/kis_selection.h @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2004 Boudewijn Rempt <boud@valdyas.org> + * + * 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_SELECTION_H_ +#define KIS_SELECTION_H_ + +#include <qrect.h> + +#include "kis_types.h" +#include "kis_paint_device.h" + +#include <koffice_export.h> + + +enum enumSelectionMode { + SELECTION_ADD, + SELECTION_SUBTRACT +}; + +/** + * KisSelection contains a byte-map representation of a layer, where + * the value of a byte signifies whether a corresponding pixel is selected, or not. + * + * NOTE: If you need to manually call emitSelectionChanged on the owner paint device + * of a selection. KisSelection does not emit any signals by itself because + * often you want to combine several actions in to perfom one operation and you + * do not want recomposition to happen all the time. + */ +class KRITACORE_EXPORT KisSelection : public KisPaintDevice { + + typedef KisPaintDevice super; + +public: + /** + * Create a new KisSelection + * @param dev the parent paint device. The selection will never be bigger than the parent + * paint device. + */ + KisSelection(KisPaintDeviceSP dev); + + /** + * Create a new KisSelection. This selection will not have a parent paint device. + */ + KisSelection(); + + /** + * Copy the selection + */ + KisSelection(const KisSelection& rhs); + + virtual ~KisSelection(); + + // Returns selectedness, or 0 if invalid coordinates + Q_UINT8 selected(Q_INT32 x, Q_INT32 y); + + void setSelected(Q_INT32 x, Q_INT32 y, Q_UINT8 s); + + QImage maskImage(); + + void select(QRect r); + + void invert(); + + void clear(QRect r); + + void clear(); + + /// Tests if the the rect is totally outside the selection + bool isTotallyUnselected(QRect r); + + /** + * Tests if the the rect is totally outside the selection, but uses selectedRect + * instead of selectedRect, and this is faster (but might deliver false positives!) + * + * XXX: This comment makes no sense anymore! (BSAR) + */ + bool isProbablyTotallyUnselected(QRect r); + + /** + * Rough, but fastish way of determining the area + * of the tiles used by the selection. + */ + QRect selectedRect() const; + + /** + * Slow, but exact way of determining the rectangle + * that encloses the selection + */ + QRect selectedExactRect() const; + + void paintSelection(QImage img, Q_INT32 x, Q_INT32 y, Q_INT32 w, Q_INT32 h); + void paintSelection(QImage img, const QRect& scaledImageRect, const QSize& scaledImageSize, const QSize& imageSize); + + void startCachingExactRect(); + void stopCachingExactRect(); + + // if the parent layer is interested in keeping up to date with the dirtyness + // of this layer, set to true + void setInterestedInDirtyness(bool b) { m_dirty = b; } + bool interestedInDirtyness() const { return m_dirty; } + + virtual void setDirty(const QRect & rc); + virtual void setDirty(); + inline KisPaintDeviceSP parentPaintDevice() { return m_parentPaintDevice; } +private: + void paintUniformSelectionRegion(QImage img, const QRect& imageRect, const QRegion& uniformRegion); + +private: + + // We don't want these methods to be used on selections: + void extent(Q_INT32 &x, Q_INT32 &y, Q_INT32 &w, Q_INT32 &h) const + { + KisPaintDevice::extent(x,y,w,h); + } + + QRect extent() const { return KisPaintDevice::extent(); } + + void exactBounds(Q_INT32 &x, Q_INT32 &y, Q_INT32 &w, Q_INT32 &h) const + { + return KisPaintDevice::exactBounds(x,y,w,h); + } + + QRect exactBounds() const + { + return KisPaintDevice::exactBounds(); + } + + QRect exactBoundsOldMethod() const + { + return KisPaintDevice::exactBoundsOldMethod(); + } + + QRect exactBoundsImprovedOldMethod() const + { + return KisPaintDevice::exactBoundsImprovedOldMethod(); + } + + +private: + KisPaintDeviceSP m_parentPaintDevice; + bool m_doCacheExactRect; + QRect m_cachedExactRect; + bool m_dirty; +}; + +#endif // KIS_SELECTION_H_ diff --git a/krita/core/kis_shear_visitor.h b/krita/core/kis_shear_visitor.h new file mode 100644 index 00000000..9a48181e --- /dev/null +++ b/krita/core/kis_shear_visitor.h @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2006 Bart Coppens <kde@bartcoppens.be> + * + * 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_SHEAR_VISITOR_H_ +#define KIS_SHEAR_VISITOR_H_ + +#include "kis_types.h" +#include "kis_progress_subject.h" +#include "kis_layer_visitor.h" +#include "kis_transform_worker.h" +#include "kis_filter_strategy.h" +#include "kis_undo_adapter.h" +#include "kis_transaction.h" +#include "kis_rotate_visitor.h" + +class KisShearVisitor : public KisLayerVisitor { +public: + KisShearVisitor(double xshear, double yshear, KisProgressDisplayInterface *progress) + : m_xshear(xshear), m_yshear(yshear), m_progress(progress), m_strategy(0), m_undo(0) {}; + + void setStrategy(KisFilterStrategy* strategy) { m_strategy = strategy; } + void setUndoAdapter(KisUndoAdapter* undo) { m_undo = undo; } +public: + virtual bool visit(KisPaintLayer* layer) { + KisPaintDeviceSP dev = layer->paintDevice(); + if(!dev) + return true; + + KisFilterStrategy* strategy = 0; + if (m_strategy) + strategy = m_strategy; + else + strategy = new KisMitchellFilterStrategy; + + KisTransaction* t = 0; + + if (m_undo && m_undo->undo()) + t = new KisTransaction("", dev.data()); + + //Doesn't do anything, internally transforms x and y shear to 0 each :-/// + //KisTransformWorker w(dev, 1.0, 1.0, m_xshear, m_yshear, 0, 0, 0, m_progress, strategy); + //w.run(); + + KisRotateVisitor v; + v.visitKisPaintDevice(dev); + v.shear(m_xshear, m_yshear, m_progress); + + if (m_undo && m_undo->undo()) + m_undo->addCommand(t); + + if (!m_strategy) + delete strategy; + + layer->setDirty(); + + return true; + } + + virtual bool visit(KisGroupLayer* layer) { + KisLayerSP child = layer->firstChild(); + + while(child) + { + child->accept(*this); + child = child->nextSibling(); + } + return true; + } + + virtual bool visit(KisPartLayer*) { return true; } + virtual bool visit(KisAdjustmentLayer *) { return true; } +private: + double m_xshear; + double m_yshear; + KisProgressDisplayInterface* m_progress; + KisFilterStrategy* m_strategy; + KisUndoAdapter* m_undo; +}; + +#endif // KIS_SHEAR_VISITOR_H_ diff --git a/krita/core/kis_strategy_move.cc b/krita/core/kis_strategy_move.cc new file mode 100644 index 00000000..bd99349b --- /dev/null +++ b/krita/core/kis_strategy_move.cc @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2002 Patrick Julien <freak@codepimps.org> + * + * 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 <qpoint.h> +#include <qcolor.h> + +#include <kaction.h> +#include <kcommand.h> +#include <klocale.h> +#include <kdebug.h> + +#include "kis_canvas_controller.h" +#include "kis_canvas_subject.h" +#include "kis_image.h" +#include "kis_layer.h" +#include "kis_strategy_move.h" +#include "kis_undo_adapter.h" + +KisStrategyMove::KisStrategyMove() +{ + reset(0); +} + +KisStrategyMove::KisStrategyMove(KisCanvasSubject *subject) +{ + reset(subject); +} + +KisStrategyMove::~KisStrategyMove() +{ +} + +void KisStrategyMove::reset(KisCanvasSubject *subject) +{ + m_subject = subject; + m_dragging = false; + + if (m_subject) { + m_controller = subject->canvasController(); + } else { + m_controller = 0; + } +} + +void KisStrategyMove::startDrag(const QPoint& pos) +{ + // pos is the user chosen handle point + + if (m_subject) { + KisImageSP img; + KisLayerSP dev; + + if (!(img = m_subject->currentImg())) + return; + + dev = img->activeLayer(); + + if (!dev || !dev->visible()) + return; + + m_dragging = true; + m_dragStart.setX(pos.x()); + m_dragStart.setY(pos.y()); + m_layerStart.setX(dev->x()); + m_layerStart.setY(dev->y()); + m_layerPosition = m_layerStart; + } +} + +void KisStrategyMove::drag(const QPoint& original) +{ + // original is the position of the user chosen handle point + + if (m_subject && m_dragging) { + KisImageSP img = m_subject->currentImg(); + KisLayerSP dev; + + if (img && (dev = img->activeLayer())) { + QPoint pos = original; + QRect rc; + + pos -= m_dragStart; // convert to delta + rc = dev->extent(); + dev->setX(dev->x() + pos.x()); + dev->setY(dev->y() + pos.y()); + rc = rc.unite(dev->extent()); + + m_layerPosition = QPoint(dev->x(), dev->y()); + m_dragStart = original; + + dev->setDirty(rc); + } + } +} + +void KisStrategyMove::endDrag(const QPoint& pos, bool undo) +{ + if (m_subject && m_dragging) { + KisImageSP img = m_subject->currentImg(); + KisLayerSP dev; + + if (img && (dev = img->activeLayer())) { + drag(pos); + m_dragging = false; + + if (undo && img->undo()) { + KCommand *cmd = dev->moveCommand(m_layerStart, m_layerPosition); + Q_CHECK_PTR(cmd); + + KisUndoAdapter *adapter = img->undoAdapter(); + if (adapter) { + adapter->addCommand(cmd); + } else { + delete cmd; + } + } + img->setModified(); + } + } +} + +void KisStrategyMove::simpleMove(const QPoint& pt1, const QPoint& pt2) +{ + startDrag(pt1); + endDrag(pt2); +} + +void KisStrategyMove::simpleMove(Q_INT32 x1, Q_INT32 y1, Q_INT32 x2, Q_INT32 y2) +{ + startDrag(QPoint(x1, y1)); + endDrag(QPoint(x2, y2)); +} + diff --git a/krita/core/kis_strategy_move.h b/krita/core/kis_strategy_move.h new file mode 100644 index 00000000..e3787866 --- /dev/null +++ b/krita/core/kis_strategy_move.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2002 Patrick Julien <freak@codepimps.org> + * + * 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_STRATEGY_MOVE_H_ +#define KIS_STRATEGY_MOVE_H_ + +#include <qpoint.h> +#include <qrect.h> + +#include <koffice_export.h> + +class KisCanvasController; +class KisCanvasSubject; + +class KRITAUI_EXPORT KisStrategyMove { +public: + KisStrategyMove(); + explicit KisStrategyMove(KisCanvasSubject *subject); + virtual ~KisStrategyMove(); + +public: + void reset(KisCanvasSubject *subject); + void startDrag(const QPoint& pos); + void drag(const QPoint& pos); + void endDrag(const QPoint& pos, bool undo = true); + void simpleMove(const QPoint& pt1, const QPoint& pt2); + void simpleMove(Q_INT32 x1, Q_INT32 y1, Q_INT32 x2, Q_INT32 y2); + +private: + KisStrategyMove(const KisStrategyMove&); + KisStrategyMove& operator=(const KisStrategyMove&); + +private: + KisCanvasController *m_controller; + KisCanvasSubject *m_subject; + QRect m_deviceBounds; + QPoint m_dragStart; + QPoint m_layerStart; + QPoint m_layerPosition; + bool m_dragging; +}; + +#endif // KIS_STRATEGY_MOVE_H_ + diff --git a/krita/core/kis_substrate.h b/krita/core/kis_substrate.h new file mode 100644 index 00000000..96207747 --- /dev/null +++ b/krita/core/kis_substrate.h @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2006 Boudewijn Rempt (boud@valdyas.org) + * + * 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_SUBSTRATE_H +#define KIS_SUBSTRATE_H + +#include <qrect.h> +#include <ksharedptr.h> + +class KisImage; + +/// All values are normalized to a range between 0 and 1. +/// XXX: Do we need more? +struct KisSubstratePixel { + float height; // absolute height of the current position + float smoothness; // determines how easily the painting tool "slips" over the surface + float absorbency; // determines how much wetness the substrate can absorb. XXX: How about speed of absorbing? + float density; // XXX? + Q_UINT8 r; //.Red component of reflectivity + Q_UINT8 g; // Green component of reflectivity + Q_UINT8 b; // Blue component of reflectivity + Q_UINT8 alpha; // For composition with the background +}; + +/** + * This abstract class defines the properties of a substrate -- that is, the simulation + * of the paper or canvas for natural media. + * + * Subclass this interface to define a specific type of substrate: repeating, + * or full-size, with specific and cool ways of generating the surface, or + * maybe based on scans of real substrates. + */ +class KisSubstrate : public KShared { + +public: + + KisSubstrate(KisImage * /*img*/) : KShared() {}; + virtual ~KisSubstrate() {}; + + + /** + * Copy the pixel values in the specified rect into an array of Substrate. + * Make sure the array is big enough! + */ + virtual void getPixels(KisSubstratePixel * substrate, const QRect & rc) const = 0; + + /** + * Copy the specified rect of substrate pixels onto the substrate. Make sure + * the array is big enough. + */ + virtual void writePixels(const KisSubstratePixel * substrate, const QRect & rc) = 0; + /** + * Read the value at the specified position into the given substrate pixel. + */ + virtual void getPixel(KisSubstratePixel * ksp, int x, int y) const = 0; + + /** + * Copy the value of the given substrate pixel to the specified location. + */ + virtual void writePixel(const KisSubstratePixel & ksp, int x, int y) = 0; + +}; + +#endif diff --git a/krita/core/kis_thread.h b/krita/core/kis_thread.h new file mode 100644 index 00000000..f26ebf8b --- /dev/null +++ b/krita/core/kis_thread.h @@ -0,0 +1,57 @@ +/* + * copyright (c) 2005 Boudewijn Rempt + * + * 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_THREAD_ +#define KIS_THREAD_ + +#include <qthread.h> +#include <ksharedptr.h> + +/** + * A KisThread is a QThread that can be set in the canceled state. + * Lengthy operations initiated in run() should regularly read the + * canceled state and stop when it's set to true + */ +class KisThread : public QThread { + +public: + + /** + * Create a new KisThread with the canceled state set to false + */ + KisThread() : QThread(), m_canceled(false) {}; + + /** + * Request the thread to cancel at the first opportunity. Note + * that the owner of the thread is responsible for restoring the + * previous state of paint devices etc, the thread itself just stops + * as soon as possible. + */ + virtual void cancel() { m_canceled = true; } + virtual bool isCanceled() { return m_canceled; } + + void runDirectly() { run(); } + +protected: + + bool m_canceled; + +}; + + +#endif diff --git a/krita/core/kis_thread_pool.cc b/krita/core/kis_thread_pool.cc new file mode 100644 index 00000000..2f80a0ae --- /dev/null +++ b/krita/core/kis_thread_pool.cc @@ -0,0 +1,192 @@ +/* + * copyright (c) 2006 Boudewijn Rempt + * + * This program is free software; you can distribute 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_thread_pool.h" +#include <kglobal.h> +#include <kconfig.h> +#include <kdebug.h> + +KisThreadPool * KisThreadPool::m_singleton = 0; + +KisThreadPool::KisThreadPool() +{ + Q_ASSERT(KisThreadPool::m_singleton == 0); + + KisThreadPool::m_singleton = this; + + KConfig * cfg = KGlobal::config(); + cfg->setGroup(""); + m_maxThreads = cfg->readNumEntry("maxthreads", 10); + m_numberOfRunningThreads = 0; + m_numberOfQueuedThreads = 0; + m_wait = 200; + + start(); +} + + +KisThreadPool::~KisThreadPool() +{ + m_poolMutex.lock(); + + m_canceled = true; + + m_runningThreads.setAutoDelete(true); + m_threads.setAutoDelete(true); + m_oldThreads.setAutoDelete(true); + + KisThread * t; + + for ( t = m_threads.first(); t; t = m_threads.next()) { + if (t) { + t->cancel(); + t->wait(); + m_threads.remove(t); + } + } + + for ( t = m_runningThreads.first(); t; t = m_runningThreads.next()) { + if (t) { + t->cancel(); + t->wait(); + m_runningThreads.remove(t); + } + } + + for ( t = m_oldThreads.first(); t; t = m_oldThreads.next()) { + if (t) { + t->cancel(); + t->wait(); + m_runningThreads.remove(t); + } + } + KisThreadPool::m_singleton = 0; + m_poolMutex.unlock(); + +} + + +KisThreadPool * KisThreadPool::instance() +{ + if(KisThreadPool::m_singleton == 0) + { + KisThreadPool::m_singleton = new KisThreadPool(); + } + else { + + if (KisThreadPool::m_singleton->finished()) { + delete KisThreadPool::m_singleton; + KisThreadPool::m_singleton = 0; + KisThreadPool::m_singleton = new KisThreadPool(); + } + } + + return KisThreadPool::m_singleton; +} + +void KisThreadPool::enqueue(KisThread * thread) +{ + m_poolMutex.lock(); + m_threads.append(thread); + m_numberOfQueuedThreads++; + m_poolMutex.unlock(); + m_wait = 200; +} + + +void KisThreadPool::dequeue(KisThread * thread) +{ + KisThread * t = 0; + + m_poolMutex.lock(); + + int i = m_threads.findRef(thread); + if (i >= 0) { + t = m_threads.take(i); + m_numberOfQueuedThreads--; + } else { + i = m_runningThreads.findRef(thread); + if (i >= 0) { + t = m_runningThreads.take(i); + m_numberOfRunningThreads--; + } + else { + i = m_oldThreads.findRef(thread); + if (i >= 0) { + t = m_oldThreads.take(i); + } + } + } + + m_poolMutex.unlock(); + + if (t) { + t->cancel(); + t->wait(); + delete t; + } + +} + +void KisThreadPool::run() +{ + int sleeps = 10; + + while(!m_canceled) { + if (m_numberOfQueuedThreads > 0 && m_numberOfRunningThreads < m_maxThreads) { + KisThread * thread = 0; + m_poolMutex.lock(); + if (m_threads.count() > 0) { + thread = m_threads.take(); + m_numberOfQueuedThreads--; + } + if (thread) { + thread->start(); + m_runningThreads.append(thread); + m_numberOfRunningThreads++; + } + m_poolMutex.unlock(); + } + else { + msleep(m_wait); + m_poolMutex.lock(); + for ( KisThread * t = m_runningThreads.first(); t; t = m_runningThreads.next()) { + if (t) { + if (t->finished()) { + m_runningThreads.remove(t); + m_numberOfRunningThreads--; + m_oldThreads.append(t); + } + } + } + m_poolMutex.unlock(); + m_poolMutex.lock(); + if (m_numberOfQueuedThreads == 0 && m_numberOfRunningThreads == 0) { + sleeps--; + if (sleeps == 0) { + m_poolMutex.unlock(); + return; + } + m_poolMutex.unlock(); + + } + m_poolMutex.unlock(); + + } + } +} diff --git a/krita/core/kis_thread_pool.h b/krita/core/kis_thread_pool.h new file mode 100644 index 00000000..069cb5a5 --- /dev/null +++ b/krita/core/kis_thread_pool.h @@ -0,0 +1,70 @@ +/* + * copyright (c) 2006 Boudewijn Rempt + * + * This program is free software; you can distribute 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_THREAD_POOL_ +#define KIS_THREAD_POOL_ + +#include <qthread.h> +#include <qptrlist.h> +#include <qmutex.h> + +#include "kis_thread.h" + +/** + * A thread pool starts executing threads some time after they are added, + * running a maximum number of threads at one time. + * + * The pool takes ownership of the threads and _deletes_ them once they + * have run. This means that you cannot add getters for important data to + * threads you feed the threadpool. Instead, post the data using a customevent. + */ +class KisThreadPool : public KisThread { + +public: + + virtual ~KisThreadPool(); + + static KisThreadPool * instance(); + + void enqueue(KisThread * thread); + void dequeue(KisThread * thread); + + void run(); + + + KisThreadPool(); + +private: + + KisThreadPool(const KisThreadPool&); + KisThreadPool operator=(const KisThreadPool&); + + QMutex m_poolMutex; + int m_numberOfRunningThreads; + int m_numberOfQueuedThreads; + int m_maxThreads; + int m_wait; + QPtrList<KisThread> m_threads; + QPtrList<KisThread> m_runningThreads; + QPtrList<KisThread> m_oldThreads; + + static KisThreadPool * m_singleton; +}; + + +#endif diff --git a/krita/core/kis_transaction.cc b/krita/core/kis_transaction.cc new file mode 100644 index 00000000..10df1a8e --- /dev/null +++ b/krita/core/kis_transaction.cc @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2002 Patrick Julien <freak@codepimps.org> + * + * 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_types.h" +#include "kis_global.h" +#include "kis_tile.h" +#include "kis_tileddatamanager.h" +#include "kis_image.h" +#include "kis_transaction.h" +#include "kis_memento.h" +#include "kis_paint_device.h" +#include "kis_layer.h" + +class KisTransactionPrivate { +public: + QString m_name; + KisPaintDeviceSP m_device; + KisMementoSP m_memento; + +}; + +KisTransaction::KisTransaction(const QString& name, KisPaintDeviceSP device) +{ + m_private = new KisTransactionPrivate; + + m_private->m_name = name; + m_private->m_device = device; + m_private->m_memento = device->getMemento(); +} + +KisTransaction::~KisTransaction() +{ + if (m_private->m_memento) { + // For debugging purposes + m_private->m_memento->setInvalid(); + } + delete m_private; +} + +void KisTransaction::execute() +{ + Q_ASSERT(m_private->m_memento != 0); + + m_private->m_device->rollforward(m_private->m_memento); + + QRect rc; + Q_INT32 x, y, width, height; + m_private->m_memento->extent(x,y,width,height); + rc.setRect(x + m_private->m_device->getX(), y + m_private->m_device->getY(), width, height); + + KisLayerSP l = m_private->m_device->parentLayer(); + if (l) l->setDirty(rc); +} + +void KisTransaction::unexecute() +{ + Q_ASSERT(m_private->m_memento != 0); + m_private->m_device->rollback(m_private->m_memento); + + QRect rc; + Q_INT32 x, y, width, height; + m_private->m_memento->extent(x,y,width,height); + rc.setRect(x + m_private->m_device->getX(), y + m_private->m_device->getY(), width, height); + + KisLayerSP l = m_private->m_device->parentLayer(); + if (l) l->setDirty(rc); + +} + +void KisTransaction::unexecuteNoUpdate() +{ + Q_ASSERT(m_private->m_memento != 0); + + m_private->m_device->rollback(m_private->m_memento); +} + +QString KisTransaction::name() const +{ + return m_private->m_name; +} diff --git a/krita/core/kis_transaction.h b/krita/core/kis_transaction.h new file mode 100644 index 00000000..0865c59b --- /dev/null +++ b/krita/core/kis_transaction.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2002 Patrick Julien <freak@codepimps.org> + * + * 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_TILE_COMMAND_H_ +#define KIS_TILE_COMMAND_H_ + +#include <qstring.h> + +#include <kcommand.h> + +#include "kis_types.h" + +class QRect; +class KisTransactionPrivate; + +class KisTransaction : public KCommand { +public: + KisTransaction(const QString& name, KisPaintDeviceSP device); + virtual ~KisTransaction(); + +public: + virtual void execute(); + virtual void unexecute(); + virtual void unexecuteNoUpdate(); + virtual QString name() const; +private: + KisTransactionPrivate * m_private; +}; + +#endif // KIS_TILE_COMMAND_H_ + diff --git a/krita/core/kis_transform_visitor.h b/krita/core/kis_transform_visitor.h new file mode 100644 index 00000000..62f6b3e4 --- /dev/null +++ b/krita/core/kis_transform_visitor.h @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2006 Casper Boemann <cbr@boemann.dk> + * + * 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_TRANSFORM_VISITOR_H_ +#define KIS_TRANSFORM_VISITOR_H_ + +#include "qrect.h" + +#include "klocale.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_transform_worker.h" +#include <kis_selected_transaction.h> + +class KisProgressDisplayInterface; +class KisFilterStrategy; + +class KisTransformVisitor : public KisLayerVisitor { + +public: + + KisTransformVisitor(KisImageSP img, double xscale, double yscale, + double /*xshear*/, double /*yshear*/, double angle, + Q_INT32 tx, Q_INT32 ty, KisProgressDisplayInterface *progress, KisFilterStrategy *filter) + : KisLayerVisitor() + , m_sx(xscale) + , m_sy(yscale) + , m_tx(tx) + , m_ty(ty) + , m_filter(filter) + , m_angle(angle) + , m_progress(progress) + , m_img(img) + { + } + + virtual ~KisTransformVisitor() + { + } + + /** + * Crops the specified layer and adds the undo information to the undo adapter of the + * layer's image. + */ + bool visit(KisPaintLayer *layer) + { + KisPaintDeviceSP dev = layer->paintDevice(); + + KisTransaction * t = 0; + if (m_img->undo()) { + t = new KisTransaction(i18n("Rotate Layer"), dev); + Q_CHECK_PTR(t); + } + + KisTransformWorker tw(dev, m_sx, m_sy, 0.0, 0.0, m_angle, m_tx, m_ty, m_progress, m_filter, true); + tw.run(); + + if (m_img->undo()) { + m_img->undoAdapter()->addCommand(t); + } + layer->setDirty(); + return true; + }; + + bool visit(KisGroupLayer *layer) + { + layer->resetProjection(); + + KisLayerSP child = layer->firstChild(); + while (child) { + child->accept(*this); + child = child->nextSibling(); + } + layer->setDirty(); + return true; + }; + + bool visit(KisPartLayer */*layer*/) + { + return true; + }; + + virtual bool visit(KisAdjustmentLayer* layer) + { + KisPaintDeviceSP dev = layer->selection().data(); + + KisTransaction * t = 0; + + if (m_img->undo()) { + t = new KisTransaction(i18n("Rotate Layer"), dev); + Q_CHECK_PTR(t); + } + + KisTransformWorker tw(dev, m_sx, m_sy, 0.0, 0.0, m_angle, m_tx, m_ty, m_progress, m_filter, true); + tw.run(); + + if (m_img->undo()) { + m_img->undoAdapter()->addCommand(t); + } + layer->setDirty(); + + layer->resetCache(); + return true; + } + + +private: + double m_sx, m_sy; + Q_INT32 m_tx, m_ty; + KisFilterStrategy *m_filter; + double m_angle; + KisProgressDisplayInterface *m_progress; + KisImageSP m_img; +}; + + +#endif diff --git a/krita/core/kis_transform_worker.cc b/krita/core/kis_transform_worker.cc new file mode 100644 index 00000000..bc4dc609 --- /dev/null +++ b/krita/core/kis_transform_worker.cc @@ -0,0 +1,676 @@ +/* + * Copyright (c) 2004 Michael Thaler <michael.thaler@physik.tu-muenchen.de> filters + * Copyright (c) 2005 Casper Boemann <cbr@boemann.dk> + * Copyright (c) 2005 Boudewijn Rempt <boud@valdyas.org> right angle rotators + * + * 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 <kdebug.h> +#include <klocale.h> + +#include "kis_debug_areas.h" +#include "kis_paint_device.h" +#include "kis_selection.h" +#include "kis_transform_worker.h" +#include "kis_progress_display_interface.h" +#include "kis_iterators_pixel.h" +#include "kis_filter_strategy.h" +#include "kis_layer.h" +#include "kis_painter.h" + +KisTransformWorker::KisTransformWorker(KisPaintDeviceSP dev, double xscale, double yscale, + double xshear, double yshear, double rotation, + Q_INT32 xtranslate, Q_INT32 ytranslate, + KisProgressDisplayInterface *progress, KisFilterStrategy *filter, bool fixBorderAlpha) +{ + m_dev= dev; + m_xscale = xscale; + m_yscale = yscale; + m_xshear = xshear; + m_yshear = yshear; + m_rotation = rotation, + m_xtranslate = xtranslate; + m_ytranslate = ytranslate; + m_progress = progress; + m_filter = filter; + m_fixBorderAlpha = fixBorderAlpha; +} + +void KisTransformWorker::rotateNone(KisPaintDeviceSP src, KisPaintDeviceSP dst) +{ + KisSelectionSP dstSelection; + Q_INT32 pixelSize = src->pixelSize(); + QRect r; + KisColorSpace *cs = src->colorSpace(); + + if(src->hasSelection()) + { + r = src->selection()->selectedExactRect(); + dstSelection = dst->selection(); + } + else + { + r = src->exactBounds(); + dstSelection = new KisSelection(dst); // essentially a dummy to be deleted + } + + KisHLineIteratorPixel hit = src->createHLineIterator(r.x(), r.top(), r.width(), true); + KisHLineIterator vit = dst->createHLineIterator(r.x(), r.top(), r.width(), true); + KisHLineIterator dstSelIt = dstSelection->createHLineIterator(r.x(), r.top(), r.width(), true); + for (Q_INT32 i = 0; i < r.height(); ++i) { + while (!hit.isDone()) { + if (hit.isSelected()) { + memcpy(vit.rawData(), hit.rawData(), pixelSize); + + // XXX: Should set alpha = alpha*(1-selectedness) + cs->setAlpha(hit.rawData(), 0, 1); + } + *(dstSelIt.rawData()) = hit.selectedness(); + ++hit; + ++vit; + ++dstSelIt; + } + hit.nextRow(); + vit.nextRow(); + dstSelIt.nextRow(); + + //progress info + m_progressStep += r.width(); + if(m_lastProgressReport != (m_progressStep * 100) / m_progressTotalSteps) + { + m_lastProgressReport = (m_progressStep * 100) / m_progressTotalSteps; + emit notifyProgress(m_lastProgressReport); + } + if (m_cancelRequested) { + break; + } + } +} + +void KisTransformWorker::rotateRight90(KisPaintDeviceSP src, KisPaintDeviceSP dst) +{ + KisSelectionSP dstSelection; + Q_INT32 pixelSize = src->pixelSize(); + QRect r; + KisColorSpace *cs = src->colorSpace(); + + if(src->hasSelection()) + { + r = src->selection()->selectedExactRect(); + dstSelection = dst->selection(); + } + else + { + r = src->exactBounds(); + dstSelection = new KisSelection(dst); // essentially a dummy to be deleted + } + + for (Q_INT32 y = r.bottom(); y >= r.top(); --y) { + KisHLineIteratorPixel hit = src->createHLineIterator(r.x(), y, r.width(), true); + KisVLineIterator vit = dst->createVLineIterator(-y, r.x(), r.width(), true); + KisVLineIterator dstSelIt = dstSelection->createVLineIterator(-y, r.x(), r.width(), true); + + while (!hit.isDone()) { + if (hit.isSelected()) { + memcpy(vit.rawData(), hit.rawData(), pixelSize); + + // XXX: Should set alpha = alpha*(1-selectedness) + cs->setAlpha(hit.rawData(), 0, 1); + } + *(dstSelIt.rawData()) = hit.selectedness(); + ++hit; + ++vit; + ++dstSelIt; + } + + //progress info + m_progressStep += r.width(); + if(m_lastProgressReport != (m_progressStep * 100) / m_progressTotalSteps) + { + m_lastProgressReport = (m_progressStep * 100) / m_progressTotalSteps; + emit notifyProgress(m_lastProgressReport); + } + if (m_cancelRequested) { + break; + } + } +} + +void KisTransformWorker::rotateLeft90(KisPaintDeviceSP src, KisPaintDeviceSP dst) +{ + kdDebug() << "rotateLeft90 called\n"; + KisSelectionSP dstSelection; + Q_INT32 pixelSize = src->pixelSize(); + QRect r; + KisColorSpace *cs = src->colorSpace(); + + if(src->hasSelection()) + { + r = src->selection()->selectedExactRect(); + dstSelection = dst->selection(); + } + else + { + r = src->exactBounds(); + dstSelection = new KisSelection(dst); // essentially a dummy to be deleted + } + Q_INT32 x = 0; + + for (Q_INT32 y = r.top(); y <= r.bottom(); ++y) { + // Read the horizontal line from back to front, write onto the vertical column + KisHLineIteratorPixel hit = src->createHLineIterator(r.x(), y, r.width(), true); + KisVLineIterator vit = dst->createVLineIterator(y, -r.x() - r.width(), r.width(), true); + KisVLineIterator dstSelIt = dstSelection->createVLineIterator(y, -r.x() - r.width(), r.width(), true); + + hit += r.width() - 1; + while (!vit.isDone()) { + if (hit.isSelected()) { + memcpy(vit.rawData(), hit.rawData(), pixelSize); + + // XXX: Should set alpha = alpha*(1-selectedness) + cs->setAlpha(hit.rawData(), 0, 1); + } + *(dstSelIt.rawData()) = hit.selectedness(); + --hit; + ++vit; + ++dstSelIt; + } + ++x; + + //progress info + m_progressStep += r.width(); + if(m_lastProgressReport != (m_progressStep * 100) / m_progressTotalSteps) + { + m_lastProgressReport = (m_progressStep * 100) / m_progressTotalSteps; + emit notifyProgress(m_lastProgressReport); + } + if (m_cancelRequested) { + break; + } + } +} + +void KisTransformWorker::rotate180(KisPaintDeviceSP src, KisPaintDeviceSP dst) +{ + kdDebug() << "Rotating 180\n"; + KisSelectionSP dstSelection; + Q_INT32 pixelSize = src->pixelSize(); + QRect r; + KisColorSpace *cs = src->colorSpace(); + + if(src->hasSelection()) + { + r = src->selection()->selectedExactRect(); + dstSelection = dst->selection(); + } + else + { + r = src->exactBounds(); + dstSelection = new KisSelection(dst); // essentially a dummy to be deleted + } + + for (Q_INT32 y = r.top(); y <= r.bottom(); ++y) { + KisHLineIteratorPixel srcIt = src->createHLineIterator(r.x(), y, r.width(), true); + KisHLineIterator dstIt = dst->createHLineIterator(-r.x() - r.width(), -y, r.width(), true); + KisHLineIterator dstSelIt = dstSelection->createHLineIterator(-r.x() - r.width(), -y, r.width(), true); + + srcIt += r.width() - 1; + while (!dstIt.isDone()) { + if (srcIt.isSelected()) { + memcpy(dstIt.rawData(), srcIt.rawData(), pixelSize); + + // XXX: Should set alpha = alpha*(1-selectedness) + cs->setAlpha(srcIt.rawData(), 0, 1); + } + *(dstSelIt.rawData()) = srcIt.selectedness(); + --srcIt; + ++dstIt; + ++dstSelIt; + } + + //progress info + m_progressStep += r.width(); + if(m_lastProgressReport != (m_progressStep * 100) / m_progressTotalSteps) + { + m_lastProgressReport = (m_progressStep * 100) / m_progressTotalSteps; + emit notifyProgress(m_lastProgressReport); + } + if (m_cancelRequested) { + break; + } + } +} + +template <class iter> iter createIterator(KisPaintDevice *dev, Q_INT32 start, Q_INT32 lineNum, Q_INT32 len); + +template <> KisHLineIteratorPixel createIterator <KisHLineIteratorPixel> +(KisPaintDevice *dev, Q_INT32 start, Q_INT32 lineNum, Q_INT32 len) +{ + return dev->createHLineIterator(start, lineNum, len, true); +} + +template <> KisVLineIteratorPixel createIterator <KisVLineIteratorPixel> +(KisPaintDevice *dev, Q_INT32 start, Q_INT32 lineNum, Q_INT32 len) +{ + return dev->createVLineIterator(lineNum, start, len, true); +} + +template <class iter> void calcDimensions (KisPaintDevice *dev, Q_INT32 &srcStart, Q_INT32 &srcLen, Q_INT32 &firstLine, Q_INT32 &numLines); + +template <> void calcDimensions <KisHLineIteratorPixel> +(KisPaintDevice *dev, Q_INT32 &srcStart, Q_INT32 &srcLen, Q_INT32 &firstLine, Q_INT32 &numLines) +{ + if(dev->hasSelection()) + { + QRect r = dev->selection()->selectedExactRect(); + r.rect(&srcStart, &firstLine, &srcLen, &numLines); + } + else + dev->exactBounds(srcStart, firstLine, srcLen, numLines); +} + +template <> void calcDimensions <KisVLineIteratorPixel> +(KisPaintDevice *dev, Q_INT32 &srcStart, Q_INT32 &srcLen, Q_INT32 &firstLine, Q_INT32 &numLines) +{ + if(dev->hasSelection()) + { + QRect r = dev->selection()->selectedExactRect(); + r.rect(&firstLine, &srcStart, &numLines, &srcLen); + } + else + dev->exactBounds(firstLine, srcStart, numLines, srcLen); +} + +struct FilterValues +{ + Q_UINT8 numWeights; + Q_UINT8 *weight; + ~FilterValues() {delete [] weight;} +}; + +template <class T> void KisTransformWorker::transformPass(KisPaintDevice *src, KisPaintDevice *dst, double floatscale, double shear, Q_INT32 dx, KisFilterStrategy *filterStrategy, bool fixBorderAlpha) +{ + Q_INT32 lineNum,srcStart,firstLine,srcLen,numLines; + Q_INT32 center, begin, end; /* filter calculation variables */ + Q_UINT8 *data; + Q_UINT8 pixelSize = src->pixelSize(); + KisSelectionSP dstSelection; + KisColorSpace * cs = src->colorSpace(); + Q_INT32 scale; + Q_INT32 scaleDenom; + Q_INT32 shearFracOffset; + + if(src->hasSelection()) + dstSelection = dst->selection(); + else + dstSelection = new KisSelection(dst); // essentially a dummy to be deleted + + calcDimensions <T>(src, srcStart, srcLen, firstLine, numLines); + + scale = int(floatscale*srcLen); + scaleDenom = srcLen; + + if(scaleDenom == 0) + return; + + Q_INT32 support = filterStrategy->intSupport(); + Q_INT32 dstLen, dstStart; + Q_INT32 invfscale = 256; + + // handle magnification/minification + if(abs(scale) < scaleDenom) + { + support *= scaleDenom; + support /= scale; + + invfscale *= scale; + invfscale /= scaleDenom; + if(scale < 0) // handle mirroring + { + support = -support; + invfscale = -invfscale; + } + } + + // handle mirroring + if(scale < 0) + dstLen = - scale; + else + dstLen = scale; + + // Calculate extra length (in each side) needed due to shear + Q_INT32 extraLen = (support+256)>>8 + 1; + + Q_UINT8 *tmpLine = new Q_UINT8[(srcLen +2*extraLen)* pixelSize]; + Q_CHECK_PTR(tmpLine); + + Q_UINT8 *tmpSel = new Q_UINT8[srcLen+2*extraLen]; + Q_CHECK_PTR(tmpSel); + + //allocate space for colors + const Q_UINT8 **colors = new const Q_UINT8 *[2*support+1]; + + // Precalculate weights + FilterValues *filterWeights = new FilterValues[256]; + + for(int center = 0; center<256; ++center) + { + Q_INT32 begin = (255 + center - support)>>8; // takes ceiling by adding 255 + Q_INT32 span = ((center + support)>>8) - begin + 1; // takes floor to get end. Subtracts begin to get span + Q_INT32 t = (((begin<<8) - center) * invfscale)>>8; + Q_INT32 dt = invfscale; + filterWeights[center].weight = new Q_UINT8[span]; +//printf("%d (",center); + Q_UINT32 sum=0; + for(int num = 0; num<span; ++num) + { + Q_UINT32 tmpw = filterStrategy->intValueAt(t) * invfscale; + + tmpw >>=8; + filterWeights[center].weight[num] = tmpw; +//printf(" %d=%d,%d",t,filterWeights[center].weight[num],tmpw); + t += dt; + sum+=tmpw; + } +//printf(" )%d sum =%d",span,sum); + if(sum!=255) + { + double fixfactor= 255.0/sum; + sum=0; + for(int num = 0; num<span; ++num) + { + filterWeights[center].weight[num] = int(filterWeights[center].weight[num] * fixfactor); + sum+=filterWeights[center].weight[num]; + } + } + +//printf(" sum2 =%d",sum); + int num = 0; + while(sum<255 && num*2<span) + { + filterWeights[center].weight[span/2 + num]++; + ++sum; + if(sum<255 && num<span/2) + { + filterWeights[center].weight[span/2 - num - 1]++; + ++sum; + } + ++num; + } +//printf(" sum3 =%d\n",sum); + + filterWeights[center].numWeights = span; + } + + for(lineNum = firstLine; lineNum < firstLine+numLines; lineNum++) + { + if(scale < 0) + dstStart = srcStart * scale / scaleDenom - dstLen + dx; + else + dstStart = (srcStart) * scale / scaleDenom + dx; + + shearFracOffset = -int( 256 * (lineNum * shear - floor(lineNum * shear))); + dstStart += int(floor(lineNum * shear)); + + // Build a temporary line + T srcIt = createIterator <T>(src, srcStart - extraLen, lineNum, srcLen+2*extraLen); + Q_INT32 i = 0; + while(!srcIt.isDone()) + { + Q_UINT8 *data; + + data = srcIt.rawData(); + memcpy(&tmpLine[i*pixelSize], data, pixelSize); + + if(srcIt.isSelected()) + { + // XXX: Should set alpha = alpha*(1-selectedness) + cs->setAlpha(data, 0, 1); + tmpSel[i] = 255; + } + else { + tmpSel[i] = 0; + } + ++srcIt; + i++; + } + + T dstIt = createIterator <T>(dst, dstStart, lineNum, dstLen); + T dstSelIt = createIterator <T>(dstSelection, dstStart, lineNum, dstLen); + + i=0; + while(!dstIt.isDone()) + { + if(scaleDenom<2500) + center = ((i<<8) * scaleDenom) / scale; + else + { + if(scaleDenom<46000) // real limit is actually 46340 pixels + center = ((i * scaleDenom) / scale)<<8; + else + center = ((i<<8)/scale * scaleDenom) / scale; // XXX fails for sizes over 2^23 pixels src width + } + + if(scale < 0) + center += srcLen<<8; + + center += 128*scaleDenom/scale;//xxx doesn't work for scale<0; + center += (extraLen<<8) + shearFracOffset; + + // find contributing pixels + begin = (255 + center - support)>>8; // takes ceiling by adding 255 + end = (center + support)>>8; // takes floor + +////printf("sup=%d begin=%d end=%d",support,begin,end); + Q_UINT8 selectedness = tmpSel[center>>8]; + if(selectedness) + { + int num=0; + for(int srcpos = begin; srcpos <= end; ++srcpos) + { + colors[num] = &tmpLine[srcpos*pixelSize]; + num++; + } + data = dstIt.rawData(); + cs->mixColors(colors, filterWeights[center&255].weight, filterWeights[center&255].numWeights, data); + + //possibly fix the alpha of the border if user wants it + if(fixBorderAlpha && (i==0 || i==dstLen-1)) + cs->setAlpha(data, cs->getAlpha(&tmpLine[(center>>8)*pixelSize]), 1); + + data = dstSelIt.rawData(); + *data = selectedness; + } + + ++dstSelIt; + ++dstIt; + i++; + } + + //progress info + m_progressStep += dstLen; + if(m_lastProgressReport != (m_progressStep * 100) / m_progressTotalSteps) + { + m_lastProgressReport = (m_progressStep * 100) / m_progressTotalSteps; + emit notifyProgress(m_lastProgressReport); + } + if (m_cancelRequested) { + break; + } + } + delete [] colors; + delete [] tmpLine; + delete [] tmpSel; + delete [] filterWeights; +} + +bool KisTransformWorker::run() +{ + //progress info + m_cancelRequested = false; + if(m_progress) + m_progress->setSubject(this, true, true); + m_progressTotalSteps = 0; + m_progressStep = 0; + QRect r; + if(m_dev->hasSelection()) + r = m_dev->selection()->selectedExactRect(); + else + r = m_dev->exactBounds(); + + KisPaintDeviceSP tmpdev1 = new KisPaintDevice(m_dev->colorSpace(),"transform_tmpdev1");; + KisPaintDeviceSP tmpdev2 = new KisPaintDevice(m_dev->colorSpace(),"transform_tmpdev2");; + KisPaintDeviceSP tmpdev3 = new KisPaintDevice(m_dev->colorSpace(),"transform_tmpdev2");; + KisPaintDeviceSP srcdev = m_dev; + + double xscale = m_xscale; + double yscale = m_yscale; + double xshear = m_xshear; + double yshear = m_yshear; + double rotation = m_rotation; + Q_INT32 xtranslate = m_xtranslate; + Q_INT32 ytranslate = m_ytranslate; + + if(rotation < 0.0) + rotation = -fmod(-rotation, 2*M_PI) + 2*M_PI; + else + rotation = fmod(rotation, 2*M_PI); + int rotQuadrant = int(rotation /(M_PI/2) + 0.5) & 3; + + // Figure out how we will do the initial right angle rotations + double tmp; + switch(rotQuadrant) + { + default: // just to shut up the compiler + case 0: + m_progressTotalSteps = 0; + break; + case 1: + rotation -= M_PI/2; + tmp = xscale; + xscale=yscale; + yscale=tmp; + m_progressTotalSteps = r.width() * r.height(); + break; + case 2: + rotation -= M_PI; + m_progressTotalSteps = r.width() * r.height(); + break; + case 3: + rotation -= -M_PI/2 + 2*M_PI; + tmp = xscale; + xscale = yscale; + yscale = tmp; + m_progressTotalSteps = r.width() * r.height(); + break; + } + + // Calculate some auxillary values + yshear = sin(rotation); + xshear = -tan(rotation/2); + xtranslate -= int(xshear*ytranslate); + + // Calculate progress steps + m_progressTotalSteps += int(yscale * r.width() * r.height()); + m_progressTotalSteps += int(xscale * r.width() * (r.height() * yscale + r.width()*yshear)); + + m_lastProgressReport=0; + + // Now that we have everything in place it's time to do the actual right angle rotations + switch(rotQuadrant) + { + default: // just to shut up the compiler + case 0: + break; + case 1: + rotateRight90(srcdev, tmpdev1); + srcdev = tmpdev1; + break; + case 2: + rotate180(srcdev, tmpdev1); + srcdev = tmpdev1; + break; + case 3: + rotateLeft90(srcdev, tmpdev1); + srcdev = tmpdev1; + break; + } + + // Handle simple move case possibly with rotation of 90,180,270 + if(rotation == 0.0 && xscale == 1.0 && yscale == 1.0) + { + if(rotQuadrant==0) + { + // Though not nessesay in the general case because we make several passes + // We need to move (not just copy) the data to a temp dev so we can move them back + rotateNone(srcdev, tmpdev1); + srcdev = tmpdev1; + } + if(m_dev->hasSelection()) + m_dev->selection()->clear(); + + srcdev->move(srcdev->getX() + xtranslate, srcdev->getY() + ytranslate); + rotateNone(srcdev, m_dev); + + //progress info + emit notifyProgressDone(); + m_dev->emitSelectionChanged(); + + return m_cancelRequested; + } + + if ( m_cancelRequested) { + emit notifyProgressDone(); + return false; + } + + transformPass <KisHLineIteratorPixel>(srcdev, tmpdev2, xscale, yscale*xshear, 0, m_filter, m_fixBorderAlpha); + if(m_dev->hasSelection()) + m_dev->selection()->clear(); + + if ( m_cancelRequested) { + emit notifyProgressDone(); + return false; + } + + // Now do the second pass + transformPass <KisVLineIteratorPixel>(tmpdev2.data(), tmpdev3.data(), yscale, yshear, ytranslate, m_filter, m_fixBorderAlpha); + + if(m_dev->hasSelection()) + m_dev->selection()->clear(); + + if ( m_cancelRequested) { + emit notifyProgressDone(); + return false; + } + + if (xshear != 0.0) + transformPass <KisHLineIteratorPixel>(tmpdev3, m_dev, 1.0, xshear, xtranslate, m_filter, m_fixBorderAlpha); + else + { + // No need to filter again when we are only scaling + tmpdev3->move(tmpdev3->getX() + xtranslate, tmpdev3->getY()); + rotateNone(tmpdev3, m_dev); + } + + if (m_dev->parentLayer()) { + m_dev->parentLayer()->setDirty(); + } + //progress info + emit notifyProgressDone(); + m_dev->emitSelectionChanged(); + + return m_cancelRequested; +} diff --git a/krita/core/kis_transform_worker.h b/krita/core/kis_transform_worker.h new file mode 100644 index 00000000..c357390e --- /dev/null +++ b/krita/core/kis_transform_worker.h @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2004 Michael Thaler <michael.thaler@physik.tu-muenchen.de> + * Copyright (c) 2005 Casper Boemann <cbr@boemann.dk> + * + * 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_TRANSFORM_WORKER_H_ +#define KIS_TRANSFORM_WORKER_H_ + +#include "kis_types.h" +#include "kis_progress_subject.h" + +class KisPaintDevice; +class KisProgressDisplayInterface; +class KisHLineIteratorPixel; +class KisVLineIteratorPixel; +class KisFilterStrategy; + +class KisTransformWorker : public KisProgressSubject { + typedef KisProgressSubject super; + +public: + KisTransformWorker(KisPaintDeviceSP dev, double xscale, double yscale, + double xshear, double yshear, double rotation, + Q_INT32 xtranslate, Q_INT32 ytranslate, + KisProgressDisplayInterface *progress, KisFilterStrategy *filter, bool fixBorderAlpha=false); + ~KisTransformWorker(); + +public: + bool isCanceled() { return m_cancelRequested;}; + + bool run(); + +private: + // XXX (BSAR): Why didn't we use the shared-pointer versions of the paint device classes? + template <class T> void transformPass(KisPaintDevice *src, KisPaintDevice *dst, double xscale, double shear, Q_INT32 dx, KisFilterStrategy *filterStrategy, bool fixBorderAlpha); + +public: + void rotateNone(KisPaintDeviceSP src, KisPaintDeviceSP dst); + void rotateRight90(KisPaintDeviceSP src, KisPaintDeviceSP dst); + void rotateLeft90(KisPaintDeviceSP src, KisPaintDeviceSP dst); + void rotate180(KisPaintDeviceSP src, KisPaintDeviceSP dst); + +private: + KisPaintDeviceSP m_dev; + double m_xscale, m_yscale; + double m_xshear, m_yshear, m_rotation; + Q_INT32 m_xtranslate, m_ytranslate; + KisProgressDisplayInterface *m_progress; + KisFilterStrategy *m_filter; + // Implement KisProgressSubject + bool m_cancelRequested; + virtual void cancel() { m_cancelRequested = true; } + Q_INT32 m_progressTotalSteps; + Q_INT32 m_progressStep; + Q_INT32 m_progressScaler; + Q_INT32 m_lastProgressReport; + bool m_fixBorderAlpha; +}; + + +inline KisTransformWorker::~KisTransformWorker() +{ +} + +#endif // KIS_TRANSFORM_VISITOR_H_ diff --git a/krita/core/kis_types.h b/krita/core/kis_types.h new file mode 100644 index 00000000..94e066fc --- /dev/null +++ b/krita/core/kis_types.h @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2002 Patrick Julien <freak@codepimps.org> + * + * 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 KISTYPES_H_ +#define KISTYPES_H_ + +#include <qvaluevector.h> +#include <qmap.h> +#include <qpoint.h> + +#include <ksharedptr.h> + +#include "kis_shared_ptr_vector.h" + +/** + * Define lots of shared pointer versions of Krita classes. + * Shared pointer classes have the advantage of near automatic + * memory management (but take care of circular references) + * and the disadvantage that inheritiance relations are no longer + * recognizable + */ + +class KisImage; +typedef KSharedPtr<KisImage> KisImageSP; + +class KisPaintDevice; +typedef KSharedPtr<KisPaintDevice> KisPaintDeviceSP; +typedef KisSharedPtrVector<KisPaintDevice> vKisPaintDeviceSP; +typedef vKisPaintDeviceSP::iterator vKisPaintDeviceSP_it; +typedef vKisPaintDeviceSP::const_iterator vKisPaintDeviceSP_cit; + +class KisLayer; +typedef KSharedPtr<KisLayer> KisLayerSP; +typedef KisSharedPtrVector<KisLayer> vKisLayerSP; +typedef vKisLayerSP::iterator vKisLayerSP_it; +typedef vKisLayerSP::const_iterator vKisLayerSP_cit; + +class KisPartLayer; +typedef KSharedPtr<KisPartLayer> KisPartLayerSP; + +class KisPaintLayer; +typedef KSharedPtr<KisPaintLayer> KisPaintLayerSP; + +class KisAdjustmentLayer; +typedef KSharedPtr<KisAdjustmentLayer> KisAdjustmentLayerSP; + +class KisGroupLayer; +typedef KSharedPtr<KisGroupLayer> KisGroupLayerSP; + +class KisSelection; +typedef KSharedPtr<KisSelection> KisSelectionSP; + +class KisBackground; +typedef KSharedPtr<KisBackground> KisBackgroundSP; + +class KisSubstrate; +typedef KSharedPtr<KisSubstrate> KisSubstrateSP; + +class KisHistogram; +typedef KSharedPtr<KisHistogram> KisHistogramSP; + +class KisPaintOpFactory; +typedef KSharedPtr<KisPaintOpFactory> KisPaintOpFactorySP; + +typedef QValueVector<QPoint> vKisSegments; + +//class KisGuide; +//typedef KSharedPtr<KisGuide> KisGuideSP; + +class KisAlphaMask; +typedef KSharedPtr<KisAlphaMask> KisAlphaMaskSP; + +class KisFilter; +typedef KSharedPtr<KisFilter> KisFilterSP; + +#endif // KISTYPES_H_ diff --git a/krita/core/kis_vec.cc b/krita/core/kis_vec.cc new file mode 100644 index 00000000..fa54e1f9 --- /dev/null +++ b/krita/core/kis_vec.cc @@ -0,0 +1,67 @@ +/* + * kis_vec.cc - part of KImageShop + * + * Copyright (c) 1999 Matthias Elter <me@kde.org> + * + * 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_vec.h" + +KisVector2D& KisVector2D::normalize() +{ + double length, ilength; + + length = m_x*m_x + m_y*m_y; + length = sqrt (length); + + if (length > epsilon) + { + ilength = 1/length; + m_x *= ilength; + m_y *= ilength; + } + return *this; +} + +KisVector3D& KisVector3D::normalize() +{ + double length, ilength; + + length = m_x*m_x + m_y*m_y + m_z*m_z; + length = sqrt (length); + + if (length > epsilon) + { + ilength = 1/length; + m_x *= ilength; + m_y *= ilength; + m_z *= ilength; + } + return *this; +} + +KisVector3D& KisVector3D::crossProduct(const KisVector3D &v) +{ + double x,y,z; + + x = m_y*v.m_z - m_z*v.m_y; + y = m_z*v.m_x - m_x*v.m_z; + z = m_x*v.m_y - m_y*v.m_x; + m_x=x; m_y=y; m_z=z; + + return *this; +} + diff --git a/krita/core/kis_vec.h b/krita/core/kis_vec.h new file mode 100644 index 00000000..1706dd4e --- /dev/null +++ b/krita/core/kis_vec.h @@ -0,0 +1,405 @@ +/* + * kis_vec.h - part of KImageShop + * + * Copyright (c) 1999 Matthias Elter <me@kde.org> + * + * 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_vec_h__ +#define __kis_vec_h__ + +#include <math.h> +#include <cfloat> +#include <qpoint.h> +#include "kis_point.h" + +/* + * vector classes + */ +const double epsilon = DBL_EPSILON; + +class KisVector2D +{ +public: + KisVector2D(); + KisVector2D(double x, double y); + KisVector2D(const QPoint& p); + KisVector2D(const KisPoint& p); + + bool isNull() const; + + double length() const; + + double x() const; + double y() const; + void setX(double); + void setY(double); + + KisVector2D &normalize(); + double dotProduct(const KisVector2D &) const; + + KisVector2D &operator+=(const KisVector2D &); + KisVector2D &operator-=(const KisVector2D &); + KisVector2D &operator*=(int); + KisVector2D &operator*=(long); + KisVector2D &operator*=(double); + KisVector2D &operator/=(int); + KisVector2D &operator/=(long); + KisVector2D &operator/=(double); + + friend inline bool operator==(const KisVector2D &, const KisVector2D &); + friend inline bool operator!=(const KisVector2D &, const KisVector2D &); + friend inline KisVector2D operator+(const KisVector2D &, const KisVector2D &); + friend inline KisVector2D operator-(const KisVector2D &, const KisVector2D &); + friend inline KisVector2D operator*(const KisVector2D &, int); + friend inline KisVector2D operator*(int, const KisVector2D &); + friend inline KisVector2D operator*(const KisVector2D &, long); + friend inline KisVector2D operator*(long, const KisVector2D &); + friend inline KisVector2D operator*(const KisVector2D &, double); + friend inline KisVector2D operator*(double, const KisVector2D &); + friend inline KisVector2D operator-(const KisVector2D &); + friend inline KisVector2D operator/(const KisVector2D &, int); + friend inline KisVector2D operator/(const KisVector2D &, long); + friend inline KisVector2D operator/(const KisVector2D &, double); + + KisPoint toKisPoint() const; + +private: + double m_x; + double m_y; +}; + +inline KisVector2D::KisVector2D() +{ m_x=0; m_y=0; } + +inline KisVector2D::KisVector2D(double x, double y) +{ m_x=x; m_y=y; } + +inline KisVector2D::KisVector2D(const QPoint& p) +{ + m_x=p.x(); m_y=p.y(); +} + +inline KisVector2D::KisVector2D(const KisPoint& p) +{ + m_x=p.x(); m_y=p.y(); +} + +inline bool KisVector2D::isNull() const +{ return fabs(m_x) < epsilon && fabs(m_y) < epsilon; } + +inline double KisVector2D::length() const +{ return (sqrt(m_x*m_x + m_y*m_y)); } + +inline double KisVector2D::dotProduct(const KisVector2D &v) const +{ return m_x*v.m_x + m_y*v.m_y; } + +inline double KisVector2D::x() const +{ return m_x; } + +inline double KisVector2D::y() const +{ return m_y; } + +inline void KisVector2D::setX(double x) +{ m_x=x; } + +inline void KisVector2D::setY(double y) +{ m_y=y; } + +inline KisVector2D &KisVector2D::operator+=(const KisVector2D &v) +{ m_x+=v.m_x; m_y+=v.m_y; return *this; } + +inline KisVector2D &KisVector2D::operator-=(const KisVector2D &v) +{ m_x-=v.m_x; m_y-=v.m_y; return *this; } + +inline KisVector2D &KisVector2D::operator*=(int c) +{ m_x*=c; m_y*=c; return *this; } + +inline KisVector2D &KisVector2D::operator*=(long c) +{ m_x*=c; m_y*=c; return *this; } + +inline KisVector2D &KisVector2D::operator*=(double c) +{ m_x*=c; m_y*=c; return *this; } + +inline bool operator==(const KisVector2D &v1, const KisVector2D &v2) +{ return fabs(v1.m_x - v2.m_x) < epsilon && fabs(v1.m_y - v2.m_y) < epsilon; } + +inline bool operator!=(const KisVector2D &v1, const KisVector2D &v2) +{ return !(v1 == v2); } + +inline KisVector2D operator+(const KisVector2D &v1, const KisVector2D &v2) +{ return KisVector2D(v1.m_x+v2.m_x, v1.m_y+v2.m_y); } + +inline KisVector2D operator-(const KisVector2D &v1, const KisVector2D &v2) +{ return KisVector2D(v1.m_x-v2.m_x, v1.m_y-v2.m_y); } + +inline KisVector2D operator*(const KisVector2D &v, int c) +{ return KisVector2D((v.m_x*c), (v.m_y*c)); } + +inline KisVector2D operator*(int c, const KisVector2D &v) +{ return KisVector2D((v.m_x*c), (v.m_y*c)); } + +inline KisVector2D operator*(const KisVector2D &v, long c) +{ return KisVector2D((v.m_x*c), (v.m_y*c)); } + +inline KisVector2D operator*(long c, const KisVector2D &v) +{ return KisVector2D((v.m_x*c), (v.m_y*c)); } + +inline KisVector2D operator*(const KisVector2D &v, double c) +{ return KisVector2D(v.m_x*c, v.m_y*c); } + +inline KisVector2D operator*(double c, const KisVector2D &v) +{ return KisVector2D(v.m_x*c, v.m_y*c); } + +inline KisVector2D operator-(const KisVector2D &v) +{ return KisVector2D(-v.m_x, -v.m_y); } + +inline KisVector2D operator/(const KisVector2D &v, int c) +{ + if (c != 0) { + return KisVector2D(v.x() / c, v.y() / c); + } else { + return v; + } +} + +inline KisVector2D operator/(const KisVector2D &v, long c) +{ + if (c != 0) { + return KisVector2D(v.x() / c, v.y() / c); + } else { + return v; + } +} + +inline KisVector2D operator/(const KisVector2D &v, double c) +{ + if (c > DBL_EPSILON || c < -DBL_EPSILON) { + return KisVector2D(v.x() / c, v.y() / c); + } else { + return v; + } +} + +inline KisVector2D &KisVector2D::operator/=(int c) +{ + if (!c == 0) + { + m_x/=c; + m_y/=c; + } + return *this; +} + +inline KisVector2D &KisVector2D::operator/=(long c) +{ + if (!c == 0) + { + m_x/=c; + m_y/=c; + } + return *this; +} + +inline KisVector2D &KisVector2D::operator/=(double c) +{ + if (!c == 0) + { + m_x/=c; + m_y/=c; + } + return *this; +} + +inline KisPoint KisVector2D::toKisPoint() const +{ + return KisPoint(m_x, m_y); +} + +class KisVector3D +{ +public: + KisVector3D(); + KisVector3D(double x, double y, double z = 0); + KisVector3D(int x, int y, int z = 0); + KisVector3D(long x, long y, long z = 0); + + bool isNull() const; + + double length() const; + + double x() const; + double y() const; + double z() const; + void setX(double); + void setY(double); + void setZ(double); + + KisVector3D &normalize(); + KisVector3D &crossProduct(const KisVector3D &); + double dotProduct(const KisVector3D &) const; + + KisVector3D &operator+=(const KisVector3D &); + KisVector3D &operator-=(const KisVector3D &); + KisVector3D &operator*=(int); + KisVector3D &operator*=(long); + KisVector3D &operator*=(double); + KisVector3D &operator/=(int); + KisVector3D &operator/=(long); + KisVector3D &operator/=(double); + + friend inline bool operator==(const KisVector3D &, const KisVector3D &); + friend inline bool operator!=(const KisVector3D &, const KisVector3D &); + friend inline KisVector3D operator+(const KisVector3D &, const KisVector3D &); + friend inline KisVector3D operator-(const KisVector3D &, const KisVector3D &); + friend inline KisVector3D operator*(const KisVector3D &, int); + friend inline KisVector3D operator*(int, const KisVector3D &); + friend inline KisVector3D operator*(const KisVector3D &, long); + friend inline KisVector3D operator*(long, const KisVector3D &); + friend inline KisVector3D operator*(const KisVector3D &, double); + friend inline KisVector3D operator*(double, const KisVector3D &); + friend inline KisVector3D operator-(const KisVector3D &); + friend inline KisVector3D operator/(const KisVector3D &, int); + friend inline KisVector3D operator/(const KisVector3D &, long); + friend inline KisVector3D operator/(const KisVector3D &, double); + +private: + double m_x; + double m_y; + double m_z; +}; + +inline KisVector3D::KisVector3D() +{ m_x=0; m_y=0; m_z=0; } + +inline KisVector3D::KisVector3D(double x, double y, double z) +{ m_x=x; m_y=y; m_z=z; } + +inline KisVector3D::KisVector3D(int x, int y, int z) +{ m_x=static_cast<double>(x); m_y=static_cast<double>(y); m_z=static_cast<double>(z); } + +inline KisVector3D::KisVector3D(long x, long y, long z) +{ m_x=static_cast<double>(x); m_y=static_cast<double>(y); m_z=static_cast<double>(z); } + +inline bool KisVector3D::isNull() const +{ return fabs(m_x) < epsilon && fabs(m_y) < epsilon && fabs(m_z) < epsilon; } + +inline double KisVector3D::length() const +{ return (sqrt(m_x*m_x + m_y*m_y + m_z*m_z)); } + +inline double KisVector3D::dotProduct(const KisVector3D &v) const +{ return m_x*v.m_x + m_y*v.m_y + m_z*v.m_z; } + +inline double KisVector3D::x() const +{ return m_x; } + +inline double KisVector3D::y() const +{ return m_y; } + +inline double KisVector3D::z() const +{ return m_z; } + +inline void KisVector3D::setX(double x) +{ m_x=x; } + +inline void KisVector3D::setY(double y) +{ m_y=y; } + +inline void KisVector3D::setZ(double z) +{ m_z=z; } + +inline KisVector3D &KisVector3D::operator+=(const KisVector3D &v) +{ m_x+=v.m_x; m_y+=v.m_y; m_z+=v.m_z; return *this; } + +inline KisVector3D &KisVector3D::operator-=(const KisVector3D &v) +{ m_x-=v.m_x; m_y-=v.m_y; m_z-=v.m_z; return *this; } + +inline KisVector3D &KisVector3D::operator*=(int c) +{ m_x*=c; m_y*=c; m_z*=c; return *this; } + +inline KisVector3D &KisVector3D::operator*=(long c) +{ m_x*=c; m_y*=c; m_z*=c; return *this; } + +inline KisVector3D &KisVector3D::operator*=(double c) +{ m_x*=c; m_y*=c; m_z*=c; return *this; } + +inline bool operator==(const KisVector3D &v1, const KisVector3D &v2) +{ return fabs(v1.m_x - v2.m_x) < epsilon && fabs(v1.m_y - v2.m_y) < epsilon && fabs(v1.m_z - v2.m_z) < epsilon; } + +inline bool operator!=(const KisVector3D &v1, const KisVector3D &v2) +{ return !(v1 == v2); } + +inline KisVector3D operator+(const KisVector3D &v1, const KisVector3D &v2) +{ return KisVector3D(v1.m_x+v2.m_x, v1.m_y+v2.m_y, v1.m_z+v2.m_z); } + +inline KisVector3D operator-(const KisVector3D &v1, const KisVector3D &v2) +{ return KisVector3D(v1.m_x-v2.m_x, v1.m_y-v2.m_y, v1.m_z-v2.m_z); } + +inline KisVector3D operator*(const KisVector3D &v, int c) +{ return KisVector3D((v.m_x*c), (v.m_y*c), (v.m_z*c)); } + +inline KisVector3D operator*(int c, const KisVector3D &v) +{ return KisVector3D((v.m_x*c), (v.m_y*c), (v.m_z*c)); } + +inline KisVector3D operator*(const KisVector3D &v, long c) +{ return KisVector3D((v.m_x*c), (v.m_y*c), (v.m_z*c)); } + +inline KisVector3D operator*(long c, const KisVector3D &v) +{ return KisVector3D((v.m_x*c), (v.m_y*c), (v.m_z*c)); } + +inline KisVector3D operator*(const KisVector3D &v, double c) +{ return KisVector3D(v.m_x*c, v.m_y*c, v.m_z*c); } + +inline KisVector3D operator*(double c, const KisVector3D &v) +{ return KisVector3D(v.m_x*c, v.m_y*c, v.m_z*c); } + +inline KisVector3D operator-(const KisVector3D &v) +{ return KisVector3D(-v.m_x, -v.m_y, -v.m_z); } + +inline KisVector3D &KisVector3D::operator/=(int c) +{ + if (!c == 0) + { + m_x/=c; + m_y/=c; + m_z/=c; + } + return *this; +} + +inline KisVector3D &KisVector3D::operator/=(long c) +{ + if (!c == 0) + { + m_x/=c; + m_y/=c; + m_z/=c; + } + return *this; +} + +inline KisVector3D &KisVector3D::operator/=(double c) +{ + if (!c == 0) + { + m_x/=c; + m_y/=c; + m_z/=c; + } + return *this; +} + +#endif diff --git a/krita/core/tests/Makefile.am b/krita/core/tests/Makefile.am new file mode 100644 index 00000000..de1a869a --- /dev/null +++ b/krita/core/tests/Makefile.am @@ -0,0 +1,30 @@ +AM_CPPFLAGS = \ + -I$(srcdir)/../ \ + -I$(srcdir)/../tiles \ + -I$(srcdir)/../../sdk \ + -I$(srcdir)/../../kritacolor \ + -I$(srcdir)/../../colorspaces/rgb_u8 \ + $(KOFFICE_INCLUDES) \ + $(KOPAINTER_INCLUDES) \ + $(all_includes) + +# The check_ target makes sure we don't install the modules, +# $(KDE_CHECK_PLUGIN) assures a shared library is created. +check_LTLIBRARIES = kunittest_kis_integer_maths_tester.la kunittest_kis_image_tester.la kunittest_kis_filter_configuration_tester.la + +kunittest_kis_integer_maths_tester_la_SOURCES = kis_integer_maths_tester.cpp +kunittest_kis_integer_maths_tester_la_LIBADD = -lkunittest ../../libkritacommon.la +kunittest_kis_integer_maths_tester_la_LDFLAGS = -module $(KDE_CHECK_PLUGIN) $(all_libraries) + +kunittest_kis_image_tester_la_SOURCES = kis_image_tester.cpp +kunittest_kis_image_tester_la_LIBADD = -lkunittest ../../libkritacommon.la +kunittest_kis_image_tester_la_LDFLAGS = -module $(KDE_CHECK_PLUGIN) $(all_libraries) + + +kunittest_kis_filter_configuration_tester_la_SOURCES = kis_filter_configuration_tester.cc +kunittest_kis_filter_configuration_tester_la_LIBADD = -lkunittest ../../libkritacommon.la +kunittest_kis_filter_configuration_tester_la_LDFLAGS = -module $(KDE_CHECK_PLUGIN) $(all_libraries) + + +check-local: kunittest_kis_integer_maths_tester.la kunittest_kis_image_tester.la kunittest_kis_filter_configuration_tester.la + kunittestmodrunner diff --git a/krita/core/tests/kis_filter_configuration_tester.cc b/krita/core/tests/kis_filter_configuration_tester.cc new file mode 100644 index 00000000..10497692 --- /dev/null +++ b/krita/core/tests/kis_filter_configuration_tester.cc @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2006 Boudewijn Rempt <boud@valdyas.org> + * + * 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 <qapplication.h> + +#include <kdebug.h> +#include <kunittest/runner.h> +#include <kunittest/module.h> + +#include "kis_filter_configuration_tester.h" +#include "../kis_filter_configuration.h" + +using namespace KUnitTest; + +KUNITTEST_MODULE(kunittest_kis_filter_configuration_tester, "KisFilterConfiguration Tester"); +KUNITTEST_MODULE_REGISTER_TESTER(KisFilterConfigurationTester); + +void KisFilterConfigurationTester::allTests() +{ + testCreation(); + testSetGetProperty(); + testRoundTrip(); +} + +void KisFilterConfigurationTester::testCreation() +{ + KisFilterConfiguration * kfc = new KisFilterConfiguration("test", 1); + if ( kfc == 0 ) failure("Could not create test filter configuration"); + CHECK(kfc->version(), 1); + CHECK(kfc->name(), QString("test")); + + delete kfc; + success("testCreation success"); +} + +void KisFilterConfigurationTester::testRoundTrip() +{ + KisFilterConfiguration * kfc = new KisFilterConfiguration("test", 1); + CHECK(kfc->version(), 1); + CHECK(kfc->name(), QString("test")); + QString s = kfc->toString(); + delete kfc; + kfc = new KisFilterConfiguration(s); + CHECK(kfc->version(), 1); + CHECK(kfc->name(), QString("test")); + delete kfc; + success("testDeserializaton success"); +} + +void KisFilterConfigurationTester::testSetGetProperty() +{ +} diff --git a/krita/core/tests/kis_filter_configuration_tester.h b/krita/core/tests/kis_filter_configuration_tester.h new file mode 100644 index 00000000..6e1ca2e5 --- /dev/null +++ b/krita/core/tests/kis_filter_configuration_tester.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2006 Boudewijn Rempt <boud@valdyas.org> + * + * 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_FILTER_CONFIGURATION_TESTER_H +#define KIS_FILTER_CONFIGURATION_TESTER_H + +#include <kunittest/tester.h> + +class KisFilterConfigurationTester : public KUnitTest::Tester +{ +public: + void allTests(); + void testCreation(); + void testRoundTrip(); + void testSetGetProperty(); +}; + +#endif + diff --git a/krita/core/tests/kis_image_tester.cpp b/krita/core/tests/kis_image_tester.cpp new file mode 100644 index 00000000..ee180787 --- /dev/null +++ b/krita/core/tests/kis_image_tester.cpp @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2005 Adrian Page <adrian@pagenet.plus.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 <qapplication.h> + +#include <kunittest/runner.h> +#include <kunittest/module.h> + +#include "kis_image_tester.h" +#include "kis_image.h" +#include "kis_meta_registry.h" +#include "kis_rgb_colorspace.h" +#include "kis_colorspace_factory_registry.h" +#include "kis_color.h" +#include "kis_paint_layer.h" +#include "kis_group_layer.h" + +using namespace KUnitTest; + +KUNITTEST_MODULE(kunittest_kis_image_tester, "KisImage Tester"); +KUNITTEST_MODULE_REGISTER_TESTER(KisImageTester); + +void KisImageTester::allTests() +{ + mergeTests(); +} + +#define IMAGE_WIDTH 1 +#define IMAGE_HEIGHT 1 + +void KisImageTester::mergeTests() +{ + KisColorSpace * colorSpace = KisMetaRegistry::instance()->csRegistry()->getColorSpace(KisID("RGBA", ""), ""); + + KisImageSP image = new KisImage(0, IMAGE_WIDTH, IMAGE_HEIGHT, colorSpace, "merge test"); + + KisColor mergedPixel = image->mergedPixel(0, 0); + + QColor colour; + Q_UINT8 opacity; + + mergedPixel.toQColor(&colour, &opacity); + + CHECK(opacity, OPACITY_TRANSPARENT); + + KisPaintLayer * layer = new KisPaintLayer(image, "layer 1", OPACITY_OPAQUE); + image->addLayer(layer, image->rootLayer(), 0); + + layer->paintDevice()->setPixel(0, 0, QColor(255, 128, 64), OPACITY_OPAQUE); + + mergedPixel = image->mergedPixel(0, 0); + mergedPixel.toQColor(&colour, &opacity); + + CHECK(opacity, OPACITY_OPAQUE); + CHECK(colour.red(), 255); + CHECK(colour.green(), 128); + CHECK(colour.blue(), 64); + + KisPaintLayer * layer2 = new KisPaintLayer(image, "layer 2", OPACITY_OPAQUE / 2); + image->addLayer(layer2, image->rootLayer(), layer); + + layer2->paintDevice()->setPixel(0, 0, QColor(255, 255, 255), OPACITY_OPAQUE); + + mergedPixel = image->mergedPixel(0, 0); + mergedPixel.toQColor(&colour, &opacity); + + CHECK(opacity, OPACITY_OPAQUE); + CHECK(colour.red(), 255); + CHECK(colour.green(), 128 + ((255 - 128) / 2)); + CHECK(colour.blue(), 64 + ((255 - 64) / 2)); +} + + diff --git a/krita/core/tests/kis_image_tester.h b/krita/core/tests/kis_image_tester.h new file mode 100644 index 00000000..4e676838 --- /dev/null +++ b/krita/core/tests/kis_image_tester.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2005 Adrian Page <adrian@pagenet.plus.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_IMAGE_TESTER_H +#define KIS_IMAGE_TESTER_H + +#include <kunittest/tester.h> + +class KisImageTester : public KUnitTest::Tester +{ +public: + void allTests(); + void mergeTests(); +}; + +#endif + diff --git a/krita/core/tests/kis_integer_maths_tester.cpp b/krita/core/tests/kis_integer_maths_tester.cpp new file mode 100644 index 00000000..1878731b --- /dev/null +++ b/krita/core/tests/kis_integer_maths_tester.cpp @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2005 Adrian Page <adrian@pagenet.plus.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 <kunittest/runner.h> +#include <kunittest/module.h> + +#include "kis_integer_maths_tester.h" +#include "kis_integer_maths.h" + +using namespace KUnitTest; + +KUNITTEST_MODULE(kunittest_kis_integer_maths_tester, "Integer Maths Tester"); +KUNITTEST_MODULE_REGISTER_TESTER(KisIntegerMathsTester); + +void KisIntegerMathsTester::allTests() +{ + UINT8Tests(); + UINT16Tests(); + conversionTests(); +} + +void KisIntegerMathsTester::UINT8Tests() +{ + CHECK((int)UINT8_MULT(0, 255), 0); + CHECK((int)UINT8_MULT(255, 255), 255); + + CHECK((int)UINT8_MULT(128, 255), 128); + CHECK((int)UINT8_MULT(255, 128), 128); + + CHECK((int)UINT8_MULT(1, 255), 1); + CHECK((int)UINT8_MULT(1, 127), 0); + CHECK((int)UINT8_MULT(64, 128), 32); + + CHECK((int)UINT8_DIVIDE(255, 255), 255); + CHECK((int)UINT8_DIVIDE(64, 128), 128); + CHECK((int)UINT8_DIVIDE(1, 64), 4); + CHECK((int)UINT8_DIVIDE(0, 1), 0); + + CHECK((int)UINT8_BLEND(255, 0, 0), 0); + CHECK((int)UINT8_BLEND(255, 0, 128), 128); + CHECK((int)UINT8_BLEND(255, 128, 128), 192); + CHECK((int)UINT8_BLEND(128, 64, 255), 128); +} + +void KisIntegerMathsTester::UINT16Tests() +{ + CHECK((int)UINT16_MULT(0, 65535), 0); + CHECK((int)UINT16_MULT(65535, 65535), 65535); + + CHECK((int)UINT16_MULT(32768, 65535), 32768); + CHECK((int)UINT16_MULT(65535, 32768), 32768); + + CHECK((int)UINT16_MULT(1, 65535), 1); + CHECK((int)UINT16_MULT(1, 32767), 0); + CHECK((int)UINT16_MULT(16384, 32768), 8192); + + CHECK((int)UINT16_DIVIDE(65535, 65535), 65535); + CHECK((int)UINT16_DIVIDE(16384, 32768), 32768); + CHECK((int)UINT16_DIVIDE(1, 16384), 4); + CHECK((int)UINT16_DIVIDE(0, 1), 0); + + CHECK((int)UINT16_BLEND(65535, 0, 0), 0); + CHECK((int)UINT16_BLEND(65535, 0, 32768), 32768); + CHECK((int)UINT16_BLEND(65535, 32768, 32768), 49152); + CHECK((int)UINT16_BLEND(32768, 16384, 65535), 32768); +} + +void KisIntegerMathsTester::conversionTests() +{ + CHECK((int)UINT8_TO_UINT16(255), 65535); + CHECK((int)UINT8_TO_UINT16(0), 0); + CHECK((int)UINT8_TO_UINT16(128), 128 * 257); + + CHECK((int)UINT16_TO_UINT8(65535), 255); + CHECK((int)UINT16_TO_UINT8(0), 0); + CHECK((int)UINT16_TO_UINT8(128 * 257), 128); +} + diff --git a/krita/core/tests/kis_integer_maths_tester.h b/krita/core/tests/kis_integer_maths_tester.h new file mode 100644 index 00000000..2a9a9a20 --- /dev/null +++ b/krita/core/tests/kis_integer_maths_tester.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2005 Adrian Page <adrian@pagenet.plus.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_INTEGER_MATHS_TESTER_H +#define KIS_INTEGER_MATHS_TESTER_H + +#include <kunittest/tester.h> + +class KisIntegerMathsTester : public KUnitTest::Tester +{ +public: + void allTests(); + void UINT8Tests(); + void UINT16Tests(); + void conversionTests(); +}; + +#endif + diff --git a/krita/core/tiles/Makefile.am b/krita/core/tiles/Makefile.am new file mode 100644 index 00000000..050cb4a4 --- /dev/null +++ b/krita/core/tiles/Makefile.am @@ -0,0 +1,23 @@ +if include_kunittest_tests +TESTSDIR = tests +endif + +SUBDIRS = . $(TESTSDIR) + +INCLUDES = -I$(srcdir)/../ \ + -I$(srcdir)/../../sdk \ + $(KOFFICE_INCLUDES) \ + -I$(interfacedir) \ + $(all_includes) + +noinst_LTLIBRARIES = libkritatile.la + +libkritatile_la_SOURCES = kis_tiledvlineiterator.cc kis_tiledhlineiterator.cc \ + kis_tileddatamanager.cc kis_tile.cc kis_tilediterator.cc kis_tiledrectiterator.cc \ + kis_memento.cc kis_tilemanager.cc kis_tiled_random_accessor.cc + +libkritatile_la_METASOURCES = AUTO + +include_HEADERS = \ + kis_tileddatamanager.h +noinst_HEADERS = kis_tiled_random_accessor.h diff --git a/krita/core/tiles/kis_memento.cc b/krita/core/tiles/kis_memento.cc new file mode 100644 index 00000000..ea6ed722 --- /dev/null +++ b/krita/core/tiles/kis_memento.cc @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2005 Casper Boemann <cbr@boemann.dk> + * + * 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_global.h" +#include "kis_memento.h" +#include "kis_tile.h" +#include "kis_tile_global.h" + +KisMemento::KisMemento(Q_UINT32 pixelSize) : KShared() +{ + m_hashTable = new KisTile * [1024]; + Q_CHECK_PTR(m_hashTable); + + m_redoHashTable = new KisTile * [1024]; + Q_CHECK_PTR(m_redoHashTable); + + for(int i = 0; i < 1024; i++) + { + m_hashTable [i] = 0; + m_redoHashTable [i] = 0; + } + m_numTiles = 0; + m_defPixel = new Q_UINT8[pixelSize]; + m_redoDefPixel = new Q_UINT8[pixelSize]; + m_valid = true; +} + +KisMemento::~KisMemento() +{ + // Deep delete every tile + for(int i = 0; i < 1024; i++) + { + deleteAll(m_hashTable[i]); + deleteAll(m_redoHashTable[i]); + } + delete [] m_hashTable; + delete [] m_redoHashTable; + + // Delete defPixel arrays; + delete [] m_defPixel; + delete [] m_redoDefPixel; +} + +KisMemento::DeletedTileList::~DeletedTileList() +{ + clear(); +} + +void KisMemento::DeletedTileList::clear() +{ + // They are not tiles just references. The actual tiles have already been deleted, + // so just delete the references. + + const DeletedTile *deletedTile = m_firstDeletedTile; + + while (deletedTile) + { + const DeletedTile *d = deletedTile; + deletedTile = deletedTile->next(); + delete d; + } + + m_firstDeletedTile = 0; +} + +void KisMemento::deleteAll(KisTile *tile) +{ + while(tile) + { + KisTile *deltile = tile; + tile = tile->getNext(); + delete deltile; + } +} + +void KisMemento::extent(Q_INT32 &x, Q_INT32 &y, Q_INT32 &w, Q_INT32 &h) const +{ + Q_INT32 maxX = Q_INT32_MIN; + Q_INT32 maxY = Q_INT32_MIN; + x = Q_INT32_MAX; + y = Q_INT32_MAX; + + for(int i = 0; i < 1024; i++) + { + KisTile *tile = m_hashTable[i]; + + while(tile) + { + if(x > tile->getCol() * KisTile::WIDTH) + x = tile->getCol() * KisTile::WIDTH; + if(maxX < (tile->getCol() + 1) * KisTile::WIDTH - 1) + maxX = (tile->getCol() + 1) * KisTile::WIDTH - 1; + if(y > tile->getRow() * KisTile::HEIGHT) + y = tile->getRow() * KisTile::HEIGHT; + if(maxY < (tile->getRow() +1) * KisTile::HEIGHT - 1) + maxY = (tile->getRow() +1) * KisTile::HEIGHT - 1; + + tile = tile->getNext(); + } + } + + if(maxX < x) + w = 0; + else + w = maxX - x +1; + + if(maxY < y) + h = 0; + else + h = maxY - y +1; +} + +QRect KisMemento::extent() const +{ + Q_INT32 x; + Q_INT32 y; + Q_INT32 w; + Q_INT32 h; + + extent(x, y, w, h); + + return QRect(x, y, w, h); +} + +bool KisMemento::containsTile(Q_INT32 col, Q_INT32 row, Q_UINT32 tileHash) const +{ + const KisTile *tile = m_hashTable[tileHash]; + + while (tile != 0) + { + if (tile->getRow() == row && tile->getCol() == col) { + return true; + } + + tile = tile->getNext(); + } + + return false; +} + diff --git a/krita/core/tiles/kis_memento.h b/krita/core/tiles/kis_memento.h new file mode 100644 index 00000000..696f8129 --- /dev/null +++ b/krita/core/tiles/kis_memento.h @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2005 Casper Boemann <cbr@boemann.dk> + * + * 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_MEMENTO_H_ +#define KIS_MEMENTO_H_ + +#include <qglobal.h> +#include <qrect.h> + +#include <ksharedptr.h> + +class KisTile; +class KisTiledDataManager; + +class KisMemento; +typedef KSharedPtr<KisMemento> KisMementoSP; + +class KisMemento : public KShared +{ +public: + KisMemento(Q_UINT32 pixelSize); + ~KisMemento(); +/* + // For consolidating transactions + virtual KisTransaction &operator+=(const KisTransaction &) = 0; + // For consolidating transactions + virtual KisTransaction &operator+(const KisTransaction &, + const KisTransaction &) = 0; +*/ + void extent(Q_INT32 &x, Q_INT32 &y, Q_INT32 &w, Q_INT32 &h) const; + QRect extent() const; + + bool containsTile(Q_INT32 col, Q_INT32 row, Q_UINT32 tileHash) const; + + // For debugging use + bool valid() const { return m_valid; } + void setInvalid() { m_valid = false; } + +private: + + class DeletedTile { + public: + DeletedTile(Q_INT32 col, Q_INT32 row, const DeletedTile *next) + : m_col(col), + m_row(row), + m_next(next) + { + } + + Q_INT32 col() const { return m_col; } + Q_INT32 row() const { return m_row; } + const DeletedTile *next() const { return m_next; } + + private: + Q_INT32 m_col; + Q_INT32 m_row; + const DeletedTile *m_next; + }; + + class DeletedTileList { + public: + DeletedTileList() + : m_firstDeletedTile(0) + { + } + + ~DeletedTileList(); + + void addTile(Q_INT32 col, Q_INT32 row) + { + DeletedTile *d = new DeletedTile(col, row, m_firstDeletedTile); + Q_CHECK_PTR(d); + + m_firstDeletedTile = d; + } + + DeletedTile *firstTile() const + { + return m_firstDeletedTile; + } + + void clear(); + + private: + DeletedTile *m_firstDeletedTile; + }; + + void addTileToDeleteOnRedo(Q_INT32 col, Q_INT32 row) + { + m_redoDelTilesList.addTile(col, row); + } + + DeletedTile *tileListToDeleteOnRedo() + { + return m_redoDelTilesList.firstTile(); + } + + void clearTilesToDeleteOnRedo() + { + m_redoDelTilesList.clear(); + } + + void addTileToDeleteOnUndo(Q_INT32 col, Q_INT32 row) + { + m_undoDelTilesList.addTile(col, row); + } + + DeletedTile *tileListToDeleteOnUndo() + { + return m_undoDelTilesList.firstTile(); + } + + void clearTilesToDeleteOnUndo() + { + m_undoDelTilesList.clear(); + } + + friend class KisTiledDataManager; + KisTiledDataManager *originator; + KisTile **m_hashTable; + Q_UINT32 m_numTiles; + KisTile **m_redoHashTable; + DeletedTileList m_redoDelTilesList; + DeletedTileList m_undoDelTilesList; + Q_UINT8 *m_defPixel; + Q_UINT8 *m_redoDefPixel; + void deleteAll(KisTile *tile); + + bool m_valid; +}; + +#endif // KIS_MEMENTO_H_ diff --git a/krita/core/tiles/kis_tile.cc b/krita/core/tiles/kis_tile.cc new file mode 100644 index 00000000..49d20cf6 --- /dev/null +++ b/krita/core/tiles/kis_tile.cc @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2002 Patrick Julien <freak@codepimps.org> + * + * 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 <assert.h> +#include <kdebug.h> + +#include "kis_tile_global.h" +#include "kis_tile.h" +#include "kis_tileddatamanager.h" +#include "kis_tilemanager.h" + +const Q_INT32 KisTile::WIDTH = 64; +const Q_INT32 KisTile::HEIGHT = 64; + + +KisTile::KisTile(Q_INT32 pixelSize, Q_INT32 col, Q_INT32 row, const Q_UINT8 *defPixel) +{ + m_pixelSize = pixelSize; + m_data = 0; + m_nextTile = 0; + m_col = col; + m_row = row; + m_nReadlock = 0; + + allocate(); + + KisTileManager::instance()->registerTile(this); + + setData(defPixel); +} + +KisTile::KisTile(const KisTile& rhs, Q_INT32 col, Q_INT32 row) +{ + if (this != &rhs) { + m_pixelSize = rhs.m_pixelSize; + m_data = 0; + m_nextTile = 0; + m_nReadlock = 0; + + allocate(); + + // Assure we have data to copy + rhs.addReader(); + memcpy(m_data, rhs.m_data, WIDTH * HEIGHT * m_pixelSize * sizeof(Q_UINT8)); + rhs.removeReader(); + + m_col = col; + m_row = row; + + KisTileManager::instance()->registerTile(this); + } +} + +KisTile::KisTile(const KisTile& rhs) +{ + if (this != &rhs) { + m_pixelSize = rhs.m_pixelSize; + m_col = rhs.m_col; + m_row = rhs.m_row; + m_data = 0; + m_nextTile = 0; + m_nReadlock = 0; + + allocate(); + + rhs.addReader(); + memcpy(m_data, rhs.m_data, WIDTH * HEIGHT * m_pixelSize * sizeof(Q_UINT8)); + rhs.removeReader(); + + KisTileManager::instance()->registerTile(this); + } +} + +KisTile::~KisTile() +{ + KisTileManager::instance()->deregisterTile(this); // goes before the deleting of m_data! + + if (m_data) { +// delete[] m_data; + KisTileManager::instance()->dontNeedTileData(m_data, m_pixelSize); + m_data = 0; + } + assert( !readers() ); +} + +void KisTile::allocate() +{ + if (m_data == 0) { + assert (!readers()); + m_data = KisTileManager::instance()->requestTileData(m_pixelSize); + Q_CHECK_PTR(m_data); + } +} + +void KisTile::setNext(KisTile *n) +{ + m_nextTile = n; +} + +Q_UINT8 *KisTile::data(Q_INT32 x, Q_INT32 y ) const +{ + addReader(); + removeReader(); + + Q_ASSERT(m_data != 0); + if (m_data == 0) return 0; + + return m_data + m_pixelSize * ( y * WIDTH + x ); +} + +void KisTile::setData(const Q_UINT8 *pixel) +{ + addReader(); + Q_UINT8 *dst = m_data; + for(int i=0; i <WIDTH * HEIGHT;i++) + { + memcpy(dst, pixel, m_pixelSize); + dst+=m_pixelSize; + } + removeReader(); +} + +void KisTile::addReader() const +{ + if (m_nReadlock++ == 0) + KisTileManager::instance()->ensureTileLoaded(this); + else if (m_nReadlock < 0) { + kdDebug(41000) << m_nReadlock << endl; + assert(0); + } + assert(m_data); +} + +void KisTile::removeReader() const +{ + if (--m_nReadlock == 0) + KisTileManager::instance()->maySwapTile(this); +} diff --git a/krita/core/tiles/kis_tile.h b/krita/core/tiles/kis_tile.h new file mode 100644 index 00000000..d80b6e2c --- /dev/null +++ b/krita/core/tiles/kis_tile.h @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2004 Casper Boemann <cbr@boemann.dk> + * + * 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_TILE_H_ +#define KIS_TILE_H_ + +#include <qglobal.h> +#include <qrect.h> + +class KisTiledDataManager; +class KisTiledIterator; + +/** + * Provides abstraction to a tile. A tile contains + * a part of a PaintDevice, but only the individual pixels + * are accesable and that only via iterators. + */ +class KisTile { +public: + KisTile(Q_INT32 pixelSize, Q_INT32 col, Q_INT32 row, const Q_UINT8 *defPixel); + KisTile(const KisTile& rhs, Q_INT32 col, Q_INT32 row); + KisTile(const KisTile& rhs); + ~KisTile(); + +public: + void release(); + void allocate(); + + Q_UINT8 *data(Q_INT32 xoff, Q_INT32 yoff) const; + Q_UINT8 *data() const { return m_data; } + + void setData(const Q_UINT8 *pixel); + + Q_INT32 refCount() const; + void ref(); + + Q_INT32 getRow() const { return m_row; } + Q_INT32 getCol() const { return m_col; } + + QRect extent() const { return QRect(m_col * WIDTH, m_row * HEIGHT, WIDTH, HEIGHT); } + + void setNext(KisTile *); + KisTile *getNext() const { return m_nextTile; } + + // These are const because they don't change the external data the tile represents, + // although they do change internal representations. We need to be able to request + // access to a tile in a const enviroment (like copyconstructor and so)! + void addReader() const; + void removeReader() const; + Q_INT32 readers() { return m_nReadlock; } + + friend class KisTiledIterator; + friend class KisTiledDataManager; + friend class KisMemento; + friend class KisTileManager; +private: + KisTile& operator=(const KisTile&); + +private: + Q_UINT8 *m_data; + mutable Q_INT32 m_nReadlock; + Q_INT32 m_row; + Q_INT32 m_col; + Q_INT32 m_pixelSize; + KisTile *m_nextTile; + +public: + static const Q_INT32 WIDTH; + static const Q_INT32 HEIGHT; +}; + +#endif // KIS_TILE_H_ + diff --git a/krita/core/tiles/kis_tile_global.h b/krita/core/tiles/kis_tile_global.h new file mode 100644 index 00000000..93052a4f --- /dev/null +++ b/krita/core/tiles/kis_tile_global.h @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2005 Boudewijn Rempt <boud@valdyas.org> + * + * 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_TILE_GLOBAL_H_ +#define KIS_TILE_GLOBAL_H_ + +#define DBG_AREA_TILES 41000 + +#endif diff --git a/krita/core/tiles/kis_tiled_random_accessor.cc b/krita/core/tiles/kis_tiled_random_accessor.cc new file mode 100644 index 00000000..52ec8634 --- /dev/null +++ b/krita/core/tiles/kis_tiled_random_accessor.cc @@ -0,0 +1,115 @@ +/* + * copyright (c) 2006 Cyrille Berger <cberger@cberger.net> + * + * 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. + */ +#include "kis_tiled_random_accessor.h" + + +const Q_UINT32 KisTiledRandomAccessor::CACHESIZE = 4; // Define the number of tiles we keep in cache + +KisTiledRandomAccessor::KisTiledRandomAccessor(KisTiledDataManager *ktm, Q_INT32 x, Q_INT32 y, bool writable) : m_ktm(ktm), m_tilesCache(new KisTileInfo*[4]), m_tilesCacheSize(0), m_pixelSize (m_ktm->pixelSize()), m_writable(writable) +{ + Q_ASSERT(ktm != 0); + moveTo(x, y); +} + +KisTiledRandomAccessor::~KisTiledRandomAccessor() +{ + for( uint i = 0; i < m_tilesCacheSize; i++) + { + m_tilesCache[i]->tile->removeReader(); + m_tilesCache[i]->oldtile->removeReader(); + delete m_tilesCache[i]; + } + delete m_tilesCache; +} + +void KisTiledRandomAccessor::moveTo(Q_INT32 x, Q_INT32 y) +{ + // Look in the cache if the tile if the data is available + for( uint i = 0; i < m_tilesCacheSize; i++) + { + if( x >= m_tilesCache[i]->area_x1 && x <= m_tilesCache[i]->area_x2 && + y >= m_tilesCache[i]->area_y1 && y <= m_tilesCache[i]->area_y2 ) + { + KisTileInfo* kti = m_tilesCache[i]; + Q_UINT32 offset = x - kti->area_x1 + (y -kti->area_y1) * KisTile::WIDTH; + offset *= m_pixelSize; + m_data = kti->data + offset; + m_oldData = kti->oldData + offset; + if(i > 0) + { + memmove(m_tilesCache+1,m_tilesCache, i * sizeof(KisTileInfo*)); + m_tilesCache[0] = kti; + } + return; + } + } + // The tile wasn't in cache + if(m_tilesCacheSize == KisTiledRandomAccessor::CACHESIZE ) + { // Remove last element of cache + m_tilesCache[CACHESIZE-1]->tile->removeReader(); + m_tilesCache[CACHESIZE-1]->oldtile->removeReader(); + delete m_tilesCache[CACHESIZE-1]; + } else { + m_tilesCacheSize++; + } + Q_UINT32 col = xToCol( x ); + Q_UINT32 row = yToRow( y ); + KisTileInfo* kti = fetchTileData(col, row); + Q_UINT32 offset = x - kti->area_x1 + (y - kti->area_y1) * KisTile::WIDTH; + offset *= m_pixelSize; + m_data = kti->data + offset; + m_oldData = kti->oldData + offset; + memmove(m_tilesCache+1,m_tilesCache, (KisTiledRandomAccessor::CACHESIZE-1) * sizeof(KisTileInfo*)); + m_tilesCache[0] = kti; +} + + +Q_UINT8 * KisTiledRandomAccessor::rawData() const +{ + return m_data; +} + + +const Q_UINT8 * KisTiledRandomAccessor::oldRawData() const +{ +#ifdef DEBUG + kdWarning(!m_ktm->hasCurrentMemento(), DBG_AREA_TILES) << "Accessing oldRawData() when no transaction is in progress.\n"; +#endif + return m_oldData; +} + +KisTiledRandomAccessor::KisTileInfo* KisTiledRandomAccessor::fetchTileData(Q_INT32 col, Q_INT32 row) +{ + KisTileInfo* kti = new KisTileInfo; + kti->tile = m_ktm->getTile(col, row, m_writable); + + kti->tile->addReader(); + + kti->data = kti->tile->data(); + + kti->area_x1 = col * KisTile::HEIGHT; + kti->area_y1 = row * KisTile::WIDTH; + kti->area_x2 = kti->area_x1 + KisTile::HEIGHT - 2; + kti->area_y2 = kti->area_y1 + KisTile::WIDTH - 2; + + // set old data + kti->oldtile = m_ktm->getOldTile(col, row, kti->tile); + kti->oldtile->addReader(); + kti->oldData = kti->oldtile->data(); + return kti; +} diff --git a/krita/core/tiles/kis_tiled_random_accessor.h b/krita/core/tiles/kis_tiled_random_accessor.h new file mode 100644 index 00000000..7766f75c --- /dev/null +++ b/krita/core/tiles/kis_tiled_random_accessor.h @@ -0,0 +1,66 @@ +/* + * copyright (c) 2006 Cyrille Berger <cberger@cberger.net> + * + * 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_TILED_RANDOM_ACCESSOR_H +#define KIS_TILED_RANDOM_ACCESSOR_H + +#include <qrect.h> +#include <qvaluelist.h> + +#include <ksharedptr.h> + +#include <kis_tileddatamanager.h> + +class KisTile; + +class KisTiledRandomAccessor : public KShared { + struct KisTileInfo { + KisTile* tile; + KisTile* oldtile; + Q_UINT8* data; + const Q_UINT8* oldData; + Q_INT32 area_x1, area_y1, area_x2, area_y2; + }; + public: + KisTiledRandomAccessor(KisTiledDataManager *ktm, Q_INT32 x, Q_INT32 y, bool writable); + ~KisTiledRandomAccessor(); + + + private: + inline Q_UINT32 xToCol(Q_UINT32 x) const { if (m_ktm) return m_ktm->xToCol(x); else return 0; }; + inline Q_UINT32 yToRow(Q_UINT32 y) const { if (m_ktm) return m_ktm->yToRow(y); else return 0; }; + KisTileInfo* fetchTileData(Q_INT32 col, Q_INT32 row); + + public: + /// Move to a given x,y position, fetch tiles and data + void moveTo(Q_INT32 x, Q_INT32 y); + Q_UINT8* rawData() const; + const Q_UINT8* oldRawData() const; + + private: + KisTiledDataManager *m_ktm; + KisTileInfo** m_tilesCache; + Q_UINT32 m_tilesCacheSize; + Q_INT32 m_pixelSize; + Q_UINT8* m_data; + const Q_UINT8* m_oldData; + bool m_writable; + static const Q_UINT32 CACHESIZE; // Define the number of tiles we keep in cache + +}; + +#endif diff --git a/krita/core/tiles/kis_tileddatamanager.cc b/krita/core/tiles/kis_tileddatamanager.cc new file mode 100644 index 00000000..4372e487 --- /dev/null +++ b/krita/core/tiles/kis_tileddatamanager.cc @@ -0,0 +1,1044 @@ +/* + * Copyright (c) 2004 Casper Boemann <cbr@boemann.dk> + * + * 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 <qvaluevector.h> + +#include <kdebug.h> + +#include <KoStore.h> + +#include "kis_global.h" +#include "kis_debug_areas.h" +#include "kis_tileddatamanager.h" +#include "kis_tilediterator.h" +#include "kis_tile.h" +#include "kis_memento.h" +#include "kis_tilemanager.h" + +/* The data area is divided into tiles each say 64x64 pixels (defined at compiletime) + * The tiles are laid out in a matrix that can have negative indexes. + * The matrix grows automatically if needed (a call for writeacces to a tile outside the current extent) + * Even though the matrix has grown it may still not contain tiles at specific positions. They are created on demand + */ + +KisTiledDataManager::KisTiledDataManager(Q_UINT32 pixelSize, const Q_UINT8 *defPixel) +{ + m_pixelSize = pixelSize; + + m_defPixel = new Q_UINT8[m_pixelSize]; + Q_CHECK_PTR(m_defPixel); + memcpy(m_defPixel, defPixel, m_pixelSize); + + m_defaultTile = new KisTile(pixelSize,0,0, m_defPixel); + Q_CHECK_PTR(m_defaultTile); + + m_hashTable = new KisTile * [1024]; + Q_CHECK_PTR(m_hashTable); + + for(int i = 0; i < 1024; i++) + m_hashTable [i] = 0; + m_numTiles = 0; + m_currentMemento = 0; + m_extentMinX = Q_INT32_MAX; + m_extentMinY = Q_INT32_MAX; + m_extentMaxX = Q_INT32_MIN; + m_extentMaxY = Q_INT32_MIN; +} + +KisTiledDataManager::KisTiledDataManager(const KisTiledDataManager & dm) + : KShared() +{ + m_pixelSize = dm.m_pixelSize; + + m_defPixel = new Q_UINT8[m_pixelSize]; + Q_CHECK_PTR(m_defPixel); + memcpy(m_defPixel, dm.m_defPixel, m_pixelSize); + + m_defaultTile = new KisTile(*dm.m_defaultTile, dm.m_defaultTile->getCol(), dm.m_defaultTile->getRow()); + Q_CHECK_PTR(m_defaultTile); + + m_hashTable = new KisTile * [1024]; + Q_CHECK_PTR(m_hashTable); + + m_numTiles = 0; + m_currentMemento = 0; + m_extentMinX = dm.m_extentMinX; + m_extentMinY = dm.m_extentMinY; + m_extentMaxX = dm.m_extentMaxX; + m_extentMaxY = dm.m_extentMaxY; + + // Deep copy every tile. XXX: Make this copy-on-write! + for(int i = 0; i < 1024; i++) + { + const KisTile *tile = dm.m_hashTable[i]; + + m_hashTable[i] = 0; + + while(tile) + { + KisTile *newtile = new KisTile(*tile, tile->getCol(), tile->getRow()); + Q_CHECK_PTR(newtile); + + newtile->setNext(m_hashTable[i]); + m_hashTable[i] = newtile; + tile = tile->getNext(); + + m_numTiles++; + } + } + +} + +KisTiledDataManager::~KisTiledDataManager() +{ + // Deep delete every tile + for(int i = 0; i < 1024; i++) + { + const KisTile *tile = m_hashTable[i]; + + while(tile) + { + const KisTile *deltile = tile; + tile = tile->getNext(); + delete deltile; + } + } + delete [] m_hashTable; + delete m_defaultTile; + delete [] m_defPixel; +} + +void KisTiledDataManager::setDefaultPixel(const Q_UINT8 *defPixel) +{ + if (defPixel == 0) return; + + memcpy(m_defPixel, defPixel, m_pixelSize); + + m_defaultTile->setData(m_defPixel); +} + +bool KisTiledDataManager::write(KoStore *store) +{ + + if (store == 0) return false; + //Q_ASSERT(store != 0); + + char str[80]; + + sprintf(str, "%d\n", m_numTiles); + store->write(str,strlen(str)); + + for(int i = 0; i < 1024; i++) + { + const KisTile *tile = m_hashTable[i]; + + while(tile) + { + sprintf(str, "%d,%d,%d,%d\n", tile->getCol() * KisTile::WIDTH, + tile->getRow() * KisTile::HEIGHT, + KisTile::WIDTH, KisTile::HEIGHT); + store->write(str,strlen(str)); + + tile->addReader(); + store->write((char *)tile->m_data, KisTile::HEIGHT * KisTile::WIDTH * m_pixelSize); + tile->removeReader(); + + tile = tile->getNext(); + } + } + + return true; +} +bool KisTiledDataManager::read(KoStore *store) +{ + if (store == 0) return false; + //Q_ASSERT(store != 0); + + char str[80]; + Q_INT32 x,y,w,h; + + QIODevice *stream = store->device(); + if (stream == 0) return false; + //Q_ASSERT(stream != 0); + + stream->readLine(str, 79); + + sscanf(str,"%u",&m_numTiles); + + for(Q_UINT32 i = 0; i < m_numTiles; i++) + { + stream->readLine(str, 79); + sscanf(str,"%d,%d,%d,%d",&x,&y,&w,&h); + + // the following is only correct as long as tile size is not changed + // The first time we change tilesize the dimensions just read needs to be respected + // but for now we just assume that tiles are the same size as ever. + Q_INT32 row = yToRow(y); + Q_INT32 col = xToCol(x); + Q_UINT32 tileHash = calcTileHash(col, row); + + KisTile *tile = new KisTile(m_pixelSize, col, row, m_defPixel); + Q_CHECK_PTR(tile); + + updateExtent(col,row); + + tile->addReader(); + store->read((char *)tile->m_data, KisTile::HEIGHT * KisTile::WIDTH * m_pixelSize); + tile->removeReader(); + + tile->setNext(m_hashTable[tileHash]); + m_hashTable[tileHash] = tile; + } + return true; +} + +void KisTiledDataManager::extent(Q_INT32 &x, Q_INT32 &y, Q_INT32 &w, Q_INT32 &h) const +{ + x = m_extentMinX; + y = m_extentMinY; + + if (m_extentMaxX >= m_extentMinX) { + w = m_extentMaxX - m_extentMinX + 1; + } else { + w = 0; + } + + if (m_extentMaxY >= m_extentMinY) { + h = m_extentMaxY - m_extentMinY + 1; + } else { + h = 0; + } +} + +QRect KisTiledDataManager::extent() const +{ + Q_INT32 x; + Q_INT32 y; + Q_INT32 w; + Q_INT32 h; + + extent(x, y, w, h); + + return QRect(x, y, w, h); +} + +void KisTiledDataManager::setExtent(Q_INT32 x, Q_INT32 y, Q_INT32 w, Q_INT32 h) +{ + QRect newRect = QRect(x, y, w, h).normalize(); + //printRect("newRect", newRect); + QRect oldRect = QRect(m_extentMinX, m_extentMinY, m_extentMaxX - m_extentMinX + 1, m_extentMaxY - m_extentMinY + 1).normalize(); + //printRect("oldRect", oldRect); + + // Do nothing if the desired size is bigger than we currently are: that is handled by the autoextending automatically + if (newRect.contains(oldRect)) return; + + // Loop through all tiles, if a tile is wholly outside the extent, add to the memento, then delete it, + // if the tile is partially outside the extent, clear the outside pixels to the default pixel. + for(int tileHash = 0; tileHash < 1024; tileHash++) + { + KisTile *tile = m_hashTable[tileHash]; + KisTile *previousTile = 0; + + while(tile) + { + QRect tileRect = QRect(tile->getCol() * KisTile::WIDTH, tile->getRow() * KisTile::HEIGHT, KisTile::WIDTH, KisTile::HEIGHT); + //printRect("tileRect", tileRect); + + if (newRect.contains(tileRect)) { + // Completely inside, do nothing + previousTile = tile; + tile = tile->getNext(); + } + else { + ensureTileMementoed(tile->getCol(), tile->getRow(), tileHash, tile); + + if (newRect.intersects(tileRect)) { + + // Create the intersection of the tile and new rect + QRect intersection = newRect.intersect(tileRect); + //printRect("intersection", intersection); + intersection.setRect(intersection.x() - tileRect.x(), intersection.y() - tileRect.y(), intersection.width(), intersection.height()); + + // This can be done a lot more efficiently, no doubt, by clearing runs of pixels to the left and the right of + // the intersecting line. + tile->addReader(); + for (int y = 0; y < KisTile::HEIGHT; ++y) { + for (int x = 0; x < KisTile::WIDTH; ++x) { + if (!intersection.contains(x,y)) { + Q_UINT8 * ptr = tile->data(x, y); + memcpy(ptr, m_defPixel, m_pixelSize); + } + } + } + tile->removeReader(); + previousTile = tile; + tile = tile->getNext(); + } + else { + KisTile *deltile = tile; + tile = tile->getNext(); + + m_numTiles--; + + if (previousTile) + previousTile->setNext(tile); + else + m_hashTable[tileHash] = tile; + delete deltile; + } + } + } + } + + // Set the extent correctly + m_extentMinX = x; + m_extentMinY = y; + m_extentMaxX = x + w - 1; + m_extentMaxY = y + h - 1; +} + +void KisTiledDataManager::recalculateExtent() +{ + m_extentMinX = Q_INT32_MAX; + m_extentMinY = Q_INT32_MAX; + m_extentMaxX = Q_INT32_MIN; + m_extentMaxY = Q_INT32_MIN; + + // Loop through all tiles. + for (int tileHash = 0; tileHash < 1024; tileHash++) + { + const KisTile *tile = m_hashTable[tileHash]; + + while (tile) + { + updateExtent(tile->getCol(), tile->getRow()); + tile = tile->getNext(); + } + } +} + +void KisTiledDataManager::clear(Q_INT32 x, Q_INT32 y, Q_INT32 w, Q_INT32 h, Q_UINT8 clearValue) +{ + if (w < 1 || h < 1) { + return; + } + + Q_INT32 firstColumn = xToCol(x); + Q_INT32 lastColumn = xToCol(x + w - 1); + + Q_INT32 firstRow = yToRow(y); + Q_INT32 lastRow = yToRow(y + h - 1); + + QRect clearRect(x, y, w, h); + + const Q_UINT32 rowStride = KisTile::WIDTH * m_pixelSize; + + for (Q_INT32 row = firstRow; row <= lastRow; ++row) { + for (Q_INT32 column = firstColumn; column <= lastColumn; ++column) { + + KisTile *tile = getTile(column, row, true); + QRect tileRect = tile->extent(); + + QRect clearTileRect = clearRect & tileRect; + + tile->addReader(); + if (clearTileRect == tileRect) { + // Clear whole tile + memset(tile->data(), clearValue, KisTile::WIDTH * KisTile::HEIGHT * m_pixelSize); + } else { + + Q_UINT32 rowsRemaining = clearTileRect.height(); + Q_UINT8 *dst = tile->data(clearTileRect.x() - tileRect.x(), clearTileRect.y() - tileRect.y()); + + while (rowsRemaining > 0) { + memset(dst, clearValue, clearTileRect.width() * m_pixelSize); + dst += rowStride; + --rowsRemaining; + } + } + tile->removeReader(); + } + } +} + +void KisTiledDataManager::clear(Q_INT32 x, Q_INT32 y, Q_INT32 w, Q_INT32 h, const Q_UINT8 *clearPixel) +{ + Q_ASSERT(clearPixel != 0); + + if (clearPixel == 0 || w < 1 || h < 1) { + return; + } + + bool pixelBytesAreTheSame = true; + + for (Q_UINT32 i = 0; i < m_pixelSize; ++i) { + if (clearPixel[i] != clearPixel[0]) { + pixelBytesAreTheSame = false; + break; + } + } + + if (pixelBytesAreTheSame) { + clear(x, y, w, h, clearPixel[0]); + } else { + + Q_INT32 firstColumn = xToCol(x); + Q_INT32 lastColumn = xToCol(x + w - 1); + + Q_INT32 firstRow = yToRow(y); + Q_INT32 lastRow = yToRow(y + h - 1); + + QRect clearRect(x, y, w, h); + + const Q_UINT32 rowStride = KisTile::WIDTH * m_pixelSize; + + Q_UINT8 *clearPixelData = 0; + + if (w >= KisTile::WIDTH && h >= KisTile::HEIGHT) { + + // There might be a whole tile to be cleared so generate a cleared tile. + clearPixelData = new Q_UINT8[KisTile::WIDTH * KisTile::HEIGHT * m_pixelSize]; + + Q_UINT8 *dst = clearPixelData; + Q_UINT32 pixelsRemaining = KisTile::WIDTH; + + // Generate one row + while (pixelsRemaining > 0) { + memcpy(dst, clearPixel, m_pixelSize); + dst += m_pixelSize; + --pixelsRemaining; + } + + Q_UINT32 rowsRemaining = KisTile::HEIGHT - 1; + + // Copy to the rest of the rows. + while (rowsRemaining > 0) { + memcpy(dst, clearPixelData, rowStride); + dst += rowStride; + --rowsRemaining; + } + + } else { + + // Generate one row + Q_UINT32 maxRunLength = QMIN(w, KisTile::WIDTH); + + clearPixelData = new Q_UINT8[maxRunLength * m_pixelSize]; + + Q_UINT8 *dst = clearPixelData; + Q_UINT32 pixelsRemaining = maxRunLength; + + while (pixelsRemaining > 0) { + memcpy(dst, clearPixel, m_pixelSize); + dst += m_pixelSize; + --pixelsRemaining; + } + } + + for (Q_INT32 row = firstRow; row <= lastRow; ++row) { + for (Q_INT32 column = firstColumn; column <= lastColumn; ++column) { + + KisTile *tile = getTile(column, row, true); + QRect tileRect = tile->extent(); + + QRect clearTileRect = clearRect & tileRect; + + if (clearTileRect == tileRect) { + + // Clear whole tile + tile->addReader(); + memcpy(tile->data(), clearPixelData, KisTile::WIDTH * KisTile::HEIGHT * m_pixelSize); + tile->removeReader(); + } else { + + Q_UINT32 rowsRemaining = clearTileRect.height(); + tile->addReader(); + Q_UINT8 *dst = tile->data(clearTileRect.x() - tileRect.x(), clearTileRect.y() - tileRect.y()); + + while (rowsRemaining > 0) { + memcpy(dst, clearPixelData, clearTileRect.width() * m_pixelSize); + dst += rowStride; + --rowsRemaining; + } + tile->removeReader(); + } + } + } + + delete [] clearPixelData; + } +} + +void KisTiledDataManager::clear() +{ + // Loop through all tiles, add to the memento, then delete it, + for(int tileHash = 0; tileHash < 1024; tileHash++) + { + const KisTile *tile = m_hashTable[tileHash]; + + while(tile) + { + ensureTileMementoed(tile->getCol(), tile->getRow(), tileHash, tile); + + const KisTile *deltile = tile; + tile = tile->getNext(); + + delete deltile; + } + m_hashTable[tileHash] = 0; + } + + m_numTiles = 0; + + // Set the extent correctly + m_extentMinX = Q_INT32_MAX; + m_extentMinY = Q_INT32_MAX; + m_extentMaxX = Q_INT32_MIN; + m_extentMaxY = Q_INT32_MIN; +} + +void KisTiledDataManager::paste(KisDataManagerSP data, Q_INT32 sx, Q_INT32 sy, Q_INT32 dx, Q_INT32 dy, + Q_INT32 w, Q_INT32 h) +{ + //CBR_MISSING + sx=sy=dx=dy=w=h;data=0; +} + + +Q_UINT32 KisTiledDataManager::calcTileHash(Q_INT32 col, Q_INT32 row) +{ + return ((row << 5) + (col & 0x1F)) & 0x3FF; +} + +KisMementoSP KisTiledDataManager::getMemento() +{ + m_currentMemento = new KisMemento(m_pixelSize); + Q_CHECK_PTR(m_currentMemento); + + memcpy(m_currentMemento->m_defPixel, m_defPixel, m_pixelSize); + + return m_currentMemento; +} + +void KisTiledDataManager::rollback(KisMementoSP memento) +{ + if (memento == 0) return; + //Q_ASSERT(memento != 0); + + if (m_currentMemento != 0) { + // Undo means our current memento is no longer valid so remove it. + m_currentMemento = 0; + } + + // Rollback means restoring all of the tiles in the memento to our hashtable. + + // But first clear the memento redo hashtable. + // This is nessesary as new changes might have been done since last rollback (automatic filters) + for(int i = 0; i < 1024; i++) + { + memento->deleteAll(memento->m_redoHashTable[i]); + memento->m_redoHashTable[i]=0; + } + + // Also clear the table of deleted tiles + memento->clearTilesToDeleteOnRedo(); + + // Now on to the real rollback + + memcpy(memento->m_redoDefPixel, m_defPixel, m_pixelSize); + setDefaultPixel(memento->m_defPixel); + + for(int i = 0; i < 1024; i++) + { + KisTile *tile = memento->m_hashTable[i]; + + while(tile) + { + // The memento has a tile stored that we need to roll back + // Now find the corresponding one in our hashtable + KisTile *curTile = m_hashTable[i]; + KisTile *preTile = 0; + while(curTile) + { + if(curTile->getRow() == tile->getRow() && curTile->getCol() == tile->getCol()) + { + break; + } + preTile = curTile; + curTile = curTile->getNext(); + } + + if(curTile) + { + // Remove it from our hashtable + if(preTile) + preTile->setNext(curTile->getNext()); + else + m_hashTable[i]= curTile->getNext(); + + m_numTiles--; + + // And put it in the redo hashtable of the memento + curTile->setNext(memento->m_redoHashTable[i]); + memento->m_redoHashTable[i] = curTile; + } + else + { + memento->addTileToDeleteOnRedo(tile->getCol(), tile->getRow()); + // As we are pratically adding a new tile we need to update the extent + updateExtent(tile->getCol(), tile->getRow()); + } + + // Put a copy of the memento tile into our hashtable + curTile = new KisTile(*tile); + Q_CHECK_PTR(curTile); + m_numTiles++; + + curTile->setNext(m_hashTable[i]); + m_hashTable[i] = curTile; + + tile = tile->getNext(); + } + } + + if (memento->tileListToDeleteOnUndo() != 0) { + // XXX: We currently add these tiles above, only to delete them again here. + deleteTiles(memento->tileListToDeleteOnUndo()); + } +} + +void KisTiledDataManager::rollforward(KisMementoSP memento) +{ + if (memento == 0) return; + //Q_ASSERT(memento != 0); + + if (m_currentMemento != 0) { + // Redo means our current memento is no longer valid so remove it. + m_currentMemento = 0; + } + + // Rollforward means restoring all of the tiles in the memento's redo to our hashtable. + + setDefaultPixel(memento->m_redoDefPixel); + + for(int i = 0; i < 1024; i++) + { + KisTile *tile = memento->m_redoHashTable[i]; + + while(tile) + { + // The memento has a tile stored that we need to roll forward + // Now find the corresponding one in our hashtable + KisTile *curTile = m_hashTable[i]; + KisTile *preTile = 0; + while(curTile) + { + if(curTile->getRow() == tile->getRow() && curTile->getCol() == tile->getCol()) + { + break; + } + preTile = curTile; + curTile = curTile->getNext(); + } + + if (curTile) + { + // Remove it from our hashtable + if(preTile) + preTile->setNext(curTile->getNext()); + else + m_hashTable[i]= curTile->getNext(); + + // And delete it (it's equal to the one stored in the memento's undo) + m_numTiles--; + delete curTile; + } + + // Put a copy of the memento tile into our hashtable + curTile = new KisTile(*tile); + Q_CHECK_PTR(curTile); + + curTile->setNext(m_hashTable[i]); + m_hashTable[i] = curTile; + m_numTiles++; + updateExtent(curTile->getCol(), curTile->getRow()); + + tile = tile->getNext(); + } + } + + // Roll forward also means re-deleting the tiles that was deleted but restored by the undo + if (memento->tileListToDeleteOnRedo() != 0) { + deleteTiles(memento->tileListToDeleteOnRedo()); + } +} + +void KisTiledDataManager::deleteTiles(const KisMemento::DeletedTile *d) +{ + while (d) + { + Q_UINT32 tileHash = calcTileHash(d->col(), d->row()); + KisTile *curTile = m_hashTable[tileHash]; + KisTile *preTile = 0; + while(curTile) + { + if(curTile->getRow() == d->row() && curTile->getCol() == d->col()) + { + break; + } + preTile = curTile; + curTile = curTile->getNext(); + } + if (curTile) { + // Remove it from our hashtable + if(preTile) + preTile->setNext(curTile->getNext()); + else + m_hashTable[tileHash] = curTile->getNext(); + + // And delete it (it's equal to the one stored in the memento's undo) + m_numTiles--; + delete curTile; + } + d = d->next(); + } + + recalculateExtent(); +} + +void KisTiledDataManager::ensureTileMementoed(Q_INT32 col, Q_INT32 row, Q_UINT32 tileHash, const KisTile *refTile) +{ + if (refTile == 0) return; + //Q_ASSERT(refTile != 0); + + // Basically we search for the tile in the current memento, and if it's already there we do nothing, otherwise + // we make a copy of the tile and put it in the current memento + + if(!m_currentMemento) + return; + + KisTile *tile = m_currentMemento->m_hashTable[tileHash]; + while(tile != 0) + { + if(tile->getRow() == row && tile->getCol() == col) + break; + + tile = tile->getNext(); + } + if(tile) + return; // it has allready been stored + + tile = new KisTile(*refTile); + Q_CHECK_PTR(tile); + + tile->setNext(m_currentMemento->m_hashTable[tileHash]); + m_currentMemento->m_hashTable[tileHash] = tile; + m_currentMemento->m_numTiles++; +} + +void KisTiledDataManager::updateExtent(Q_INT32 col, Q_INT32 row) +{ + if(m_extentMinX > col * KisTile::WIDTH) + m_extentMinX = col * KisTile::WIDTH; + if(m_extentMaxX < (col+1) * KisTile::WIDTH - 1) + m_extentMaxX = (col+1) * KisTile::WIDTH - 1; + if(m_extentMinY > row * KisTile::HEIGHT) + m_extentMinY = row * KisTile::HEIGHT; + if(m_extentMaxY < (row+1) * KisTile::HEIGHT - 1) + m_extentMaxY = (row+1) * KisTile::HEIGHT - 1; +} + +KisTile *KisTiledDataManager::getTile(Q_INT32 col, Q_INT32 row, bool writeAccess) +{ + Q_UINT32 tileHash = calcTileHash(col, row); + + // Lookup tile in hash table + KisTile *tile = m_hashTable[tileHash]; + while(tile != 0) + { + if(tile->getRow() == row && tile->getCol() == col) + break; + + tile = tile->getNext(); + } + + // Might not have been created yet + if(!tile) + { + if(writeAccess) + { + // Create a new tile + tile = new KisTile(*m_defaultTile, col, row); + Q_CHECK_PTR(tile); + + tile->setNext(m_hashTable[tileHash]); + m_hashTable[tileHash] = tile; + m_numTiles++; + updateExtent(col, row); + + if (m_currentMemento && !m_currentMemento->containsTile(col, row, tileHash)) { + m_currentMemento->addTileToDeleteOnUndo(col, row); + } + } + else + // If only read access then it's enough to share a default tile + tile = m_defaultTile; + } + + if(writeAccess) + ensureTileMementoed(col, row, tileHash, tile); + + return tile; +} + +KisTile *KisTiledDataManager::getOldTile(Q_INT32 col, Q_INT32 row, KisTile *def) +{ + KisTile *tile = 0; + + // Lookup tile in hash table of current memento + if (m_currentMemento) + { + if (!m_currentMemento->valid()) return def; + //Q_ASSERT(m_currentMemento->valid()); + + Q_UINT32 tileHash = calcTileHash(col, row); + tile = m_currentMemento->m_hashTable[tileHash]; + while (tile != 0) + { + if (tile->getRow() == row && tile->getCol() == col) + break; + + tile = tile->getNext(); + } + } + + if (!tile) + tile = def; + + return tile; +} + +Q_UINT8* KisTiledDataManager::pixelPtr(Q_INT32 x, Q_INT32 y, bool writable) +{ + // Ahem, this is a bit not as good. The point is, this function needs the tile data, + // but it might be swapped out. This code swaps it in, but at function exit it might + // be swapped out again! THIS MAKES THE RETURNED POINTER QUITE VOLATILE + return pixelPtrSafe(x, y, writable) -> data(); +} + +KisTileDataWrapperSP KisTiledDataManager::pixelPtrSafe(Q_INT32 x, Q_INT32 y, bool writable) { + Q_INT32 row = yToRow(y); + Q_INT32 col = xToCol(x); + + // calc limits within the tile + Q_INT32 yInTile = y - row * KisTile::HEIGHT; + Q_INT32 xInTile = x - col * KisTile::WIDTH; + Q_INT32 offset = m_pixelSize * (yInTile * KisTile::WIDTH + xInTile); + + KisTile *tile = getTile(col, row, writable); + + return new KisTileDataWrapper(tile, offset); +} + +const Q_UINT8* KisTiledDataManager::pixel(Q_INT32 x, Q_INT32 y) +{ + return pixelPtr(x, y, false); +} + +Q_UINT8* KisTiledDataManager::writablePixel(Q_INT32 x, Q_INT32 y) +{ + return pixelPtr(x, y, true); +} + +void KisTiledDataManager::setPixel(Q_INT32 x, Q_INT32 y, const Q_UINT8 * data) +{ + Q_UINT8 *pixel = pixelPtr(x, y, true); + memcpy(pixel, data, m_pixelSize); +} + + +void KisTiledDataManager::readBytes(Q_UINT8 * data, + Q_INT32 x, Q_INT32 y, + Q_INT32 w, Q_INT32 h) +{ + if (data == 0) return; + //Q_ASSERT(data != 0); + if (w < 0) + w = 0; + + if (h < 0) + h = 0; + + Q_INT32 dstY = 0; + Q_INT32 srcY = y; + Q_INT32 rowsRemaining = h; + + while (rowsRemaining > 0) { + + Q_INT32 dstX = 0; + Q_INT32 srcX = x; + Q_INT32 columnsRemaining = w; + Q_INT32 numContiguousSrcRows = numContiguousRows(srcY, srcX, srcX + w - 1); + + Q_INT32 rows = QMIN(numContiguousSrcRows, rowsRemaining); + + while (columnsRemaining > 0) { + + Q_INT32 numContiguousSrcColumns = numContiguousColumns(srcX, srcY, srcY + rows - 1); + + Q_INT32 columns = QMIN(numContiguousSrcColumns, columnsRemaining); + + KisTileDataWrapperSP tileData = pixelPtrSafe(srcX, srcY, false); + const Q_UINT8 *srcData = tileData -> data(); + Q_INT32 srcRowStride = rowStride(srcX, srcY); + + Q_UINT8 *dstData = data + ((dstX + (dstY * w)) * m_pixelSize); + Q_INT32 dstRowStride = w * m_pixelSize; + + for (Q_INT32 row = 0; row < rows; row++) { + memcpy(dstData, srcData, columns * m_pixelSize); + dstData += dstRowStride; + srcData += srcRowStride; + } + + srcX += columns; + dstX += columns; + columnsRemaining -= columns; + } + + srcY += rows; + dstY += rows; + rowsRemaining -= rows; + } + +} + + +void KisTiledDataManager::writeBytes(const Q_UINT8 * bytes, + Q_INT32 x, Q_INT32 y, + Q_INT32 w, Q_INT32 h) +{ + if (bytes == 0) return; + //Q_ASSERT(bytes != 0); + + // XXX: Is this correct? + if (w < 0) + w = 0; + + if (h < 0) + h = 0; + + Q_INT32 srcY = 0; + Q_INT32 dstY = y; + Q_INT32 rowsRemaining = h; + + while (rowsRemaining > 0) { + + Q_INT32 srcX = 0; + Q_INT32 dstX = x; + Q_INT32 columnsRemaining = w; + Q_INT32 numContiguousdstRows = numContiguousRows(dstY, dstX, dstX + w - 1); + + Q_INT32 rows = QMIN(numContiguousdstRows, rowsRemaining); + + while (columnsRemaining > 0) { + + Q_INT32 numContiguousdstColumns = numContiguousColumns(dstX, dstY, dstY + rows - 1); + + Q_INT32 columns = QMIN(numContiguousdstColumns, columnsRemaining); + + //Q_UINT8 *dstData = writablePixel(dstX, dstY); + KisTileDataWrapperSP tileData = pixelPtrSafe(dstX, dstY, true); + Q_UINT8 *dstData = tileData->data(); + Q_INT32 dstRowStride = rowStride(dstX, dstY); + + const Q_UINT8 *srcData = bytes + ((srcX + (srcY * w)) * m_pixelSize); + Q_INT32 srcRowStride = w * m_pixelSize; + + for (Q_INT32 row = 0; row < rows; row++) { + memcpy(dstData, srcData, columns * m_pixelSize); + srcData += srcRowStride; + dstData += dstRowStride; + } + + dstX += columns; + srcX += columns; + columnsRemaining -= columns; + } + + dstY += rows; + srcY += rows; + rowsRemaining -= rows; + } +} + +Q_INT32 KisTiledDataManager::numContiguousColumns(Q_INT32 x, Q_INT32 minY, Q_INT32 maxY) +{ + Q_INT32 numColumns; + + Q_UNUSED(minY); + Q_UNUSED(maxY); + + if (x >= 0) { + numColumns = KisTile::WIDTH - (x % KisTile::WIDTH); + } else { + numColumns = ((-x - 1) % KisTile::WIDTH) + 1; + } + + return numColumns; +} + +Q_INT32 KisTiledDataManager::numContiguousRows(Q_INT32 y, Q_INT32 minX, Q_INT32 maxX) +{ + Q_INT32 numRows; + + Q_UNUSED(minX); + Q_UNUSED(maxX); + + if (y >= 0) { + numRows = KisTile::HEIGHT - (y % KisTile::HEIGHT); + } else { + numRows = ((-y - 1) % KisTile::HEIGHT) + 1; + } + + return numRows; +} + +Q_INT32 KisTiledDataManager::rowStride(Q_INT32 x, Q_INT32 y) +{ + Q_UNUSED(x); + Q_UNUSED(y); + + return KisTile::WIDTH * m_pixelSize; +} + +Q_INT32 KisTiledDataManager::numTiles(void) const +{ + return m_numTiles; +} + +KisTileDataWrapper::KisTileDataWrapper(KisTile* tile, Q_INT32 offset) + : m_tile(tile), m_offset(offset) +{ + m_tile->addReader(); +} + +KisTileDataWrapper::~KisTileDataWrapper() +{ + m_tile->removeReader(); +} diff --git a/krita/core/tiles/kis_tileddatamanager.h b/krita/core/tiles/kis_tileddatamanager.h new file mode 100644 index 00000000..d4976080 --- /dev/null +++ b/krita/core/tiles/kis_tileddatamanager.h @@ -0,0 +1,233 @@ +/* + * Copyright (c) 2004 Boudewijn Rempt <boud@valdyas.org> + * + * 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_TILEDDATAMANAGER_H_ +#define KIS_TILEDDATAMANAGER_H_ + +#include <qglobal.h> +#include <qvaluevector.h> + +#include <ksharedptr.h> + +#include "kis_tile_global.h" +#include "kis_tile.h" +#include "kis_memento.h" + +class KisTiledDataManager; +typedef KSharedPtr<KisTiledDataManager> KisTiledDataManagerSP; + +class KisDataManager; +typedef KSharedPtr<KisDataManager> KisDataManagerSP; + +class KisTiledIterator; +class KisTiledRandomAccessor; +class KoStore; + +class KisTileDataWrapper : public KShared { + KisTile* m_tile; + Q_INT32 m_offset; +public: + KisTileDataWrapper(KisTile* tile, Q_INT32 offset); + virtual ~KisTileDataWrapper(); + Q_UINT8* data() const { return m_tile->data() + m_offset; } +}; + +typedef KSharedPtr<KisTileDataWrapper> KisTileDataWrapperSP; + +/** + * KisTiledDataManager implements the interface that KisDataManager defines + * + * The interface definition is enforced by KisDataManager calling all the methods + * which must also be defined in KisTiledDataManager. It is not allowed to change the interface + * as other datamangers may also rely on the same interface. + * + * * Storing undo/redo data + * * Offering ordered and unordered iterators over rects of pixels + * * (eventually) efficiently loading and saving data in a format + * that may allow deferred loading. + * + * A datamanager knows nothing about the type of pixel data except + * how many Q_UINT8's a single pixel takes. + */ + +class KisTiledDataManager : public KShared { + +protected: + KisTiledDataManager(Q_UINT32 pixelSize, const Q_UINT8 *defPixel); + ~KisTiledDataManager(); + KisTiledDataManager(const KisTiledDataManager &dm); + KisTiledDataManager & operator=(const KisTiledDataManager &dm); + + +protected: + // Allow the baseclass of iterators acces to the interior + // derived iterator classes must go through KisTiledIterator + friend class KisTiledIterator; + friend class KisTiledRandomAccessor; + +protected: + + void setDefaultPixel(const Q_UINT8 *defPixel); + const Q_UINT8 * defaultPixel() const { return m_defPixel;}; + + KisMementoSP getMemento(); + void rollback(KisMementoSP memento); + void rollforward(KisMementoSP memento); + + // For debugging use. + bool hasCurrentMemento() const { return m_currentMemento != 0; } + +protected: + /** + * Reads and writes the tiles from/onto a KoStore (which is simply a file within a zip file) + * + */ + bool write(KoStore *store); + bool read(KoStore *store); + +protected: + + Q_UINT32 pixelSize(); + + void extent(Q_INT32 &x, Q_INT32 &y, Q_INT32 &w, Q_INT32 &h) const; + QRect extent() const; + + void setExtent(Q_INT32 x, Q_INT32 y, Q_INT32 w, Q_INT32 h); + +protected: + + void clear(Q_INT32 x, Q_INT32 y, Q_INT32 w, Q_INT32 h, Q_UINT8 clearValue); + void clear(Q_INT32 x, Q_INT32 y, Q_INT32 w, Q_INT32 h, const Q_UINT8 *clearPixel); + void clear(); + + +protected: + + void paste(KisDataManagerSP data, Q_INT32 sx, Q_INT32 sy, Q_INT32 dx, Q_INT32 dy, + Q_INT32 w, Q_INT32 h); + + +protected: + + + /** + * Get a read-only pointer to pixel (x, y). + */ + const Q_UINT8* pixel(Q_INT32 x, Q_INT32 y); + + /** + * Get a read-write pointer to pixel (x, y). + */ + Q_UINT8* writablePixel(Q_INT32 x, Q_INT32 y); + + /** + * write the specified data to x, y. There is no checking on pixelSize! + */ + void setPixel(Q_INT32 x, Q_INT32 y, const Q_UINT8 * data); + + + /** + * Copy the bytes in the specified rect to a vector. The caller is responsible + * for managing the vector. + */ + void readBytes(Q_UINT8 * bytes, + Q_INT32 x, Q_INT32 y, + Q_INT32 w, Q_INT32 h); + /** + * Copy the bytes in the vector to the specified rect. If there are bytes left + * in the vector after filling the rect, they will be ignored. If there are + * not enough bytes, the rest of the rect will be filled with the default value + * given (by default, 0); + */ + void writeBytes(const Q_UINT8 * bytes, + Q_INT32 x, Q_INT32 y, + Q_INT32 w, Q_INT32 h); + + /// Get the number of contiguous columns starting at x, valid for all values + /// of y between minY and maxY. + Q_INT32 numContiguousColumns(Q_INT32 x, Q_INT32 minY, Q_INT32 maxY); + + /// Get the number of contiguous rows starting at y, valid for all values + /// of x between minX and maxX. + Q_INT32 numContiguousRows(Q_INT32 y, Q_INT32 minX, Q_INT32 maxX); + + /// Get the row stride at pixel (x, y). This is the number of bytes to add to a + /// pointer to pixel (x, y) to access (x, y + 1). + Q_INT32 rowStride(Q_INT32 x, Q_INT32 y); + + // For debugging use + Q_INT32 numTiles() const; + +private: + + Q_UINT32 m_pixelSize; + Q_UINT32 m_numTiles; + KisTile *m_defaultTile; + KisTile **m_hashTable; + KisMementoSP m_currentMemento; + Q_INT32 m_extentMinX; + Q_INT32 m_extentMinY; + Q_INT32 m_extentMaxX; + Q_INT32 m_extentMaxY; + Q_UINT8 *m_defPixel; + +private: + + void ensureTileMementoed(Q_INT32 col, Q_INT32 row, Q_UINT32 tileHash, const KisTile *refTile); + KisTile *getOldTile(Q_INT32 col, Q_INT32 row, KisTile *def); + KisTile *getTile(Q_INT32 col, Q_INT32 row, bool writeAccess); + Q_UINT32 calcTileHash(Q_INT32 col, Q_INT32 row); + void updateExtent(Q_INT32 col, Q_INT32 row); + void recalculateExtent(); + void deleteTiles(const KisMemento::DeletedTile *deletedTileList); + Q_INT32 xToCol(Q_INT32 x) const; + Q_INT32 yToRow(Q_INT32 y) const; + void getContiguousColumnsAndRows(Q_INT32 x, Q_INT32 y, Q_INT32 *columns, Q_INT32 *rows); + Q_UINT8* pixelPtr(Q_INT32 x, Q_INT32 y, bool writable); + KisTileDataWrapperSP pixelPtrSafe(Q_INT32 x, Q_INT32 y, bool writable); +}; + + +inline Q_UINT32 KisTiledDataManager::pixelSize() +{ + return m_pixelSize; +} + +inline Q_INT32 KisTiledDataManager::xToCol(Q_INT32 x) const +{ + if (x >= 0) { + return x / KisTile::WIDTH; + } else { + return -(((-x - 1) / KisTile::WIDTH) + 1); + } +} + +inline Q_INT32 KisTiledDataManager::yToRow(Q_INT32 y) const +{ + if (y >= 0) { + return y / KisTile::HEIGHT; + } else { + return -(((-y - 1) / KisTile::HEIGHT) + 1); + } +} + +// during development the following line helps to check the interface is correct +// it should be safe to keep it here even during normal compilation +#include "kis_datamanager.h" + +#endif // KIS_TILEDDATAMANAGER_H_ + diff --git a/krita/core/tiles/kis_tiledhlineiterator.cc b/krita/core/tiles/kis_tiledhlineiterator.cc new file mode 100644 index 00000000..37a1195e --- /dev/null +++ b/krita/core/tiles/kis_tiledhlineiterator.cc @@ -0,0 +1,213 @@ +/* + * This file is part of the KDE project + * + * Copyright (c) 2004 Casper Boemann <cbr@boemann.dk> + * + * 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 <kdebug.h> + +#include "kis_tile_global.h" +#include "kis_tilediterator.h" + +KisTiledHLineIterator::KisTiledHLineIterator( KisTiledDataManager *ndevice, Q_INT32 x, Q_INT32 y, Q_INT32 w, bool writable) : + KisTiledIterator(ndevice), + m_right(x+w-1), m_left(x) +{ + Q_ASSERT(ndevice != 0); + + m_writable = writable; + m_x = x; + m_y = y; + + // Find tile row,col matching x,y + m_row = yToRow(m_y); + m_leftCol = xToCol(m_x); + m_rightCol = xToCol(m_right); + m_col = m_leftCol; + + // calc limits within the tile + m_yInTile = m_y - m_row * KisTile::HEIGHT; + m_leftInTile = m_x - m_leftCol * KisTile::WIDTH; + + if(m_col == m_rightCol) + m_rightInTile = m_right - m_rightCol * KisTile::WIDTH; + else + m_rightInTile = KisTile::WIDTH - 1; + + m_xInTile = m_leftInTile; + + fetchTileData(m_col, m_row); + m_offset = m_pixelSize * (m_yInTile * KisTile::WIDTH + m_xInTile); +} + +KisTiledHLineIterator::KisTiledHLineIterator(const KisTiledHLineIterator& rhs) + : KisTiledIterator(rhs) +{ + if (this != &rhs) { + m_right = rhs.m_right; + m_left = rhs.m_left; + m_leftCol = rhs.m_leftCol; + m_rightCol = rhs.m_rightCol; + m_xInTile = rhs.m_xInTile; + m_yInTile = rhs.m_yInTile; + m_leftInTile = rhs.m_leftInTile; + m_rightInTile = rhs.m_rightInTile; + } +} + +KisTiledHLineIterator& KisTiledHLineIterator::operator=(const KisTiledHLineIterator& rhs) +{ + if (this != &rhs) { + KisTiledIterator::operator=(rhs); + m_right = rhs.m_right; + m_left = rhs.m_left; + m_leftCol = rhs.m_leftCol; + m_rightCol = rhs.m_rightCol; + m_xInTile = rhs.m_xInTile; + m_yInTile = rhs.m_yInTile; + m_leftInTile = rhs.m_leftInTile; + m_rightInTile = rhs.m_rightInTile; + } + return *this; +} + +KisTiledHLineIterator::~KisTiledHLineIterator( ) +{ +} + +KisTiledHLineIterator & KisTiledHLineIterator::operator ++ () +{ + if(m_xInTile >= m_rightInTile) + { + nextTile(); + fetchTileData(m_col, m_row); + m_xInTile =m_leftInTile; + m_offset = m_pixelSize * (m_yInTile * KisTile::WIDTH + m_xInTile); + } + else + { + m_xInTile++; + m_offset += m_pixelSize; + } + m_x++; + + return *this; +} + +void KisTiledHLineIterator::nextTile() +{ + if(m_col < m_rightCol) + { + m_col++; + m_leftInTile = 0; + + if(m_col == m_rightCol) + m_rightInTile = m_right - m_rightCol * KisTile::WIDTH; + else + m_rightInTile = KisTile::WIDTH - 1; + } +} + +void KisTiledHLineIterator::prevTile() +{ + if(m_col > m_leftCol) + { + m_col--; + + if(m_col == m_leftCol) { + m_leftInTile = m_left - m_leftCol * KisTile::WIDTH; + } else { + m_leftInTile = 0; + } + // the only place this doesn't apply, is if we're in rightCol, and we can't go there + m_rightInTile = KisTile::WIDTH - 1; + } +} + +Q_INT32 KisTiledHLineIterator::nConseqHPixels() const +{ + return m_rightInTile - m_xInTile + 1; +} + +KisTiledHLineIterator & KisTiledHLineIterator::operator+=(int n) +{ + // XXX what if outside the valid range of this iterator? + if(m_xInTile + n > m_rightInTile) + { + m_x += n; + m_col = xToCol(m_x); + m_xInTile = m_x - m_col * KisTile::WIDTH; + m_leftInTile = 0; + + if(m_col == m_rightCol) + m_rightInTile = m_right - m_rightCol * KisTile::WIDTH; + else + m_rightInTile = KisTile::WIDTH - 1; + + fetchTileData(m_col, m_row); + } + else + { + m_xInTile += n; + m_x += n; + } + m_offset = m_pixelSize * (m_yInTile * KisTile::WIDTH + m_xInTile); + + return *this; +} + +KisTiledHLineIterator & KisTiledHLineIterator::operator -- () +{ + if(m_xInTile <= 0) + { + prevTile(); + fetchTileData(m_col, m_row); + m_xInTile = KisTile::WIDTH - 1; + m_offset = m_pixelSize * (m_yInTile * KisTile::WIDTH + m_xInTile); + } + else + { + m_xInTile--; + m_offset -= m_pixelSize; + } + m_x--; + + return *this; +} + +void KisTiledHLineIterator::nextRow() +{ + m_y++; + m_yInTile++; + m_x = m_left; + m_leftInTile = m_x - m_leftCol * KisTile::WIDTH; + m_xInTile = m_leftInTile; + if( m_yInTile >= KisTile::HEIGHT ) + { // Need a new row + m_yInTile = 0; + m_row++; + m_col = m_leftCol; + fetchTileData(m_col, m_row); + } else if( m_leftCol != m_col ) { + m_col = m_leftCol; + fetchTileData(m_col, m_row); + } + if(m_col == m_rightCol) + m_rightInTile = m_right - m_rightCol * KisTile::WIDTH; + else + m_rightInTile = KisTile::WIDTH - 1; + m_offset = m_pixelSize * (m_yInTile * KisTile::WIDTH + m_xInTile); +} diff --git a/krita/core/tiles/kis_tilediterator.cc b/krita/core/tiles/kis_tilediterator.cc new file mode 100644 index 00000000..5806a82a --- /dev/null +++ b/krita/core/tiles/kis_tilediterator.cc @@ -0,0 +1,131 @@ +/* + * This file is part of the Krita + * + * Copyright (c) 2004 Casper Boemann <cbr@boemann.dk> + * + * 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 <kdebug.h> + +#include "kis_tile_global.h" +#include "kis_tilediterator.h" + +KisTiledIterator::KisTiledIterator( KisTiledDataManager *ndevice) +{ + Q_ASSERT(ndevice != 0); + m_ktm = ndevice; + m_x = 0; + m_y = 0; + m_row = 0; + m_col = 0; + m_pixelSize = m_ktm->pixelSize(); + m_tile = 0; + m_oldTile = 0; +} + +KisTiledIterator::~KisTiledIterator( ) +{ + if (m_tile) + m_tile->removeReader(); + if (m_oldTile) + m_oldTile->removeReader(); +} + +KisTiledIterator::KisTiledIterator(const KisTiledIterator& rhs) + : KShared() +{ + if (this != &rhs) { + m_ktm = rhs.m_ktm; + m_pixelSize = rhs.m_pixelSize; + m_x = rhs.m_x; + m_y = rhs.m_y; + m_row = rhs.m_row; + m_col = rhs.m_col; + m_data = rhs.m_data; + m_oldData = rhs.m_oldData; + m_offset = rhs.m_offset; + m_tile = rhs.m_tile; + m_oldTile = rhs.m_oldTile; + m_writable = rhs.m_writable; + if (m_tile) + m_tile->addReader(); + } +} + +KisTiledIterator& KisTiledIterator::operator=(const KisTiledIterator& rhs) +{ + if (this != &rhs) { + if (m_tile) + m_tile->removeReader(); + if (m_oldTile) + m_oldTile->removeReader(); + m_ktm = rhs.m_ktm; + m_pixelSize = rhs.m_pixelSize; + m_x = rhs.m_x; + m_y = rhs.m_y; + m_row = rhs.m_row; + m_col = rhs.m_col; + m_data = rhs.m_data; + m_oldData = rhs.m_oldData; + m_offset = rhs.m_offset; + m_tile = rhs.m_tile; + m_oldTile = rhs.m_oldTile; + m_writable = rhs.m_writable; + if (m_tile) + m_tile->addReader(); + } + return *this; +} + +Q_UINT8 * KisTiledIterator::rawData() const +{ + return m_data + m_offset; +} + + +const Q_UINT8 * KisTiledIterator::oldRawData() const +{ +#ifdef DEBUG + // Warn if we're misusing oldRawData(). If there's no memento, oldRawData is the same + // as rawData(). + kdWarning(!m_ktm->hasCurrentMemento(), DBG_AREA_TILES) << "Accessing oldRawData() when no transaction is in progress.\n"; +#endif + return m_oldData + m_offset; +} + +void KisTiledIterator::fetchTileData(Q_INT32 col, Q_INT32 row) +{ + if (m_tile) + m_tile->removeReader(); + if (m_oldTile) + m_oldTile->removeReader(); + m_oldTile = 0; + + m_tile = m_ktm->getTile(col, row, m_writable); + + if (m_tile == 0) return; + //Q_ASSERT(m_tile != 0); + m_tile->addReader(); + + m_data = m_tile->data(); + if (m_data == 0) return; + + //Q_ASSERT(m_data != 0); + + // set old data but default to current value + m_oldTile = m_ktm->getOldTile(col, row, m_tile); + m_oldTile->addReader(); // Double locking in case m_oldTile==m_tile is no problem + m_oldData = m_oldTile->data(); +} diff --git a/krita/core/tiles/kis_tilediterator.h b/krita/core/tiles/kis_tilediterator.h new file mode 100644 index 00000000..02431f4b --- /dev/null +++ b/krita/core/tiles/kis_tilediterator.h @@ -0,0 +1,213 @@ +/* This file is part of the KDE project + * Copyright (c) 2004 Casper Boemann <cbr@boemann.dkt> + * 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_TILED_ITERATOR_H_ +#define KIS_TILED_ITERATOR_H_ + +#include <qglobal.h> + +#include <ksharedptr.h> + +#include <kis_tile.h> +#include <kis_tileddatamanager.h> +#include <koffice_export.h> +/** + * The KisIterator class iterates through the pixels of a KisPaintDevice hiding the tile structure + */ +class KRITACORE_EXPORT KisTiledIterator : public KShared { + +protected: + KisTiledDataManager *m_ktm; + Q_INT32 m_pixelSize; // bytes per pixel + Q_INT32 m_x; // current x position + Q_INT32 m_y; // cirrent y position + Q_INT32 m_row; // row in tilemgr + Q_INT32 m_col; // col in tilemgr + Q_UINT8 *m_data; + Q_UINT8 *m_oldData; + Q_INT32 m_offset; + KisTile *m_tile; + KisTile* m_oldTile; + bool m_writable; + +protected: + inline Q_UINT32 xToCol(Q_UINT32 x) const { if (m_ktm) return m_ktm->xToCol(x); else return 0; }; + inline Q_UINT32 yToRow(Q_UINT32 y) const { if (m_ktm) return m_ktm->yToRow(y); else return 0; }; + void fetchTileData(Q_INT32 col, Q_INT32 row); + +public: + KisTiledIterator( KisTiledDataManager *ktm); + KisTiledIterator(const KisTiledIterator&); + KisTiledIterator& operator=(const KisTiledIterator&); + ~KisTiledIterator(); + +public: + // current x position + Q_INT32 x() const { return m_x; }; + + // cirrent y position + Q_INT32 y() const { return m_y; }; + + /// Returns a pointer to the pixel data. Do NOT interpret the data - leave that to a colorstrategy + Q_UINT8 *rawData() const; + + /// Returns a pointer to the pixel data as it was at the moment tof he last memento creation. + const Q_UINT8 * oldRawData() const; +}; + +/** + * The KisRectIterator class iterates through the pixels of a rect in a KisPaintDevice hiding the + * tile structure + */ +class KRITACORE_EXPORT KisTiledRectIterator : public KisTiledIterator +{ + +public: + /// do not call constructor directly use factory method in KisDataManager instead. + KisTiledRectIterator( KisTiledDataManager *dm, Q_INT32 x, Q_INT32 y, Q_INT32 w, Q_INT32 h, bool writable); + KisTiledRectIterator(const KisTiledRectIterator&); + KisTiledRectIterator& operator=(const KisTiledRectIterator&); + ~KisTiledRectIterator(); + +public: + Q_INT32 nConseqPixels() const; + + /// Advances a number of pixels until it reaches the end of the rect + KisTiledRectIterator & operator+=(int n); + + /// Advances one pixel. Going to the beginning of the next line when it reaches the end of a line + KisTiledRectIterator & operator++(); + + /// Goes back one pixel. Going to the end of the line above when it reaches the beginning of a line + //KisTiledRectIterator & operator--(); + + /// returns true when the iterator has reached the end + inline bool isDone() const { return m_beyondEnd; } + + +protected: + Q_INT32 m_left; + Q_INT32 m_top; + Q_INT32 m_w; + Q_INT32 m_h; + Q_INT32 m_topRow; + Q_INT32 m_bottomRow; + Q_INT32 m_leftCol; + Q_INT32 m_rightCol; + Q_INT32 m_xInTile; + Q_INT32 m_yInTile; + Q_INT32 m_leftInTile; + Q_INT32 m_rightInTile; + Q_INT32 m_topInTile; + Q_INT32 m_bottomInTile; + bool m_beyondEnd; + +private: + void nextTile(); +}; + +/** + * The KisHLineIterator class iterates through the pixels of a horizontal line in a KisPaintDevice hiding the + * tile structure + */ +class KRITACORE_EXPORT KisTiledHLineIterator : public KisTiledIterator +{ + +public: + /// do not call constructor directly use factory method in KisDataManager instead. + KisTiledHLineIterator( KisTiledDataManager *dm, Q_INT32 x, Q_INT32 y, Q_INT32 w, bool writable); + KisTiledHLineIterator(const KisTiledHLineIterator&); + KisTiledHLineIterator& operator=(const KisTiledHLineIterator&); + ~KisTiledHLineIterator(); + +public: + /// Advances one pixel. Going to the beginning of the next line when it reaches the end of a line + KisTiledHLineIterator & operator++(); + + /// Returns the number of consequtive horizontal pixels that we point at + /// This is useful for optimizing + Q_INT32 nConseqHPixels() const; + + /// Advances a number of pixels until it reaches the end of the line + KisTiledHLineIterator & operator+=(int); + + /// Goes back one pixel. Going to the end of the line above when it reaches the beginning of a line + KisTiledHLineIterator & operator--(); + + /// returns true when the iterator has reached the end + bool isDone() const { return m_x > m_right; } + + /// increment to the next row and rewind to the begining + void nextRow(); + +protected: + Q_INT32 m_right; + Q_INT32 m_left; + Q_INT32 m_leftCol; + Q_INT32 m_rightCol; + Q_INT32 m_xInTile; + Q_INT32 m_yInTile; + Q_INT32 m_leftInTile; + Q_INT32 m_rightInTile; + +private: + void nextTile(); + void prevTile(); +}; + +/** + * The KisVLineIterator class iterates through the pixels of a vertical line in a KisPaintDevice hiding the + * tile structure + */ +class KRITACORE_EXPORT KisTiledVLineIterator : public KisTiledIterator +{ + +public: + /// do not call constructor directly use factory method in KisDataManager instead. + KisTiledVLineIterator( KisTiledDataManager *dm, Q_INT32 x, Q_INT32 y, Q_INT32 h, bool writable); + KisTiledVLineIterator(const KisTiledVLineIterator&); + KisTiledVLineIterator& operator=(const KisTiledVLineIterator&); + ~KisTiledVLineIterator(); + +public: + /// Advances one pixel. Going to the beginning of the next line when it reaches the end of a line + KisTiledVLineIterator & operator++(); + + /// Goes back one pixel. Going to the end of the line above when it reaches the beginning of a line + //KisTiledVLineIterator & operator--(); + + /// returns true when the iterator has reached the end + bool isDone() const { return m_y > m_bottom; } + + /// increment to the next column and rewind to the begining + void nextCol(); + +protected: + Q_INT32 m_top; + Q_INT32 m_bottom; + Q_INT32 m_topRow; + Q_INT32 m_bottomRow; + Q_INT32 m_xInTile; + Q_INT32 m_yInTile; + Q_INT32 m_topInTile; + Q_INT32 m_bottomInTile; + +private: + void nextTile(); +}; + +#endif // KIS_TILED_ITERATOR_H_ diff --git a/krita/core/tiles/kis_tiledrectiterator.cc b/krita/core/tiles/kis_tiledrectiterator.cc new file mode 100644 index 00000000..73146504 --- /dev/null +++ b/krita/core/tiles/kis_tiledrectiterator.cc @@ -0,0 +1,242 @@ +/* + * This file is part of the KDE project + * + * Copyright (c) 2004 Casper Boemann <cbr@boemann.dk> + * + * 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 <kdebug.h> + +#include "kis_tile_global.h" +#include "kis_tilediterator.h" + +KisTiledRectIterator::KisTiledRectIterator( KisTiledDataManager *ndevice, Q_INT32 nleft, + Q_INT32 ntop, Q_INT32 nw, Q_INT32 nh, bool writable) : + KisTiledIterator(ndevice), + m_left(nleft), + m_top(ntop), + m_w(nw), + m_h(nh) +{ + + Q_ASSERT(ndevice != 0); + + m_writable = writable; + m_x = nleft; + m_y = ntop; + m_beyondEnd = (m_w == 0) || (m_h == 0); + + // Find tile row,col matching x,y + m_topRow = yToRow(m_y); + m_bottomRow = yToRow(m_y + m_h - 1); + m_leftCol = xToCol(m_x); + m_rightCol = xToCol(m_x + m_w - 1); + m_row = m_topRow; + m_col = m_leftCol; + + // calc limits within the tile + m_topInTile = m_top - m_topRow * KisTile::HEIGHT; + + if(m_row == m_bottomRow) + m_bottomInTile = m_top + m_h - 1 - m_bottomRow * KisTile::HEIGHT; + else + m_bottomInTile = KisTile::HEIGHT - 1; + + m_leftInTile = m_left - m_leftCol * KisTile::WIDTH; + + if(m_col == m_rightCol) + m_rightInTile = m_left + m_w - 1 - m_rightCol * KisTile::WIDTH; + else + m_rightInTile = KisTile::WIDTH - 1; + + m_xInTile = m_leftInTile; + m_yInTile = m_topInTile; + + if( ! m_beyondEnd) + fetchTileData(m_col, m_row); + m_offset = m_pixelSize * (m_yInTile * KisTile::WIDTH + m_xInTile); +} + +KisTiledRectIterator::KisTiledRectIterator(const KisTiledRectIterator& rhs) + : KisTiledIterator(rhs) +{ + if (this != &rhs) { + m_left = rhs.m_left; + m_top = rhs.m_top; + m_w = rhs.m_w; + m_h = rhs.m_h; + m_topRow = rhs.m_topRow; + m_bottomRow = rhs.m_bottomRow; + m_leftCol = rhs.m_leftCol; + m_rightCol = rhs.m_rightCol; + m_xInTile = rhs.m_xInTile; + m_yInTile = rhs.m_yInTile; + m_leftInTile = rhs.m_leftInTile; + m_rightInTile = rhs.m_rightInTile; + m_topInTile = rhs.m_topInTile; + m_bottomInTile = rhs.m_bottomInTile; + m_beyondEnd = rhs.m_beyondEnd; + } +} + +KisTiledRectIterator& KisTiledRectIterator::operator=(const KisTiledRectIterator& rhs) +{ + if (this != &rhs) { + KisTiledIterator::operator=(rhs); + m_left = rhs.m_left; + m_top = rhs.m_top; + m_w = rhs.m_w; + m_h = rhs.m_h; + m_topRow = rhs.m_topRow; + m_bottomRow = rhs.m_bottomRow; + m_leftCol = rhs.m_leftCol; + m_rightCol = rhs.m_rightCol; + m_xInTile = rhs.m_xInTile; + m_yInTile = rhs.m_yInTile; + m_leftInTile = rhs.m_leftInTile; + m_rightInTile = rhs.m_rightInTile; + m_topInTile = rhs.m_topInTile; + m_bottomInTile = rhs.m_bottomInTile; + m_beyondEnd = rhs.m_beyondEnd; + } + return *this; +} + +KisTiledRectIterator::~KisTiledRectIterator( ) +{ +} + +Q_INT32 KisTiledRectIterator::nConseqPixels() const +{ + if(m_leftInTile || (m_rightInTile != KisTile::WIDTH - 1)) + return m_rightInTile - m_xInTile + 1; + else + return KisTile::WIDTH * (m_bottomInTile - m_yInTile + 1) - m_xInTile; +} + +KisTiledRectIterator & KisTiledRectIterator::operator+=(int n) +{ + int remainInTile; + + remainInTile= (m_bottomInTile - m_yInTile) * (m_rightInTile - m_leftInTile + 1); + remainInTile += m_rightInTile - m_xInTile + 1; + + // This while loop may not bet the fastest, but usually it's not entered more than once. + while(n >= remainInTile) + { + n -= remainInTile; + nextTile(); + if(m_beyondEnd) + return *this; + m_yInTile = m_topInTile; + m_xInTile = m_leftInTile; + remainInTile= (m_bottomInTile - m_yInTile) * (m_rightInTile - m_leftInTile + 1); + remainInTile += m_rightInTile - m_xInTile + 1; + } + + int lWidth = m_rightInTile - m_leftInTile + 1; + while(n >= lWidth) + { + n -= lWidth; + m_yInTile++; + } + m_xInTile += n; + m_x = m_col * KisTile::WIDTH + m_xInTile; + m_y = m_row * KisTile::HEIGHT + m_yInTile; + fetchTileData(m_col, m_row); + m_offset = m_pixelSize * (m_yInTile * KisTile::WIDTH + m_xInTile); + + return *this; +} + + +KisTiledRectIterator & KisTiledRectIterator::operator ++ () +{ + // advance through rect completing each tile before moving on + // as per excellent suggestion by Cyrille, avoiding excessive tile switching + if(m_xInTile >= m_rightInTile) + { + if (m_yInTile >= m_bottomInTile) + { + nextTile(); + if(m_beyondEnd) + return *this; + m_yInTile = m_topInTile; + m_x = m_col * KisTile::WIDTH + m_leftInTile; + m_y = m_row * KisTile::HEIGHT + m_topInTile; + fetchTileData(m_col, m_row); + } + else + { + m_x -= m_rightInTile - m_leftInTile; + m_y++; + m_yInTile++; + } + m_xInTile =m_leftInTile; + m_offset = m_pixelSize * (m_yInTile * KisTile::WIDTH + m_xInTile); + } + else + { + m_x++; + m_xInTile++; + m_offset += m_pixelSize; + } + return *this; +} + +void KisTiledRectIterator::nextTile() +{ + if(m_col >= m_rightCol) + { + // needs to switch row + if(m_row >= m_bottomRow) + m_beyondEnd = true; + else + { + m_col = m_leftCol; + m_row++; + // The row has now changed, so recalc vertical limits + if(m_row == m_topRow) + m_topInTile = m_top - m_topRow * KisTile::HEIGHT; + else + m_topInTile = 0; + + if(m_row == m_bottomRow) + m_bottomInTile = m_top + m_h - 1 - m_bottomRow * KisTile::HEIGHT; + else + m_bottomInTile = KisTile::HEIGHT - 1; + } + } + else + m_col++; + + // No matter what the column has now changed, so recalc horizontal limits + if(m_col == m_leftCol) + m_leftInTile = m_left - m_leftCol * KisTile::WIDTH; + else + m_leftInTile = 0; + + if(m_col == m_rightCol) + m_rightInTile = m_left + m_w - 1 - m_rightCol * KisTile::WIDTH; + else + m_rightInTile = KisTile::WIDTH - 1; +} + +/* +KisTiledRectIterator & KisTiledRectIterator::operator -- () +{ + return *this; +} +*/ diff --git a/krita/core/tiles/kis_tiledvlineiterator.cc b/krita/core/tiles/kis_tiledvlineiterator.cc new file mode 100644 index 00000000..5b6270e1 --- /dev/null +++ b/krita/core/tiles/kis_tiledvlineiterator.cc @@ -0,0 +1,154 @@ +/* + * This file is part of the Krita + * + * Copyright (c) 2004 Casper Boemann <cbr@boemann.dk> + * + * 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 <kdebug.h> + +#include "kis_tile_global.h" +#include "kis_tilediterator.h" + +KisTiledVLineIterator::KisTiledVLineIterator( KisTiledDataManager *ndevice, Q_INT32 x, Q_INT32 y, Q_INT32 h, bool writable) : + KisTiledIterator(ndevice), + m_bottom(y + h - 1) +{ + m_writable = writable; + m_top = y; + m_x = x; + m_y = y; + + // Find tile row,col matching x,y + m_col = xToCol(m_x); + m_topRow = yToRow(m_y); + m_bottomRow = yToRow(m_bottom); + m_row = m_topRow; + + // calc limits within the tile + m_xInTile = m_x - m_col * KisTile::WIDTH; + m_topInTile = m_y - m_topRow * KisTile::HEIGHT; + + if(m_row == m_bottomRow) + m_bottomInTile = m_bottom - m_bottomRow * KisTile::HEIGHT; + else + m_bottomInTile = KisTile::HEIGHT - 1; + + m_yInTile = m_topInTile; + + fetchTileData(m_col, m_row); + m_offset = m_pixelSize * (m_yInTile * KisTile::WIDTH + m_xInTile); +} + +KisTiledVLineIterator::KisTiledVLineIterator(const KisTiledVLineIterator& rhs) + : KisTiledIterator(rhs) +{ + if (this != &rhs) { + m_top = rhs.m_top; + m_bottom = rhs.m_bottom; + m_topRow = rhs.m_topRow; + m_bottomRow = rhs.m_bottomRow; + m_xInTile = rhs.m_xInTile; + m_yInTile = rhs.m_yInTile; + m_topInTile = rhs.m_topInTile; + m_bottomInTile = rhs.m_bottomInTile; + } +} + +KisTiledVLineIterator& KisTiledVLineIterator::operator=(const KisTiledVLineIterator& rhs) +{ + if (this != &rhs) { + KisTiledIterator::operator=(rhs); + + m_top = rhs.m_top; + m_bottom = rhs.m_bottom; + m_topRow = rhs.m_topRow; + m_bottomRow = rhs.m_bottomRow; + m_xInTile = rhs.m_xInTile; + m_yInTile = rhs.m_yInTile; + m_topInTile = rhs.m_topInTile; + m_bottomInTile = rhs.m_bottomInTile; + } + return *this; +} + +KisTiledVLineIterator::~KisTiledVLineIterator( ) +{ +} + +KisTiledVLineIterator & KisTiledVLineIterator::operator ++ () +{ + if(m_yInTile >= m_bottomInTile) + { + nextTile(); + fetchTileData(m_col, m_row); + m_yInTile =m_topInTile; + m_offset = m_pixelSize * (m_yInTile * KisTile::WIDTH + m_xInTile); + } + else + { + m_yInTile++; + m_offset += m_pixelSize * KisTile::WIDTH; + } + m_y++; + + return *this; +} + +void KisTiledVLineIterator::nextTile() +{ + if(m_row < m_bottomRow) + { + m_row++; + m_topInTile = 0; + + if(m_row == m_bottomRow) + m_bottomInTile = m_bottom - m_bottomRow * KisTile::HEIGHT; + else + m_bottomInTile = KisTile::HEIGHT - 1; + } +} + +void KisTiledVLineIterator::nextCol() +{ + m_x++; + m_xInTile++; + m_y = m_top; + m_topInTile = m_y - m_topRow * KisTile::HEIGHT; + m_yInTile = m_topInTile; + if( m_xInTile >= KisTile::WIDTH ) + { // Need a new row + m_xInTile = 0; + m_col++; + m_row = m_topRow; + fetchTileData(m_col, m_row); + } else if( m_topRow != m_row ) { + m_row = m_topRow; + fetchTileData(m_col, m_row); + } + if(m_row == m_bottomRow) + m_bottomInTile = m_bottom - m_bottomRow * KisTile::HEIGHT; + else + m_bottomInTile = KisTile::HEIGHT - 1; + + m_offset = m_pixelSize * (m_yInTile * KisTile::WIDTH + m_xInTile); +} + +/* +KisTiledVLineIterator & KisTiledVLineIterator::operator -- () +{ + return *this; +} +*/ diff --git a/krita/core/tiles/kis_tilemanager.cc b/krita/core/tiles/kis_tilemanager.cc new file mode 100644 index 00000000..0ac968fd --- /dev/null +++ b/krita/core/tiles/kis_tilemanager.cc @@ -0,0 +1,578 @@ +/* + * Copyright (c) 2005-2006 Bart Coppens <kde@bartcoppens.be> + * + * 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 <kdebug.h> + +#include <sys/mman.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <string.h> +#include <fcntl.h> + +#include <qmutex.h> +#include <qthread.h> +#include <qfile.h> + +#include <kstaticdeleter.h> +#include <kglobal.h> +#include <kconfig.h> + +#include "kis_tileddatamanager.h" +#include "kis_tile.h" +#include "kis_tilemanager.h" + +// Note: the cache file doesn't get deleted when we crash and so :( + +KisTileManager* KisTileManager::m_singleton = 0; + +static KStaticDeleter<KisTileManager> staticDeleter; + +KisTileManager::KisTileManager() { + + Q_ASSERT(KisTileManager::m_singleton == 0); + KisTileManager::m_singleton = this; + m_bytesInMem = 0; + m_bytesTotal = 0; + m_swapForbidden = false; + + // Hardcoded (at the moment only?): 4 pools of 1000 tiles each + m_tilesPerPool = 1000; + + m_pools = new Q_UINT8*[4]; + m_poolPixelSizes = new Q_INT32[4]; + m_poolFreeList = new PoolFreeList[4]; + for (int i = 0; i < 4; i++) { + m_pools[i] = 0; + m_poolPixelSizes[i] = 0; + m_poolFreeList[i] = PoolFreeList(); + } + m_currentInMem = 0; + + KConfig * cfg = KGlobal::config(); + cfg->setGroup(""); + m_maxInMem = cfg->readNumEntry("maxtilesinmem", 4000); + m_swappiness = cfg->readNumEntry("swappiness", 100); + + m_tileSize = KisTile::WIDTH * KisTile::HEIGHT; + m_freeLists.resize(8); + + counter = 0; + + m_poolMutex = new QMutex(true); + m_swapMutex = new QMutex(true); +} + +KisTileManager::~KisTileManager() { + if (!m_freeLists.empty()) { // See if there are any nonempty freelists + FreeListList::iterator listsIt = m_freeLists.begin(); + FreeListList::iterator listsEnd = m_freeLists.end(); + + while(listsIt != listsEnd) { + if ( ! (*listsIt).empty() ) { + FreeList::iterator it = (*listsIt).begin(); + FreeList::iterator end = (*listsIt).end(); + + while (it != end) { + delete *it; + ++it; + } + (*listsIt).clear(); + } + ++listsIt; + } + m_freeLists.clear(); + } + + for (FileList::iterator it = m_files.begin(); it != m_files.end(); ++it) { + (*it).tempFile->close(); + (*it).tempFile->unlink(); + delete (*it).tempFile; + } + + delete [] m_poolPixelSizes; + delete [] m_pools; + + delete m_poolMutex; + delete m_swapMutex; +} + +KisTileManager* KisTileManager::instance() +{ + if(KisTileManager::m_singleton == 0) { + staticDeleter.setObject(KisTileManager::m_singleton, new KisTileManager()); + Q_CHECK_PTR(KisTileManager::m_singleton); + } + return KisTileManager::m_singleton; +} + +void KisTileManager::registerTile(KisTile* tile) +{ + + m_swapMutex->lock(); + + TileInfo* info = new TileInfo(); + info->tile = tile; + info->inMem = true; + info->mmapped = false; + info->onFile = false; + info->file = 0; + info->filePos = 0; + info->size = tile->WIDTH * tile->HEIGHT * tile->m_pixelSize; + info->fsize = 0; // the size in the file + info->validNode = true; + + m_tileMap[tile] = info; + m_swappableList.push_back(info); + info->node = -- m_swappableList.end(); + + m_currentInMem++; + m_bytesTotal += info->size; + m_bytesInMem += info->size; + + doSwapping(); + + if (++counter % 50 == 0) + printInfo(); + + m_swapMutex->unlock(); +} + +void KisTileManager::deregisterTile(KisTile* tile) { + + m_swapMutex->lock(); + + if (!m_tileMap.contains(tile)) { + m_swapMutex->unlock(); + return; + } + // Q_ASSERT(m_tileMap.contains(tile)); + + TileInfo* info = m_tileMap[tile]; + + if (info->onFile) { // It was once mmapped + // To freelist + FreeInfo* freeInfo = new FreeInfo(); + freeInfo->file = info->file; + freeInfo->filePos = info->filePos; + freeInfo->size = info->fsize; + uint pixelSize = (info->size / m_tileSize); + + // It is still mmapped? + if (info->mmapped) { + // munmap it + munmap(info->tile->m_data, info->size); + m_bytesInMem -= info->size; + m_currentInMem--; + } + + if (m_freeLists.capacity() <= pixelSize) + m_freeLists.resize(pixelSize + 1); + m_freeLists[pixelSize].push_back(freeInfo); + + // the KisTile will attempt to delete its data. This is of course silly when + // it was mmapped. So change the m_data to NULL, which is safe to delete + tile->m_data = 0; + } else { + m_bytesInMem -= info->size; + m_currentInMem--; + } + + if (info->validNode) { + m_swappableList.erase(info->node); + info->validNode = false; + } + + m_bytesTotal -= info->size; + + delete info; + m_tileMap.erase(tile); + + doSwapping(); + + m_swapMutex->unlock(); +} + +void KisTileManager::ensureTileLoaded(const KisTile* tile) +{ + + m_swapMutex->lock(); + + TileInfo* info = m_tileMap[tile]; + if (info->validNode) { + m_swappableList.erase(info->node); + info->validNode = false; + } + + if (!info->inMem) { + fromSwap(info); + } + + m_swapMutex->unlock(); +} + +void KisTileManager::maySwapTile(const KisTile* tile) +{ + + m_swapMutex->lock(); + + TileInfo* info = m_tileMap[tile]; + m_swappableList.push_back(info); + info->validNode = true; + info->node = -- m_swappableList.end(); + + doSwapping(); + + m_swapMutex->unlock(); +} + +void KisTileManager::fromSwap(TileInfo* info) +{ + m_swapMutex->lock(); + + if (info->inMem) { + m_swapMutex->unlock(); + return; + } + + doSwapping(); + + Q_ASSERT(info->onFile); + Q_ASSERT(info->file); + Q_ASSERT(!info->mmapped); + + if (!kritaMmap(info->tile->m_data, 0, info->size, PROT_READ | PROT_WRITE, MAP_SHARED, + info->file->handle(), info->filePos)) { + kdWarning() << "fromSwap failed!" << endl; + m_swapMutex->unlock(); + return; + } + + info->inMem = true; + info->mmapped = true; + + m_currentInMem++; + m_bytesInMem += info->size; + + m_swapMutex->unlock(); +} + +void KisTileManager::toSwap(TileInfo* info) { + m_swapMutex->lock(); + + //Q_ASSERT(info->inMem); + if (!info || !info->inMem) { + m_swapMutex->unlock(); + return; + } + + KisTile *tile = info->tile; + + if (!info->onFile) { + // This tile is not yet in the file. Save it there + uint pixelSize = (info->size / m_tileSize); + bool foundFree = false; + + if (m_freeLists.capacity() > pixelSize) { + if (!m_freeLists[pixelSize].empty()) { + // found one + FreeList::iterator it = m_freeLists[pixelSize].begin(); + + info->file = (*it)->file; + info->filePos = (*it)->filePos; + info->fsize = (*it)->size; + + delete *it; + m_freeLists[pixelSize].erase(it); + + foundFree = true; + } + } + + if (!foundFree) { // No position found or free, create a new + long pagesize = sysconf(_SC_PAGESIZE); + TempFile* tfile = 0; + if (m_files.empty() || m_files.back().fileSize >= MaxSwapFileSize) { + m_files.push_back(TempFile()); + tfile = &(m_files.back()); + tfile->tempFile = new KTempFile(); + tfile->fileSize = 0; + } else { + tfile = &(m_files.back()); + } + off_t newsize = tfile->fileSize + info->size; + newsize = newsize + newsize % pagesize; + + if (ftruncate(tfile->tempFile->handle(), newsize)) { + // XXX make these maybe i18n()able and in an error box, but then through + // some kind of proxy such that we don't pollute this with GUI code + kdWarning(DBG_AREA_TILES) << "Resizing the temporary swapfile failed!" << endl; + // Be somewhat pollite and try to figure out why it failed + switch (errno) { + case EIO: kdWarning(DBG_AREA_TILES) << "Error was E IO, " + << "possible reason is a disk error!" << endl; break; + case EINVAL: kdWarning(DBG_AREA_TILES) << "Error was E INVAL, " + << "possible reason is that you are using more memory than " + << "the filesystem or disk can handle" << endl; break; + default: kdWarning(DBG_AREA_TILES) << "Errno was: " << errno << endl; + } + kdWarning(DBG_AREA_TILES) << "The swapfile is: " << tfile->tempFile->name() << endl; + kdWarning(DBG_AREA_TILES) << "Will try to avoid using the swap any further" << endl; + + kdDebug(DBG_AREA_TILES) << "Failed ftruncate info: " + << "tried adding " << info->size << " bytes " + << "(rounded to pagesize: " << newsize << ") " + << "from a " << tfile->fileSize << " bytes file" << endl; + printInfo(); + + m_swapForbidden = true; + m_swapMutex->unlock(); + return; + } + + info->file = tfile->tempFile; + info->fsize = info->size; + info->filePos = tfile->fileSize; + tfile->fileSize = newsize; + } + + //memcpy(data, tile->m_data, info->size); + QFile* file = info->file->file(); + if(!file) { + kdWarning() << "Opening the file as QFile failed" << endl; + m_swapForbidden = true; + m_swapMutex->unlock(); + return; + } + + int fd = file->handle(); + Q_UINT8* data = 0; + if (!kritaMmap(data, 0, info->size, PROT_READ | PROT_WRITE, MAP_SHARED, + fd, info->filePos)) { + kdWarning() << "Initial mmap failed" << endl; + m_swapForbidden = true; + m_swapMutex->unlock(); + return; + } + + memcpy(data, info->tile->m_data, info->size); + munmap(data, info->size); + + m_poolMutex->lock(); + if (isPoolTile(tile->m_data, tile->m_pixelSize)) + reclaimTileToPool(tile->m_data, tile->m_pixelSize); + else + delete[] tile->m_data; + m_poolMutex->unlock(); + + tile->m_data = 0; + } else { + //madvise(info->tile->m_data, info->fsize, MADV_DONTNEED); + Q_ASSERT(info->mmapped); + + // munmap it + munmap(tile->m_data, info->size); + tile->m_data = 0; + } + + info->inMem = false; + info->mmapped = false; + info->onFile = true; + + m_currentInMem--; + m_bytesInMem -= info->size; + + m_swapMutex->unlock(); +} + +void KisTileManager::doSwapping() +{ + m_swapMutex->lock(); + + if (m_swapForbidden || m_currentInMem <= m_maxInMem) { + m_swapMutex->unlock(); + return; + } + +#if 1 // enable this to enable swapping + + Q_UINT32 count = QMIN(m_swappableList.size(), m_swappiness); + + for (Q_UINT32 i = 0; i < count && !m_swapForbidden; i++) { + toSwap(m_swappableList.front()); + m_swappableList.front()->validNode = false; + m_swappableList.pop_front(); + } + +#endif + + m_swapMutex->unlock(); +} + +void KisTileManager::printInfo() +{ + kdDebug(DBG_AREA_TILES) << m_bytesInMem << " out of " << m_bytesTotal << " bytes in memory\n"; + kdDebug(DBG_AREA_TILES) << m_currentInMem << " out of " << m_tileMap.size() << " tiles in memory\n"; + kdDebug(DBG_AREA_TILES) << m_files.size() << " swap files in use" << endl; + kdDebug(DBG_AREA_TILES) << m_swappableList.size() << " elements in the swapable list\n"; + kdDebug(DBG_AREA_TILES) << "Freelists information\n"; + for (uint i = 0; i < m_freeLists.capacity(); i++) { + if ( ! m_freeLists[i].empty() ) { + kdDebug(DBG_AREA_TILES) << m_freeLists[i].size() + << " elements in the freelist for pixelsize " << i << "\n"; + } + } + kdDebug(DBG_AREA_TILES) << "Pool stats (" << m_tilesPerPool << " tiles per pool)" << endl; + for (int i = 0; i < 4; i++) { + if (m_pools[i]) { + kdDebug(DBG_AREA_TILES) << "Pool " << i << ": Freelist count: " << m_poolFreeList[i].count() + << ", pixelSize: " << m_poolPixelSizes[i] << endl; + } + } + if (m_swapForbidden) + kdDebug(DBG_AREA_TILES) << "Something was wrong with the swap, see above for details" << endl; + kdDebug(DBG_AREA_TILES) << endl; +} + +Q_UINT8* KisTileManager::requestTileData(Q_INT32 pixelSize) +{ + m_swapMutex->lock(); + + Q_UINT8* data = findTileFor(pixelSize); + if ( data ) { + m_swapMutex->unlock(); + return data; + } + m_swapMutex->unlock(); + return new Q_UINT8[m_tileSize * pixelSize]; +} + +void KisTileManager::dontNeedTileData(Q_UINT8* data, Q_INT32 pixelSize) +{ + m_poolMutex->lock(); + if (isPoolTile(data, pixelSize)) { + reclaimTileToPool(data, pixelSize); + } else + delete[] data; + m_poolMutex->unlock(); +} + +Q_UINT8* KisTileManager::findTileFor(Q_INT32 pixelSize) +{ + m_poolMutex->lock(); + + for (int i = 0; i < 4; i++) { + if (m_poolPixelSizes[i] == pixelSize) { + if (!m_poolFreeList[i].isEmpty()) { + Q_UINT8* data = m_poolFreeList[i].front(); + m_poolFreeList[i].pop_front(); + m_poolMutex->unlock(); + return data; + } + } + if (m_pools[i] == 0) { + // allocate new pool + m_poolPixelSizes[i] = pixelSize; + m_pools[i] = new Q_UINT8[pixelSize * m_tileSize * m_tilesPerPool]; + // j = 1 because we return the first element, so no need to add it to the freelist + for (int j = 1; j < m_tilesPerPool; j++) + m_poolFreeList[i].append(&m_pools[i][j * pixelSize * m_tileSize]); + m_poolMutex->unlock(); + return m_pools[i]; + } + } + + m_poolMutex->unlock(); + return 0; +} + +bool KisTileManager::isPoolTile(Q_UINT8* data, Q_INT32 pixelSize) { + + if (data == 0) + return false; + + m_poolMutex->lock(); + for (int i = 0; i < 4; i++) { + if (m_poolPixelSizes[i] == pixelSize) { + bool b = data >= m_pools[i] + && data < m_pools[i] + pixelSize * m_tileSize * m_tilesPerPool; + if (b) { + m_poolMutex->unlock(); + return true; + } + } + } + m_poolMutex->unlock(); + return false; +} + +void KisTileManager::reclaimTileToPool(Q_UINT8* data, Q_INT32 pixelSize) { + m_poolMutex->lock(); + for (int i = 0; i < 4; i++) { + if (m_poolPixelSizes[i] == pixelSize) + if (data >= m_pools[i] && data < m_pools[i] + pixelSize * m_tileSize * m_tilesPerPool) { + m_poolFreeList[i].append(data); + } + } + m_poolMutex->unlock(); +} + +void KisTileManager::configChanged() { + KConfig * cfg = KGlobal::config(); + cfg->setGroup(""); + m_maxInMem = cfg->readNumEntry("maxtilesinmem", 4000); + m_swappiness = cfg->readNumEntry("swappiness", 100); + + m_swapMutex->lock(); + doSwapping(); + m_swapMutex->unlock(); +} + +bool KisTileManager::kritaMmap(Q_UINT8*& result, void *start, size_t length, + int prot, int flags, int fd, off_t offset) { + result = (Q_UINT8*) mmap(start, length, prot, flags, fd, offset); + + // Same here for warning and GUI + if (result == (Q_UINT8*)-1) { + kdWarning(DBG_AREA_TILES) << "mmap failed: errno is " << errno << "; we're probably going to crash very soon now...\n"; + + // Try to ignore what happened and carry on, but unlikely that we'll get + // much further, since the file resizing went OK and this is memory-related... + if (errno == ENOMEM) { + kdWarning(DBG_AREA_TILES) << "mmap failed with E NOMEM! This means that " + << "either there are no more memory mappings available for Krita, " + << "or that there is no more memory available!" << endl; + } + + kdWarning(DBG_AREA_TILES) << "Trying to continue anyway (no guarantees)" << endl; + kdWarning(DBG_AREA_TILES) << "Will try to avoid using the swap any further" << endl; + kdDebug(DBG_AREA_TILES) << "Failed mmap info: " + << "tried mapping " << length << " bytes" << endl; + if (!m_files.empty()) { + kdDebug(DBG_AREA_TILES) << "Probably to a " << m_files.back().fileSize << " bytes file" << endl; + } + printInfo(); + + // Be nice + result = 0; + + return false; + } + + return true; +} diff --git a/krita/core/tiles/kis_tilemanager.h b/krita/core/tiles/kis_tilemanager.h new file mode 100644 index 00000000..a66ca634 --- /dev/null +++ b/krita/core/tiles/kis_tilemanager.h @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2005 Bart Coppens <kde@bartcoppens.be> + * + * 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_TILEMANAGER_H_ +#define KIS_TILEMANAGER_H_ + +#include <sys/types.h> + +#include <qglobal.h> +#include <qmap.h> +#include <qvaluelist.h> +#include <qmutex.h> + +#include <ktempfile.h> + +class KisTile; +class KisTiledDataManager; + +/** + * This class keeps has the intention to make certain tile-related operations faster or more + * efficient. It does this by keeping lots of info on KisTiles, and manages the way they are + * created, used, etc. + * It mainly does the following more visible things + * * provide a way to store tiles on disk to a swap file, to reduce memory usage + * * keep a list of previously swapped (but now unused) tiles, to reuse these when we want + * to swap new tiles. + * * tries to preallocate and recycle some tiles to make future allocations faster + * (not done yet) + */ +class KisTileManager { +public: + ~KisTileManager(); + static KisTileManager* instance(); + +public: // Tile management + void registerTile(KisTile* tile); + void deregisterTile(KisTile* tile); + // these can change the tile indirectly, though, through the actual swapping! + void ensureTileLoaded(const KisTile* tile); + void maySwapTile(const KisTile* tile); + +public: // Pool management + Q_UINT8* requestTileData(Q_INT32 pixelSize); + void dontNeedTileData(Q_UINT8* data, Q_INT32 pixelSize); + +public: // Configuration + void configChanged(); + +private: + KisTileManager(); + KisTileManager(KisTileManager&) {} + KisTileManager operator=(const KisTileManager&); + +private: + static KisTileManager *m_singleton; + + // For use when any swap-allocating function failed; the risk of swap allocating failing + // again is too big, and we'd clutter the logs with kdWarnings otherwise + bool m_swapForbidden; + + // This keeps track of open swap files, and their associated filesizes + struct TempFile { + KTempFile* tempFile; + off_t fileSize; + }; + // validNode says if you can swap it (true) or not (false) mmapped, if this tile + // currently is memory mapped. If it is false, but onFile, it is on disk, + // but not mmapped, and should be mapped! + // filePos is the position inside the file; size is the actual size, fsize is the size + // being used in the swap for this tile (may be larger!) + // The file points to 0 if it is not swapped, and to the relevant TempFile otherwise + struct TileInfo { KisTile *tile; KTempFile* file; off_t filePos; int size; int fsize; + QValueList<TileInfo*>::iterator node; + bool inMem; bool onFile; bool mmapped; bool validNode; }; + typedef struct { KTempFile* file; off_t filePos; int size; } FreeInfo; + typedef QMap<const KisTile*, TileInfo*> TileMap; + typedef QValueList<TileInfo*> TileList; + typedef QValueList<FreeInfo*> FreeList; + typedef QValueVector<FreeList> FreeListList; + typedef QValueList<Q_UINT8*> PoolFreeList; + typedef QValueList<TempFile> FileList; + + + TileMap m_tileMap; + TileList m_swappableList; + FreeListList m_freeLists; + FileList m_files; + Q_INT32 m_maxInMem; + Q_INT32 m_currentInMem; + Q_UINT32 m_swappiness; + Q_INT32 m_tileSize; // size of a tile if it used 1 byte per pixel + unsigned long m_bytesInMem; + unsigned long m_bytesTotal; + + Q_UINT8 **m_pools; + Q_INT32 *m_poolPixelSizes; + Q_INT32 m_tilesPerPool; + PoolFreeList *m_poolFreeList; + QMutex * m_poolMutex; + QMutex * m_swapMutex; + + // This is the constant that we will use to see if we want to add a new tempfile + // We use 1<<30 (one gigabyte) because apparently 32bit systems don't really like very + // large files. + static const long MaxSwapFileSize = 1<<30; // For debugging purposes: 1<<20 is a megabyte + + // debug + int counter; + +private: + void fromSwap(TileInfo* info); + void toSwap(TileInfo* info); + void doSwapping(); + void printInfo(); + Q_UINT8* findTileFor(Q_INT32 pixelSize); + bool isPoolTile(Q_UINT8* data, Q_INT32 pixelSize); + void reclaimTileToPool(Q_UINT8* data, Q_INT32 pixelSize); + + // Mmap wrapper that prints warnings on error. The result is stored in the *& result + // the return value is true on succes, false on failure. Other args as in man mmap + bool kritaMmap(Q_UINT8*& result, void *start, size_t length, + int prot, int flags, int fd, off_t offset); +}; + +#endif // KIS_TILEMANAGER_H_ diff --git a/krita/core/tiles/tests/Makefile.am b/krita/core/tiles/tests/Makefile.am new file mode 100644 index 00000000..bc729320 --- /dev/null +++ b/krita/core/tiles/tests/Makefile.am @@ -0,0 +1,15 @@ +AM_CPPFLAGS = -I$(srcdir)/../ \ + -I$(srcdir)/../.. \ + -I$(srcdir)/../../../sdk \ + $(all_includes) + +# The check_ target makes sure we don't install the modules, +# $(KDE_CHECK_PLUGIN) assures a shared library is created. +check_LTLIBRARIES = kunittest_kis_tiled_data_tester.la +kunittest_kis_tiled_data_tester_la_SOURCES = kis_tiled_data_tester.cpp +kunittest_kis_tiled_data_tester_la_LIBADD = -lkunittest ../../../libkritacommon.la +kunittest_kis_tiled_data_tester_la_LDFLAGS = -module $(KDE_CHECK_PLUGIN) $(all_libraries) + +check-local: kunittest_kis_tiled_data_tester.la + kunittestmodrunner + diff --git a/krita/core/tiles/tests/kis_tiled_data_tester.cpp b/krita/core/tiles/tests/kis_tiled_data_tester.cpp new file mode 100644 index 00000000..2d7fbe05 --- /dev/null +++ b/krita/core/tiles/tests/kis_tiled_data_tester.cpp @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2005 Adrian Page <adrian@pagenet.plus.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 <kunittest/runner.h> +#include <kunittest/module.h> + +#include "kis_tiled_data_tester.h" +#include "kis_datamanager.h" +#include "kis_global.h" + +using namespace KUnitTest; + +KUNITTEST_MODULE( kunittest_kis_tiled_data_tester, "Tiled Data Tester" ); +KUNITTEST_MODULE_REGISTER_TESTER( KisTiledDataTester ); + +#define TEST_PIXEL_SIZE 4 + +static Q_UINT8 defaultPixel[TEST_PIXEL_SIZE] = {0, 0, 0, OPACITY_TRANSPARENT}; + +void KisTiledDataTester::allTests() +{ + KisDataManager *dm = new KisDataManager(TEST_PIXEL_SIZE, defaultPixel); + + Q_INT32 extentX; + Q_INT32 extentY; + Q_INT32 extentWidth; + Q_INT32 extentHeight; + + dm->extent(extentX, extentY, extentWidth, extentHeight); + CHECK(extentWidth, 0); + CHECK(extentHeight, 0); + + const Q_UINT8 *readOnlyPixel = dm->pixel(KisTile::WIDTH/2, KisTile::HEIGHT/2); + dm->extent(extentX, extentY, extentWidth, extentHeight); + CHECK(extentWidth, 0); + CHECK(extentHeight, 0); + + Q_UINT8 *writablePixel = dm->writablePixel(KisTile::WIDTH/2, KisTile::HEIGHT/2); + dm->extent(extentX, extentY, extentWidth, extentHeight); + CHECK(extentX, 0); + CHECK(extentY, 0); + CHECK(extentWidth, KisTile::WIDTH); + CHECK(extentHeight, KisTile::HEIGHT); + + writablePixel = dm->writablePixel(-KisTile::WIDTH, -KisTile::HEIGHT); + dm->extent(extentX, extentY, extentWidth, extentHeight); + CHECK(extentX, -KisTile::WIDTH); + CHECK(extentY, -KisTile::HEIGHT); + CHECK(extentWidth, 2*KisTile::WIDTH); + CHECK(extentHeight, 2*KisTile::HEIGHT); + + dm->clear(); + dm->extent(extentX, extentY, extentWidth, extentHeight); + CHECK(extentWidth, 0); + CHECK(extentHeight, 0); + + delete dm; +} + diff --git a/krita/core/tiles/tests/kis_tiled_data_tester.h b/krita/core/tiles/tests/kis_tiled_data_tester.h new file mode 100644 index 00000000..8a569d23 --- /dev/null +++ b/krita/core/tiles/tests/kis_tiled_data_tester.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2005 Adrian Page <adrian@pagenet.plus.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_TILED_DATA_TESTER_H +#define KIS_TILED_DATA_TESTER_H + +#include <kunittest/tester.h> + +class KisTiledDataTester : public KUnitTest::Tester +{ +public: + void allTests(); +}; + +#endif + |